Java多线程工具包java.util.concurrent---原子性和ABA问题

Posted yvan1115

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java多线程工具包java.util.concurrent---原子性和ABA问题相关的知识,希望对你有一定的参考价值。

什么是原子性 什么是可见性

  • 可见性
    在多核处理器中,如果多个线程对一个变量(假设)进行操作,但是这多个线程有可能被分配到多个处理器中运行,那么编译器会对代码进行优化,当线程要处理该变量时,多个处理器会将变量从主存复制一份分别存储在自己的片上存储器中,等到进行完操作后,再赋值回主存。(这样做的好处是提高了运行的速度,因为在处理过程中多个处理器减少了同主存通信的次数);同样在单核处理器中这样由于“备份”造成的问题同样存在!
    这样的优化带来的问题之一是变量可见性——如果线程t1与线程t2分别被安排在了不同的处理器上面,那么t1与t2对于变量A的修改时相互不可见,如果t1给A赋值,然后t2又赋新值,那么t2的操作就将t1的操作覆盖掉了,这样会产生不可预料的结果。
  • 原子性
    原子的意思代表着——“不可分”
    由不可分性可知,原子性是拒绝多线程操作的(只有分解为多步操作,多个线程才能对其操作:就像一个盒子里有多个兵乓球,多个人能够从盒子里拿乒乓球;如果盒子只有一个兵乓球,一个人拿的话,其他人就拿不到了;这就是原子性,乒乓球就具有原子性,人就相当于原子)
    简而言之——不被线程调度器中断的操作
    例如:
    int i=1;这是原子性操作
    i++; 这不是原子性操作,会被拆解

  • 实现原子性

package com.yvan.atomicReference;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;

/**
 * 原子性和非原子性
 * 
 * @author yvan
 *
 */
public class AppT 

    private static int count = 0;
    private static volatile int Vcount = 0;
    private static AtomicInteger ai = new AtomicInteger(0);
    private static AtomicReference<Integer> atomicReference = new AtomicReference<Integer>(0);

    public static void main(String[] args) throws InterruptedException 
        for (int i = 1; i < 4; i++) 
            System.out.println("开始执行第" + i + "次");
            notAtomic();
            volatileAtomic();
            atomic();
            atomicReference();
            System.out.println("结束执行第" + i + "次\\r\\n");
        

    

    /**
     * 非原子性
     */
    public static void notAtomic() 
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < 1000; i++) 
            executorService.execute(new Runnable() 
                @Override
                public void run() 
                    count++;
                
            );
        
        executorService.shutdown();
        while (!executorService.isTerminated()) 

        
        System.out.println("notAtomic 结果:" + count);
        count = 0;
    

    /**
     * 非原子性
     */
    public static void volatileAtomic() 
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < 1000; i++) 
            executorService.execute(new Runnable() 
                @Override
                public void run() 
                    Vcount++;
                
            );
        
        executorService.shutdown();
        while (!executorService.isTerminated()) 

        
        System.out.println("volatileAtomic 结果:" + Vcount);
        Vcount = 0;
    

    /**
     * 原子性
     */
    public static void atomic() 
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < 1000; i++) 
            executorService.execute(new Runnable() 
                @Override
                public void run() 
                    ai.getAndIncrement();
                
            );
        
        executorService.shutdown();
        while (!executorService.isTerminated()) 

        
        System.out.println("atomic 结果:" + ai.get());
        ai.set(0);
    

    /**
     * 原子性
     */
    public static void atomicReference() throws InterruptedException 
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < 1000; i++) 
            executorService.execute(new Runnable() 
                @Override
                public void run() 
                    while (true) 
                        Integer temp = atomicReference.get();
                        if (atomicReference.compareAndSet(temp, temp + 1)) 
                            break;
                        
                    
                
            );
        
        executorService.shutdown();
        while (!executorService.isTerminated()) 

        
        System.out.println("atomicReference 结果:" + atomicReference.get());
        atomicReference.set(0);
    


某次执行结果

开始执行第1次
notAtomic 结果:924
volatileAtomic 结果:967
atomic 结果:1000
atomicReference 结果:1000
结束执行第1次

开始执行第2次
notAtomic 结果:1000
volatileAtomic 结果:975
atomic 结果:1000
atomicReference 结果:1000
结束执行第2次

开始执行第3次
notAtomic 结果:952
volatileAtomic 结果:973
atomic 结果:1000
atomicReference 结果:1000
结束执行第3次

这里说明一下,volatile 不保证原子性

ABA问题

  • 什么是ABA问题

套用这位博主的例子

举个通俗点的例子,你倒了一杯水放桌子上,干了点别的事,然后同事把你水喝了又给你重新倒了一杯水,你回来看水还在,拿起来就喝,如果你不管水中间被人喝过,只关心水还在,这就是ABA问题。

这篇博客中讲的很明白,CAS带来的ABA问题
CAS和ABA问题

乐观锁用到的机制就是CAS,Compare and Swap[比较 替换]。
CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。

  • 如何形成ABA问题

比如说一个线程one从内存位置V中取出A,这时候另一个线程two也从内存中取出A,并且two进行了一些操作变成了B,然后two又将V位置的数据变成A,这时候线程one进行CAS操作发现内存中仍然是A,然后one操作成功。尽管线程one的CAS操作成功,但是不代表这个过程就是没有问题的。

解决ABA问题

java为我们提供了AtomicMarkableReferenceAtomicStampedReference

  • 示例
package com.yvan.aba;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicStampedReference;

/**
 * ABA问题
 * 
 * @author yvan
 *
 */
public class AppMain 
    private static AtomicInteger atomicInt = new AtomicInteger(100);
    private static AtomicStampedReference<Integer> atomicStampedRef = new AtomicStampedReference<>(100, 0);

    public static void main(String[] args) throws InterruptedException 
        Thread t1 = new Thread(new Runnable() 
            @Override
            public void run() 
                atomicInt.compareAndSet(100, 101);
                atomicInt.compareAndSet(101, 100);
            
        );

        Thread t2 = new Thread(new Runnable() 
            @Override
            public void run() 
                try 
                    TimeUnit.SECONDS.sleep(1);
                 catch (InterruptedException e) 
                
                boolean flag = atomicInt.compareAndSet(100, 102);
                // 虽然返回为true,但实际过程
                // 1、100→101
                // 2、101→100
                // 3、100→102
                System.out.println(flag);
            
        );

        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(atomicInt.get());
        /* 解决ABA问题 */
        int stamp = atomicStampedRef.getStamp();
        Thread refT1 = new Thread(new Runnable() 
            @Override
            public void run() 
                try 
                    TimeUnit.SECONDS.sleep(1);
                 catch (Exception e) 
                
                atomicStampedRef.compareAndSet(100, 101, atomicStampedRef.getStamp(), atomicStampedRef.getStamp() + 1);
                atomicStampedRef.compareAndSet(101, 100, atomicStampedRef.getStamp(), atomicStampedRef.getStamp() + 1);
                System.out.println("执行");
            
        );

        Thread refT2 = new Thread(new Runnable() 
            @Override
            public void run() 
                int stamp = atomicStampedRef.getStamp();
                try 
                    TimeUnit.SECONDS.sleep(2);
                 catch (Exception e) 
                
                boolean flag = atomicStampedRef.compareAndSet(100, 101, stamp, stamp + 1);
                System.out.println(flag); // false

            
        );
        refT1.start();
        refT1.join();// join使子线程先执行,主线程等待子线程执行后再向下执行
        boolean flag = atomicStampedRef.compareAndSet(100, 101, stamp, stamp + 1);
        System.out.println(flag); // false
        System.out.println(atomicStampedRef.getReference());
    

true
102
执行
false
100

从结果我们可以看出AtomicInteger 执行成功,产生了ABA问题,而AtomicStampedReference没有

  • AtomicStampedReference如何避免的ABA问题

public boolean compareAndSet(V expectedReference,
V newReference,
int expectedStamp,
int newStamp)

expectedReference - 该引用的预期值
newReference - 该引用的新值
expectedStamp - 该标志的预期值
newStamp - 该标志的新值 

如果当前引用 == 预期引用,并且当前标志等于预期标志,则以原子方式将该引用和该标志的值设置为给定的更新值。

以上是关于Java多线程工具包java.util.concurrent---原子性和ABA问题的主要内容,如果未能解决你的问题,请参考以下文章

Java多线程工具包java.util.concurrent---CyclicBarrier

Java多线程工具包java.util.concurrent---ExecutorService

Java多线程工具包java.util.concurrent---ReadWriteLock

Java多线程工具包java.util.concurrent---Lock

Java多线程_同步工具CyclicBarrier

Java多线程(线程池原子性并发工具类)