Java线程池原理解析之ReentrantLock源码解读

本次解读将从以下四个方面进行。

一、volatile关键字;

二、CAS机制;

三、JAVA同步框架AQS;

四、ReentrantLock类可重入加锁、解锁源码解读。

一、volatile关键字

volatile关键字能保证内存可见性,即对一个volatile修饰的变量修改,将会强制性的从工作内存中刷新到主内存中,并使其他处理器缓存该变量的内存地址无效。其他线程使用该变量时,必须从主内存中加载该变量,从而保证其他线程能读取到最新值。

总之,java内存模型保证对volatile修饰变量的修改会做如下两件事:

1、当写一个volatile变量时,JMM(java共享内存模型)会把该线程对应的本地内存中的共享变量值刷新到主内存;

2、当读一个volatile变量时,JMM会把该线程对应的本地内存置为无效,线程接下来从主内存中读取共享变量。

package com.cn.thread.sync;

public class VolatileTest {
    
    public static volatile int count = 0;
    
    public static void main(String[] args) throws InterruptedException {
        
        Thread readThread = new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + " 读取 count:" + count);
        });
        readThread.setName("[readThread]");
        readThread.start();
        
        Thread writeThread = new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + " 读取 count:" + count);
            count++;//写完volatile变量后,当前线程此时并没有结束,但下面2个读线程,已经读取到最新的count值
            try {
                Thread.sleep(5000);
            } catch (Exception e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "我休息好了");
        });
        writeThread.setName("[writeThread]");
        writeThread.start();
        
        Thread readThread2 = new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + " 读取 count:" + count);
        });
        readThread2.setName("[readThread2]");
        readThread2.start();
        
        Thread readThread3 = new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + " 读取 count:" + count);
        });
        readThread3.setName("[readThread3]");
        readThread3.start();
        
        writeThread.join();
    }

}

执行结果:
[readThread] 读取 count:0
[writeThread] 读取 count:0
[readThread2] 读取 count:1
[readThread3] 读取 count:1
[writeThread]我休息好了

从代码执行结果可以看出,当写入volatile修饰的变量线程还没有执行结束时,volatile修饰的变量修改值已经刷新到主内存中,其他读线程读取的是最新值。

二、CAS机制

CAS是英文Compare and Swap的简称,含义为:比较并替换,Java虚拟机保证CAS为原子性操作。CAS对一个变量修改时,会涉及3个值,分别为:当前该变量的内存值,期望该变量的内存值,要修改的值,当且仅当,当前该变量的内存值和期望该变量的内存值相等时,才会修改该变量的值为要修改的值。这些操作是原子性操作,返回boolean值,true表示修改成功,false表示修改失败。

JAVA提供了一些原子类,下面将对三个有代表性的原子类进行分析。

package com.cn.thread.sync;

import java.util.concurrent.atomic.AtomicInteger;

/**
 * AtomicInteger类的使用
 * 
 * @author wuqiang
 *
 */
public class AtomicIntegerTest {
    
    public static AtomicInteger safeCount = new AtomicInteger(0);
    
    public static void main(String[] args) {
        
        int threadNum = 10;
        
        for(int i = 0;i < threadNum;i++){
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + ": " + AtomicIntegerTest.safeCount.getAndIncrement());
            }).start();
        }
        
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        
        System.out.println("safeCount = " + AtomicIntegerTest.safeCount);
    }

}

执行结果:
Thread-1: 0
Thread-3: 2
Thread-2: 1
Thread-0: 3
Thread-4: 4
Thread-6: 5
Thread-5: 6
Thread-8: 7
Thread-9: 8
Thread-7: 9
safeCount = 10

可以看到,返回了我们期望正确的结果。

 看AtomicInteger源码:

/**
     * Atomically increments by one the current value.
* 原子性的给当前value加1 * *
@return the previous value */ public final int getAndIncrement() { return unsafe.getAndAddInt(this, valueOffset, 1); }

该方法内部调用:
private static final Unsafe unsafe = Unsafe.getUnsafe(),unsafe对象getAndAddInt方法,该方法需要传入3个参数。
this - 当前AtomicInteger对象;
valueOffset - value在内存中的偏移量。通过this加上这个偏移量,就能找到该value的内存地址,从而能读取到该value在内存中的值;
1 - value要加的值。

 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;

openJDK中Unsafe源码:
    /**
     * Atomically adds the given value to the current value of a field
     * or array element within the given object <code>o</code>
     * at the given <code>offset</code>.
     *
     * @param o object/array to update the field/element in
     * @param offset field/element offset
     * @param delta the value to add
     * @return the previous value
     * @since 1.8
     */
    public final int getAndAddInt(Object o, long offset, int delta) {
        int v;
        do {
            v = getIntVolatile(o, offset); 
        } while (!compareAndSwapInt(o, offset, v, v + delta));
        return v;
    }
v = getIntVolatile(o, offset),该方法含义:通过当前对象和当前对象字段的偏移量,获取当前对象字段的内存值。

再看
compareAndSwapInt方法源码:

/**
     * Atomically update Java variable to <tt>x</tt> if it is currently
     * holding <tt>expected</tt>.
     * @return <tt>true</tt> if successful
     */
    public final native boolean compareAndSwapInt(Object o, long offset,
                                                  int expected,
                                                  int x);

compareAndSwapInt方法是个本地方法(底层由c++实现),其中该方法第三个expected参数为变量的期望值,第四个x参数为要修改的值,这是一个CAS方法,修改成功返回true,修改失败返回false。

再看上一个方法:
public final int getAndAddInt(Object o, long offset, int delta) {
        int v;
        do {
            v = getIntVolatile(o, offset); 
        } while (!compareAndSwapInt(o, offset, v, v + delta));
        return v;
    }
该方法通过CAS操作修改value的值,当CAS操作失败时,会不停的调用CAS直至成功,由于value变量被volatile修饰,所以有线程修改该变量值时,其他线程是可见的,很明显底层也用到了自旋。

 可以看到AtomicInteger是对单个变量进行原子性操作,那对于多个变量如何进行原子性CAS操作呢?很庆幸,JAVA提供了AtomicReference原子类,将多个变量封装成一个对象,就可以使用AtomicReference进行CAS操作了。

package com.cn.thread.sync;

import java.util.concurrent.atomic.AtomicReference;

/**
 * AtomicReference类的使用<br>
 * 多个属性的修改,可以封装成一个对象,使用AtomicReference保证对象属性更新的线程安全
 * 
 * @author wuqiang
 *
 */
public class AtomicReferenceTest {
    
    private static AtomicReference<CountDesc> reference = new AtomicReference<CountDesc>(new CountDesc(0, "计数量"));
    
    public static void main(String[] args) {
        
        CountDesc countDesc = new CountDesc(0, "计数量");
        Work work = new Work(countDesc);
        
        int threadNum = 10;
        
        for(int i = 0;i < threadNum;i++){
            new Thread(work).start();
        }
        
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        
        System.out.println();
        System.out.println("-------------------------以下使用[AtomicReference]-------------------------");
        System.out.println();
        
        SafeWork safeWork = new SafeWork();
        
        for(int i = 0;i < threadNum;i++){
            new Thread(safeWork).start();
        }
        
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        
    }
    
    static class SafeWork implements Runnable {
        
        @Override
        public void run() {
            while(true){
                CountDesc desc = reference.get();
                if(reference.compareAndSet(desc, new CountDesc(reference.get().getCount() + 1, Thread.currentThread().getName() + "更改了count的值"))){
                    break;
                }
            }
            System.out.println(Thread.currentThread().getName() + "[SafeWork]: " + reference.get().getCount() + " " + reference.get().getDesc());
        }
        
    }
    
    static class Work implements Runnable {
        
        private CountDesc countDesc;
        
        public Work(CountDesc countDesc) {
            this.countDesc = countDesc;
        }

        @Override
        public void run() {
            countDesc.setCount(countDesc.getCount() + 1);
            countDesc.setDesc(Thread.currentThread().getName() + "更改了count的值");
            System.out.println(Thread.currentThread().getName() + "[Work]: " + countDesc.getCount() + " " + countDesc.getDesc());
        }
        
    }
    
    static class CountDesc {
        
        private int count;
        
        private String desc;
        
        public CountDesc(int count, String desc) {
            this.count = count;
            this.desc = desc;
        }

        public int getCount() {
            return count;
        }
        
        public void setCount(int count) {
            this.count = count;
        }
        
        public String getDesc() {
            return desc;
        }
        
        public void setDesc(String desc) {
            this.desc = desc;
        }
    }

}

执行结果:

Thread-0[Work]: 3 Thread-2更改了count的值
Thread-2[Work]: 4 Thread-3更改了count的值
Thread-3[Work]: 4 Thread-3更改了count的值
Thread-1[Work]: 3 Thread-2更改了count的值
Thread-5[Work]: 6 Thread-5更改了count的值
Thread-6[Work]: 7 Thread-6更改了count的值
Thread-7[Work]: 8 Thread-7更改了count的值
Thread-4[Work]: 5 Thread-4更改了count的值
Thread-9[Work]: 9 Thread-9更改了count的值
Thread-8[Work]: 10 Thread-8更改了count的值


-------------------------以下使用[AtomicReference]-------------------------


Thread-10[SafeWork]: 1 Thread-10更改了count的值
Thread-11[SafeWork]: 2 Thread-11更改了count的值
Thread-12[SafeWork]: 3 Thread-12更改了count的值
Thread-14[SafeWork]: 4 Thread-14更改了count的值
Thread-13[SafeWork]: 5 Thread-13更改了count的值
Thread-15[SafeWork]: 6 Thread-15更改了count的值
Thread-16[SafeWork]: 7 Thread-16更改了count的值
Thread-17[SafeWork]: 8 Thread-17更改了count的值
Thread-18[SafeWork]: 9 Thread-18更改了count的值
Thread-19[SafeWork]: 10 Thread-19更改了count的值

很显然,第一种方式,出现竞争条件下的多线程问题,而使用AtomicReference原子类,则保证的结果的正确性。

看AtomicReference类源码

/**
     * Atomically sets the value to the given updated value
     * if the current value {@code ==} the expected value.
     * @param expect the expected value
     * @param update the new value
     * @return {@code true} if successful. False return indicates that
     * the actual value was not equal to the expected value.
     */
    public final boolean compareAndSet(V expect, V update) {
        return unsafe.compareAndSwapObject(this, valueOffset, expect, update);
    }

该方法第二个参数
valueOffset含义为:value在内存中的偏移量。通过this加上这个偏移量,就能找到该value的内存地址,从而能读取到该value在内存中的值。(和AtomicInteger类里的valueOffset具有相同的内存含义。)

private static final long valueOffset;

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

private volatile V value;

openJDK中Unsafe源码:
 /**
     * Atomically update Java variable to <tt>x</tt> if it is currently
     * holding <tt>expected</tt>.
     * @return <tt>true</tt> if successful
     */
    public final native boolean compareAndSwapObject(Object o, long offset,
                                                     Object expected,
                                                     Object x);

这也是一个native方法,内部使用CAS来更新对象,成功返回true,失败返回false。

其实原子类的实现源码,在JAVA层面上看,大同小异。

CAS相比于Synchronized,减少了线程上下文切换,在竞争条件不是很激烈的情况下,性能有了很大的提高,但竞争条件非常激烈的条件下,CAS会有如下问题:

1、CAS失败过多,导致不停的自旋,会给CPU带来非常大的执行开销;

2、ABA的问题。当前线程期望变量值为A,一个线程将变量的值改为B,另一个线程将变量又改回A,此时当前线程通过CAS操作,将变量成功的改为C。在有些情况下该问题似乎可以忽略,但是在涉及到金额等敏感情况,会有很大的问题。目前,可以使用加版本号的方式来解决,及当前线程期望变量值为A、版本号为1,一个线程将变量的值改为B、版本号加到2,另外一个线程将变量改回A、版本号加到3,此时当前线程通过CAS操作发现版本号不对,CAS操作失败,自旋。JAVA内部提供了加版本号的原子类AtomicStampedReference。

package com.cn.thread.sync;

import java.util.concurrent.atomic.AtomicStampedReference;

/**
 * AtomicStampedReference类的使用<br>
 * 通过增加版本号解决ABA问题
 * 
 * @author wuqiang
 *
 */
public class AtomicStampedReferenceTest {
    
    public final static AtomicStampedReference<Work> reference = new AtomicStampedReference<>(new Work(0, "起始数为0"), 0);
    
    public static void main(String[] args) {
        
        System.out.println("        初始值:" + reference.getReference().toString() + ",版本号:" + reference.getStamp());
        
        Work expectedReference = reference.getReference();
        int expectedStamp = reference.getStamp();
        
        Work newReference = new Work(1, "数量为1");
        int newStamp = 1;
        
        reference.compareAndSet(expectedReference, newReference, expectedStamp, newStamp);
        
        System.out.println("第一次设置:" + reference.getReference().toString() + ",版本号:" + reference.getStamp());
        
        expectedReference = reference.getReference();
        expectedStamp = 0;//版本号不对,CAS更新失败
        
        newReference = new Work(0, "数量为0");
        newStamp = 2;
        
        reference.compareAndSet(expectedReference, newReference, expectedStamp, newStamp);
        
        System.out.println("第二次设置:" + reference.getReference().toString() + ",版本号:" + reference.getStamp());
        
        expectedReference = reference.getReference();
        expectedStamp = reference.getStamp();//版本号对,CAS更新成功
        
        newReference = new Work(0, "数量为0");
        newStamp = 2;
        
        reference.compareAndSet(expectedReference, newReference, expectedStamp, newStamp);
        
        System.out.println("第三次设置:" + reference.getReference().toString() + ",版本号:" + reference.getStamp());
    }
    
    static class Work {
        
        private int num;
        
        private String desc;
        
        public Work(int num, String desc) {
            this.num = num;
            this.desc = desc;
        }
        
        public int getNum() {
            return num;
        }
        
        public void setNum(int num) {
            this.num = num;
        }
        
        public String getDesc() {
            return desc;
        }
        
        public void setDesc(String desc) {
            this.desc = desc;
        }

        @Override
        public String toString() {
            return "[num:" + this.num + ",desc:" + this.desc + "]";
        }
        
    }

}

执行结果:

初始值:[num:0,desc:起始数为0],版本号:0
第一次设置:[num:1,desc:数量为1],版本号:1
第二次设置:[num:1,desc:数量为1],版本号:1
第三次设置:[num:0,desc:数量为0],版本号:2

可以看到,第一次设置成功,第二次版本号不对、设置失败,第三次版本号正确,设置成功。

看AtomicStampedReference类源码:

/**
     * Atomically sets the value of both the reference and stamp
     * to the given update values if the
     * current reference is {@code ==} to the expected reference
     * and the current stamp is equal to the expected stamp.
     *
     * @param expectedReference the expected value of the reference
     * @param newReference the new value for the reference
     * @param expectedStamp the expected value of the stamp
     * @param newStamp the new value for the stamp
     * @return {@code true} if successful
     */
    public boolean compareAndSet(V   expectedReference,
                                 V   newReference,
                                 int expectedStamp,
                                 int newStamp) {
        Pair<V> current = pair;
        return
            expectedReference == current.reference &&
            expectedStamp == current.stamp &&
            ((newReference == current.reference &&
              newStamp == current.stamp) ||
             casPair(current, Pair.of(newReference, newStamp)));
    }
expectedReference == current.reference:期望变量的值要当前变量内存值要相同

expectedStamp == current.stamp:期望的版本号和当前内存中的版本号要相同
((newReference == current.reference && newStamp == current.stamp) || casPair(current, Pair.of(newReference, newStamp)):如果要修改的变量和要修改的版本号都是当前内存的值,那就直接返回true,没必要CAS操作,否则,进行CAS操作。

 casPair(current, Pair.of(newReference, newStamp))源码:

private boolean casPair(Pair<V> cmp, Pair<V> val) {
return UNSAFE.compareAndSwapObject(this, pairOffset, cmp, val);
}

和上面的原子类大同小异。

 三、JAVA同步框架AQS

先看ReentrantLock类AQS类图

 

 

 

posted @ 2018-03-01 18:28  wuq126  阅读(165)  评论(0编辑  收藏  举报