2.常用Java并发工具的原理
常用java并发工具
volatile
synchronized
AQS
原子操作类
线程池
2.1volatile变量的作用及实现原理
2.1.1可见性问题
当cpu1改变A的值后,cpu2读取A的值到B,但此时可能cache1的缓存还未同步到主内存,导致可见性问题。
2.1.2volatile解决可见性问题
volatile
volatile private boolean opened;
增加volatile的变量引起CPU层面的变化:增加lock前缀指令
汇编
0x0000000002931351: lock add dword ptr [rsp],0h
基本描述:volatile,保证共享变量多线程可见性,轻量级的synchronized,不引起线程上下文切换
基本保证:
1)当前CPU缓存行的数据写回到系统内存
2)写回操作使其他CPU里缓存了该内存地址的数据无效
CPU层面变化:
lock在CPU层面使用锁总线或锁缓存(缓存一致性协议)来实现,锁总线开销大,近期的CPU一般使用锁缓存的机制
锁总线:
锁总线,其它CPU对内存的读写请求都会被阻塞,直到锁释放
锁缓存:
缓存一致性协议。最出名的就是Intel 的MESI协议,MESI协议保证了每个缓存中使用的共享变量的副本是一致的。它核心的思想是:当CPU写数据时,如果发现操作的变量是共享变量,即在其他CPU中也存在该变量的副本,会发出信号通知其他CPU将该变量的缓存行置为无效状态,因此当其他CPU需要读取这个变量时,发现自己缓存中缓存该变量的缓存行是无效的,那么它就会从内存重新读取。
参考:[https://www.cnblogs.com/xrq730/p/7048693.html]
2.2synchronized作用和实现原理
synchronized
//静态方法同步
private static synchronized void staticSyncFunc(){
//do something
}
//普通方法同步
private synchronized void syncFunc(Object obj){
//同步对象
synchronized(obj){
//do something
}
}
java锁的是对象,对象内存结构:
对象结构长度:
锁的表现形式 | 锁的具体对象 |
普通方法 | 当前实例对象 |
静态方法 | 当前class对象 |
同步方法块 | Synchronized括号里配置的对象 |
锁升级流程
锁升级流程:
重量级锁:
Java6后的优化手段
偏向锁:
偏向锁的核心思想是,如果一个线程获得了锁,那么锁就进入偏向模式,此时Mark Word 的结构也变为偏向锁结构,当这个线程再次请求锁时,无需再做任何同步操作,即获取锁的过程,这样就省去了大量有关锁申请的操作,从而也就提供程序的性能。
记录线程id,若id一致,则直接获取锁,否则膨胀。
轻量级锁:
CAS更新指向栈中锁记录的指针,若成功,则获取锁,否则膨胀。
偏向锁与轻量级锁均为乐观锁。
重量级锁为悲观锁。
参考[Java并发编程的艺术.第2章]
参考[http://www.cnblogs.com/dennyzhangdd/p/6734638.html]对java并发编程的艺术上的内容复述及总结
参考[https://blog.csdn.net/boling_cavalry/article/details/77995069]
参考[http://www.hollischuang.com/archives/2030]monitor原理及源码分析
参考[https://www.jianshu.com/p/c5058b6fe8e5]hotspot c++源码分析
参考[https://www.jianshu.com/p/e53e7964db03]hotspot编译方法
2.3原子操作作用和原理
原子操作:不可被中断的一个或一系列操作
Atomic
public class AtomicIntegerTest {
static AtomicInteger ai = new AtomicInteger(1);
public static void main(String[] args) {
System.out.println(ai.getAndIncrement());
System.out.println(ai.get());
}
}
public class AtomicInteger extends Number implements java.io.Serializable {
public final int getAndIncrement() {
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return current;
}
}
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
}
Unsafe
// setup to use Unsafe.compareAndSwapInt for updates
private static final Unsafe unsafe = Unsafe.getUnsafe();
查看源码发现Atomic包里的类基本都是使用Unsafe实现的,Unsafe只提供了以下三种CAS方法。
CAS
public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);
2.4经典等待通知机制
等待通知示例
public class WaitNotify {
static boolean flag = true;
static Object lock = new Object();
public static void main(String[] args) throws Exception {
Thread waitThread = new Thread(new Wait(), "WaitThread");
waitThread.start();
TimeUnit.SECONDS.sleep(1);
Thread notifyThread = new Thread(new Notify(), "NotifyThread");
notifyThread.start();
}
static class Wait implements Runnable {
public void run() {
// 加锁,拥有lock的Monitor
synchronized (lock) {
// 当条件不满足时,继续wait,同时释放了lock的锁
while (flag) {
try {
System.out.println(Thread.currentThread() + " flag is true. wait @ "
+ new SimpleDateFormat("HH:mm:ss").format(new Date()));
lock.wait();
} catch (InterruptedException e) {
}
}
// 条件满足时,完成工作
System.out.println(Thread.currentThread() + " flag is false. running @ "
+ new SimpleDateFormat("HH:mm:ss").format(new Date()));
}
}
}
static class Notify implements Runnable {
public void run() {
// 加锁,拥有lock的Monitor
synchronized (lock) {
// 获取lock的锁,然后进行通知,通知时不会释放lock的锁,
// 直到当前线程释放了lock后,WaitThread才能从wait方法中返回
System.out.println(Thread.currentThread() + " hold lock. notify @ " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
lock.notifyAll();
flag = false;
SleepUtils.second(5);
}
// 再次加锁
synchronized (lock) {
System.out.println(Thread.currentThread() + " hold lock again. sleep @ "
+ new SimpleDateFormat("HH:mm:ss").format(new Date()));
SleepUtils.second(5);
}
}
}
}
等待通知流程:
2.5AQS队列同步器作用及源码分析
synchronized实现基本锁功能,AQS同步器提供更多的多线程工具,提供更多的使用场景
AQS描述
Java
AQS为依赖于先进先出 (FIFO) 等待队列的阻塞锁和相关同步器(信号量、事件,等等)实现提供框架。
它使用了一个原子的int value status来作为同步器的状态值(如:独占锁。1代表已占有,0代表未占有),
通过该类提供的原子修改status方法(getState, setState and compareAndSetState),我们可以把它作为同步器的基础框架类来实现各种同步器。
AQS还定义了一个实现了Condition接口的ConditionObject内部类。
Condition 将 Object 监视器方法(wait、notify 和 notifyAll)分解成截然不同的对象,
以便通过将这些对象与任意 Lock 实现组合使用,为每个对象提供多个等待 set (wait-set)。
其中,Lock 替代了 synchronized 方法和语句的使用,Condition 替代了 Object 监视器方法的使用。
简单来说,就是Condition提供类似于Object的wait、notify的功能signal和await,使一个正在执行的线程挂起(推迟执行),直到被其他线程唤醒。
但是Condition更加强大,如支持多个条件谓词、保证线程唤醒的顺序和在挂起时不需要拥有锁。
使用AQS同步器的锁
CountDownLatch
Mutex
OneShotLatch
ReentrantLock
ReentrantReadWriteLock
Semaphore
TwinsLock
AQS主要成员变量:
private transient volatile Node head;//等待队列头部
private transient volatile Node tail;//等待队列尾部
private volatile int state;//AQS状态位,通过try*方法维护
AQS内部类Node:
static final class Node {
static final Node SHARED = new Node();//标识等待节点处于共享模式
static final Node EXCLUSIVE = null;//标识等待节点处于独占模式
static final int CANCELLED = 1;//由于超时或中断,节点已被取消
static final int SIGNAL = -1;//表示下一个节点是通过park堵塞的,需要通过unpark唤醒
static final int CONDITION = -2;//表示线程在等待条件变量(先获取锁,加入到条件等待队列,然后释放锁,等待条件变量满足条件;只有重新获取锁之后才能返回)
static final int PROPAGATE = -3;//表示后续结点会传播唤醒的操作,共享模式下起作用
//等待状态:对于condition节点,初始化为CONDITION;其它情况,默认为0,通过CAS操作原子更新
volatile int waitStatus;
//前节点
volatile Node prev;
//后节点
volatile Node next;
//线程对象
volatile Thread thread;
//对于Condtion表示下一个等待条件变量的节点;其它情况下用于区分共享模式和独占模式;
Node nextWaiter;
final boolean isShared() {
return nextWaiter == SHARED;//判断是否共享模式
}
//获取前节点,如果为null,抛出异常
final Node predecessor() throws NullPointerException {
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
}
Node() { // Used to establish initial head or SHARED marker
}
Node(Thread thread, Node mode) { //addWaiter方法使用
this.nextWaiter = mode;
this.thread = thread;
}
Node(Thread thread, int waitStatus) { //Condition使用
this.waitStatus = waitStatus;
this.thread = thread;
}
}
AQS主要函数:
Mutex lock 调用AQS Acquire:
public void lock() {
sync.acquire(1);
}
Acquire完成:
1.锁尝试获取(tryAcquire),
2’.获取到锁,则结束
2.若获取不到,则通过addWaiter方法构造节点并放入队列,
3.再通过acquireQueued的方法自旋阻塞获取锁
Mutex tryAcquire
// 当状态为0的时候获取锁
public boolean tryAcquire(int acquires) {
assert acquires == 1; // Otherwise unused
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
Acquire
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
AddWaiter
/**
* Creates and enqueues node for current thread and given mode.
*
* @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared
* @return the new node
*/
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
enq
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
AddWaiter流程解析:
acquireQueued
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;//未发生中断
//仍然通过自旋,根据前面的逻辑,此处传入的为新入列的节点
for (;;) {
final Node p = node.predecessor();//获取前节点,即prev指向节点
//如果node的前一节点为head节点,而head节点为空节点,说明node是等待队列里排在最前面的节点
if (p == head && tryAcquire(arg)) {
//获取资源成功,将node设置为头节点,setHead清空节点属性thread,prev
setHead(node);
p.next = null; // 将原头节点的next设为null,帮助GC
failed = false;
return interrupted;//返回是否发生中断
}
//如果acquire失败,是否要park,如果是则调用LockSupport.park
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;//发生中断
}
} finally {
if (failed)//只有循环中出现异常,才会进入该逻辑
cancelAcquire(node);
}
}
shouldParkAfterFailedAcquire
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
//如果acquireQueued第一次调用该方法,ws==0
int ws = pred.waitStatus;
//已经设置了状态,由于SIGNAL表示要通过unpark唤醒后一节点,因此当获取失败时,是要调用park堵塞的,返回true
if (ws == Node.SIGNAL)
return true;
//如果前一节点已取消,则往前找,直到找到一个状态正常的节点,其实就是从队列删除取消状态的节点
if (ws > 0) {
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;//更新next指针,去掉中间取消状态的节点
} else {//更新pred节点的waitStatus为SIGNAL
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;//返回false,表示不需要调用park
}
parkAndCheckInterrupt
private final boolean parkAndCheckInterrupt() {
//将当前线程的parkBlocker变量指向this,调用unsafe.park堵塞当前线程
//简单来说park是申请许可,如果存在许可,马上返回,否则一直等待获得许可;unpark是将许可数量设为1,会唤醒park返回;
//LockSupport提供了unpark(Thread thread)方法,可以为指定线程颁发许可
LockSupport.park(this);
return Thread.interrupted();//注意:该方法会清除线程的中断状态
}
release
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);//unpark唤醒第一个等待节点
return true;
}
return false;
}
参考[https://www.jianshu.com/p/c244abd588a8]AQS源码分析