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类图