为编程爱好者分享易语言教程源码的资源网
好用的代理IP,游戏必备 ____广告位招租____ 服务器99/年 ____广告位招租____ ____广告位招租____ 挂机,建站服务器
好用的代理IP,游戏必备 ____广告位招租____ 服务器低至38/年 ____广告位招租____ ____广告位招租____ 挂机,建站服务器

网站首页 > 网络编程 > 其它综合 正文

原子变量操作类AtomicLong详解

三叶资源网 2023-01-08 20:18:50 其它综合 239 ℃ 0 评论

JUC并发包中有AtomicInteger、AtomicIntegerArray、AtomicLong、AtomicBoolean等原子变量操作类,他们原理都类似,本文主要分析为什么需要原子操作类以及AtomicLong类的实现原理。

为什么需要原子变量操作类?

public class Test {
    
    private static long count = 0L;
    
    private static void increase() {
        count++;
    }
    public static void main(String[] args) throws InterruptedException {
        Thread[] threads = new Thread[10];
        for (int i = 0; i < threads.length; i++) {
            threads[i] = new Thread(new Runnable() {
                public void run() {
                    for (int j = 0; j < 1000; j++) {
                        increase();
                    }
                }
            });
            threads[i].start();
        }
        for (Thread thread : threads) {
            thread.join();
        }
        System.out.println(count);
    }
}

上方的代码创建一个普通long类型的变量count,启动10个线程分别对count进行1000次递增操作。我们知道count++解析为count=count+1,这个操作并不是原子性的,多线程执行这个操作必然会出现问题,如果按照正常的并发情况,执行结果应该是10000,但是实际情况是每次执行的结果都是小于10000的数字。为什么呢?

有人肯定会说,使用volatile来修饰count变量是否能解决这个问题,这里顺便来说下volatile关键字的作用及使用场景。

volatile作用

  1. 当一个共享变量被volatile修饰时,它会保证修改的值立即被更新到主存,通俗来讲就是,线程A对一个volatile变量的修改,对于其它线程来说是可见的,即线程每次获取volatile变量的值都是最新的
  2. 禁止指令重排序

volatile使用场景

  1. 对变量的写操作不依赖当前值
  2. 该变量没有包含在具有其它变量的不变式中
private static volatile long count = 0L;

用volatile关键字修饰count后再次测试,发现执行结果依然小于10000。
分析volatile使用场景发现必须满足对变量的写操作不应该依赖当前值,而count++明显是依赖当前值的,所以多线程下执行count++是无法通过volatile保证结果准确性的。

那既然无法通过volatile关键字来保证准确性,我们就可以尝试使用AtomicLong原子变量操作类来进行操作,代码如下

public class Test {

    private static AtomicLong count = new AtomicLong(0L);

    private static void increase() {
        count.incrementAndGet();
    }
    public static void main(String[] args) throws InterruptedException {
        Thread[] threads = new Thread[10];
        for (int i = 0; i < threads.length; i++) {
            threads[i] = new Thread(new Runnable() {
                public void run() {
                    for (int j = 0; j < 1000; j++) {
                        increase();
                    }
                }
            });
            threads[i].start();
        }
        for (Thread thread : threads) {
            thread.join();
        }
        System.out.println(count);
    }
}

我们发现不管怎么执行,最后的结果都是10000,这是因为AtomicInteger.incrementAndGet()方法是原子性的。

AtomicLong原理分析

首先,我们来看一下一下AtomicLong的属性

public class AtomicLong extends Number implements java.io.Serializable {
	//通过Unsafe.getUnsafe()方法,获取unsafe实例
	private static final Unsafe unsafe = Unsafe.getUnsafe();
	//存放变量value的偏移量
    private static final long valueOffset;
    //判断JVM是否支持Long类型无锁CAS
    static final boolean VM_SUPPORTS_LONG_CAS = VMSupportsCS8();
    //VMSupportsCS8()方法是native方法
    private static native boolean VMSupportsCS8();
    //静态代码块获取value在AtomicLong中的偏移量
    static {
        try {
            valueOffset = unsafe.objectFieldOffset
                (AtomicLong.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }
	//实际使用的变量值,可以看到这里也是使用volatile修饰的,保证多线程下的内存可见性
	private volatile long value;
	
	//构造函数
    public AtomicLong(long initialValue) {
        value = initialValue;
    }
    public AtomicLong() {
    }
}

然后我们来看下AtomicLong的主要方法,都是使用Unsafe来实现的


//调用unsafe方法原子性设置value的值,并返回设置之前的值
public final long getAndSet(long newValue) {
        return unsafe.getAndSetLong(this, valueOffset, newValue);
}
    
	//getAndSetLong()方法是使用CAS方式设置value值的
	//其中getLongVolatile()方法和compareAndSwapLong()方法是native方法
	public final long getAndSetLong(Object var1, long var2, long var4) {
        long var6;
        //每个线程获取到变量的当前值,然后使用CAS方式设置新的值,如果设置失败,则循环继续尝试,直到成功为止
        do {
            var6 = this.getLongVolatile(var1, var2);
        } while(!this.compareAndSwapLong(var1, var2, var6, var4));
		
        return var6;
    }

//如果当前值等于期望值,则原子性设置value为新的值
public final boolean compareAndSet(long expect, long update) {
    return unsafe.compareAndSwapLong(this, valueOffset, expect, update);
}

//调用unsafe方法,原子性设置当前值自增1,返回修改前的值
public final long getAndIncrement() {
        return unsafe.getAndAddLong(this, valueOffset, 1L);
//调用unsafe方法,原子性设置当前值自减1,返回修改前的值
public final long getAndDecrement() {
        return unsafe.getAndAddLong(this, valueOffset, -1L);
    }
//调用unsafe方法,原子性设置当前值加上参数值,返回修改前的值
public final long getAndAdd(long delta) {
        return unsafe.getAndAddLong(this, valueOffset, delta);
//调用unsafe方法,原子性设置当前值自增1,返回修改后的值
public final long incrementAndGet() {
        return unsafe.getAndAddLong(this, valueOffset, 1L) + 1L;
    }
//调用unsafe方法,原子性设置当前值自减1,返回修改后的值
public final long decrementAndGet() {
        return unsafe.getAndAddLong(this, valueOffset, -1L) - 1L;
    }
//调用unsafe方法,原子性设置当前值加上参数值,返回修改后的值
public final long addAndGet(long delta) {
        return unsafe.getAndAddLong(this, valueOffset, delta) + delta;
    }

可以看到,AtomicLong的操作都是通过Unsafe类来实现的

Tags:

来源:三叶资源网,欢迎分享,公众号:iisanye,(三叶资源网⑤群:21414575

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

百度站内搜索
关注微信公众号
三叶资源网⑤群:三叶资源网⑤群

网站分类
随机tag
LOL无限视距胆码随机内存运行模块斗鱼TCP协议抖音快速启动快递查询内涵段子辅助源码多线程模板精易皮肤模块DLL函数查看器界面控件配置保存迅雷网站登录易语言多开中控源码音频处理图像二值化Exui超级编辑框应用二维码自动裁剪
最新评论