java开发:java多线程:原子类AtomicInteger和AtomicStampedReference使用详解

在上一节java开发:乐观锁CAS机制中我们说过CAS机制的原理,与及使用CAS会发生的ABA问题解决办法。
java中提供一些列的基本数据类型原子操作类用来实现操作基本数据类型时保证线程安全,其底层就是使用CAS机制实现的。AtomicInteger则是用来操作int类型的数据,保证线程安全。

什么是ABA问题呢?举个例子:比如说我的卡里有100大洋,此时有俩个线程同时去操作这100大洋,它们拿到的副本都是100。若线程2被阻塞了,线程1执行任务扣掉了50大洋,现在卡里剩50。正好我妈(线程3)此时刚好打给我50大洋,现在卡里的钱又变回了100。当线程2被唤醒后也执行扣款操作,扣掉50大洋,当它准备提交数据时比较旧的预期值和共享内存的实际值发现都是100,因此线程2也扣款成功,卡里只剩50大洋。。。想想就不对劲啊,我本来有100,我妈打给我50,我就取了50。不应该是剩100吗?这不害我白白损失了50大洋吗

 	final AtomicInteger atomicInteger = new AtomicInteger(100);
        ExecutorService service = Executors.newCachedThreadPool();
        service.submit(new Callable<Object>() {
            @Override
            public Object call() throws Exception {  
         		//获取最新的值
               int result = atomicInteger.get();
                System.out.println("线程1预期值"+result);
                //休眠五秒
                Thread.sleep(5000);
                //修改共享变量,减去50
                atomicInteger.addAndGet(-50);
                System.out.println("线程1新值"+atomicInteger.get() );
                return null;
            }
        });

       service.submit(new Callable<Object>() {
            @Override
            public Object call() throws Exception {
                //获取最新的值
                int result = atomicInteger.get();
                System.out.println("线程2预期值"+result);
                //修改共享变量,减去50
                atomicInteger.addAndGet(-50);
                return null;
            }
        });

        service.submit(new Callable<Object>() {
            @Override
            public Object call() throws Exception {
            	//休眠1秒
                Thread.sleep(1000);
                //获取最新值
                int result = atomicInteger.get();
                System.out.println("线程3预期值"+result);
                atomicInteger.addAndGet(50);
                return null;
            }

        });
2019-12-27 21:26:44.691 14154-14189/? I/System.out: 线程1预期值100
2019-12-27 21:26:44.693 14154-14190/? I/System.out: 线程2预期值100
2019-12-27 21:26:45.694 14154-14191/com.example.serializationapplication I/System.out: 线程3预期值50
`2019-12-27 21:26:49.693 14154-14189/? I/System.out: 线程1新值50

上诉代码就是一个典型的ABA问题,暂且不管AtomicInteger 底层是怎么实现CAS的。
我们的初始值为100,线程1和线程2可以说是同时获取atomicInteger对象的值都是100,线程1获取完后进入休眠状态。此时线程2执行任务扣去了50,共享变量成了50,然后线程3休眠结束,获取atomicInteger对象的值的得到50,线程3执行任务又把共享变量改成了100。等待线程1休眠结束后,执行任务扣去50,在提交之前它判断旧的预期值和共享内存的实际值是否相等,俩者得到的都是100,因此线程1认为在它休眠过程中这个变量没有被修改,则它扣款成功,共享变量变为50。这就CAS造成的ABA问题。而在上一篇文章我们也说到解决ABA问题就是给共享变量加上版本号,而java中则提供AtomicStampedReference帮助我们实现了。把上诉的代码修改:

 final AtomicStampedReference reference =new AtomicStampedReference(100,0);
        ExecutorService service = Executors.newCachedThreadPool();
        service.submit(new Callable<Object>() {
            @Override
            public Object call() throws Exception {
            	//获取旧值
                int result = (int) reference.getReference();
                System.out.println("线程1的预期值"+result);
                //休眠五秒
                Thread.sleep(5000);                

//参数:旧的预期值、新值、旧的版本号、新版本号         
reference.compareAndSet(reference.getReference(),50,reference.getStamp(),reference.getStamp()+1);
                System.out.println("线程1的新值"+atomicInteger.get());
                return null;
            }
        });

        Future future = service.submit(new Callable<Object>() {
            @Override
            public Object call() throws Exception {
                int result = (int) reference.getReference();
                System.out.println("线程2的预期值"+result);
                reference.compareAndSet(reference.getReference(),50,reference.getStamp(),reference.getStamp()+1);
                return null;
            }
        });

        service.submit(new Callable<Object>() {
            @Override
            public Object call() throws Exception {
                Thread.sleep(1000);
                int result = (int) reference.getReference();
                System.out.println("线程3的预期值"+result);
                reference.compareAndSet(reference.getReference(),100,reference.getStamp(),reference.getStamp()+1);
                return null;
            }

        });

2019-12-27 21:46:42.572 15859-15905/com.example.serializationapplication I/System.out: 线程1的预期值100
2019-12-27 21:46:42.573 15859-15906/com.example.serializationapplication I/System.out: 线程2的预期值100
2019-12-27 21:46:43.582 15859-15907/com.example.serializationapplication I/System.out: 线程3的预期值50
2019-12-27 21:46:47.573 15859-15905/com.example.serializationapplication I/System.out: 线程1的新值100

AtomicStampedReference 代替了AtomicIntegerAtomicStampedReference 的构造函数多了一个版本号,getReference()方法我们可以获取当前的值,getStamp()可以获取当前的版本号。可以看到我们输出的结果解决了ABA问题。

发布了194 篇原创文章 · 获赞 42 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/qq_39027256/article/details/103738487