1.Atomic原子类

  Atomic原子类就是具有原子/原子操作的类,具体类别见下图

  

 

 

  按照类型分,可以分为基本数据类型,数组类型,引用类型,升级类型,累加器。

  AtomicInteger用的比较多,介绍一下其方法。

  获取值

    /**
     * Gets the current value.
     *
     * @return the current value
     */
    public final int get() {
        return value;
    }

  获取值然后设置为新值

  

    /**
     * Atomically sets to the given value and returns the old value.
     *
     * @param newValue the new value
     * @return the previous value
     */
    public final int getAndSet(int newValue) {
        return unsafe.getAndSetInt(this, valueOffset, newValue);
    }

  获取值,自增/自减,注意是先获取值,再自增/还有相同的incrementAndGet和decrementAndGet(自增,自减后再获取)

   public final int getAndIncrement() {
        return unsafe.getAndAddInt(this, valueOffset, 1);
    }

    /**
     * Atomically decrements by one the current value.
     *
     * @return the previous value
     */
    public final int getAndDecrement() {
        return unsafe.getAndAddInt(this, valueOffset, -1);
    }

  获取后相加

  

    /**
     * Atomically adds the given value to the current value.
     *
     * @param delta the value to add
     * @return the previous value
     */
    public final int getAndAdd(int delta) {
        return unsafe.getAndAddInt(this, valueOffset, delta);
    }

  比较后,如果相等则替换为新值

    public final boolean weakCompareAndSet(int expect, int update) {
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }

2.Atomic原子类的作用以及原理

  在多线程同时操作一个变量,i++的时候,通常是用synchronized来解决。有了原子类后,我们可以通过用原子类来定义变量,在线程安全的前提下,原子操作类更加简单,高效。

  测试类

public class AtomicTest {

    public static void main(String[] args) {

        Addself addself = new Addself();
        for(int i =0; i<10; i++) {
            new Thread() {
                @Override
                public void run() {
                    try {
                        System.out.println(addself.getCount());
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }.start();
        }
    }

}

  实现类

  

public class Addself {
    
    //1使用原子类的自增,获取方法
    private AtomicInteger count = new AtomicInteger(0);
    public int getCount() throws InterruptedException {
        Thread.sleep(1000);
        return count.incrementAndGet();
    }
    
    //2不使用任何多线程关键字
//    private int count = 0;
//
//    public  int getCount() throws InterruptedException {
//        count++;
//        Thread.sleep(1000);
//        return count;
//    }
    
    //3加volatile和synchronized修饰变量与方法
//    private volatile  int count = 0;
//
//    public synchronized int getCount() throws InterruptedException {
//        count++;
//        Thread.sleep(1000);
//        return count;
//    }

}

  其中case1,虽然每个线程都等待了1s,但是会发现,10个线程不到2s就跑完了

6
10
4
7
2
9
8
1
5
3

  case2,木有考虑多线程,由于等待了1s,导致获取到的全是10.

10
10
10
10
10
10
10
10
10
10

  case3,利用synchronized锁住了实例方法,每次只能一个线程去运行,所以在控制台上1~10是每隔1s打印出来的(如果不考虑其他运行的时间)

1
2
3
4
5
6
7
8
9
10

  那Atomic原子类是怎么保证线程安全的。  

  比如AtomicInteger,其中的value定义是这样的,用了volatile来修饰该变量。保证了该变量的可见性,在JVM中取到的是最新的值。

    // setup to use Unsafe.compareAndSwapInt for updates
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final long valueOffset;

    static {
        try {
            valueOffset = unsafe.objectFieldOffset
                (AtomicInteger.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }

    private volatile int value;

   下面的就有意思了,看源码果然不会让人失望,你会发现,除了value还有一个叫valueOffest的变量,修饰符是private static final long 。然后你再往下看会发现有很多方法都用到了这个参数,然后还会发现,基本上很多方法都用到了unsafe.function()

而里面的参数都是(this,valueOffset,?),这个参数是通过unsafe.objectFieldOffset获取到的原来值的内存地址。

 

    /**
     * Atomically increments by one the current value.
     *
     * @return the previous value
     */
    public final int getAndIncrement() {
        return unsafe.getAndAddInt(this, valueOffset, 1);
    }

  然后执行获取自增方法时,会先比较当前值与原来值是否相等,如果相等,则进行后面的操作。

  所以说,Atomic原子类是通过 CAS (compare and swap) + volatile 和 native 方法来保证原子操作的。

3.ABA问题

  你会发现,上面说到,只会在用的时候比较2个值是否一样,但是没有在乎过程,就会出现ABA问题,大概意思就是,线程1拿到了值为20,但是在等待IO,然后线程2拿过去,先改成了30,然后有改成了20,线程2结束,线程1刚好继续运行,线程1一比较,欸,还是20啊,那我继续用了。看起来没问题,但是中间有个值变化的过程。咋办呢,别慌~,有个AtomicStampedReference类,在类中维护了一个stamp变量,可以理解为版本号,在cas过程中,除了比较原来的,还需要比较版本号,都一样才继续执行下面的操作。还有一个类似的类AtomicMarkablereference,同样用来一个变量,不过该变量是Boolean的,只能说能够减少ABA情况的发生,但不能杜绝.

  革命尚未成功,同志仍需努力,加油!

  

posted on 2021-04-20 15:38  Refuse_to_be_simple  阅读(65)  评论(0编辑  收藏  举报