一、JMM模型与volatile详解
二、synchronized原理详解
三、AQS框架详解——AbstractQueuedSynchronizer
四、ArrayBlockingQueue、LinkedBlockingQueue、PriorityBlockingQueue和DelayQueue学习总结
五、CountDownLatch、CyclicBarrier和Semaphore的比较
java中interruptor理解与使用
Java线程的6种状态及切换
MESI协议:保证可见性,无法保证原子性
AQS定义了一套多线程访问共享资源的同步框架,一个依赖状态(state)的同步器。
ReentrantLock
lock.lock()
//业务逻辑
lock.unLock()
下面是模拟三个线程执行上面代码的简单流程图:
从上面lock的用法中,我们总结出三个核心问题:
(1)自旋;
(2)阻塞线程,并放入队列中;同步等待队列(CLH队列);
(3)如何实现加锁;CAS类实现加锁,底层是汇编指令CMPXCHG
ReentranLock是基于AQS框架实现的,支持手动加锁与解锁。支持锁的公平与非公平。
static ReentrantLock lock = new ReentrantLock(false);
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
ReentrantLock通过两个继承Sync的内部类实现,FairSync,NonfairSync来公平锁和非公平锁。
static final class FairSync extends Sync
static final class NonfairSync extends Sync
abstract static class Sync extends AbstractQueuedSynchronizer
而Sync类就是继承自AbstractQueuedSynchronizer。
FairSync.lock()源码分析
tryAcquire()方法—锁竞争逻辑
protected final boolean tryAcquire(int acquires) {
//获取当前线程的引用
final Thread current = Thread.currentThread();
//获取当前线程的状态量
int c = getState();
//如果状态量等于0,表示是可以加锁的状态;
if (c == 0) {
//hasQueuedPredecessors():判断是否需要排队;
//compareAndSetState(0, acquires):通过CAS的方式修改状态量,加锁
//setExclusiveOwnerThread(current):把OwnerThread标记为当前线程;
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
//如果状态量不为0,并且OwnerThread就是当前线程,重入逻辑;否则,加锁失败
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
compareAndSetState()
通过unsafe类,利用CAS操作,完成加锁操作;
底层依赖汇编指令CMPXCHG
protected final boolean compareAndSetState(int expect, int update) {
// See below for intrinsics setup to support this
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
addwaiter(Node.EXCLUSIVE) —线程入队,Node节点,Node对Thread引用
node节点示意图
// mode是Node.EXCLUSIVE;初始化一个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;
}
}
//队列为空,初始化一个队列,并且放入队列中;
//利用for循环保证入队成功
enq(node);
return node;
}
acquireQueued
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
//中断标志开始为false;
boolean interrupted = false;
//第一次循环,修改head的状态,修改成signal=-1标记为可以唤醒;
//第二轮循环,阻塞线程,并设置中断为true;
for (;;) {
//如果一开始是第一个节点,就再尝试一次获取锁
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
//中断标志为false;
return interrupted;
}
//如果不是第一个节点,则需要阻塞并且中断标志设置为true;
//shouldParkAfterFailedAcquire,判断是否需要阻塞线程
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
唤醒方式:unlock唤醒队列头部信息;
中断方式也可以唤醒:thread.interrupt()
场景:
Interrupt存在的意义,终止线程,避免影响重要的业务;
提供一个终止线程的方法,优雅的终结方式;
unlock()源码分析
release(int arg)
//释放锁的方法
public final boolean release(int arg) {
//尝试释放锁,释放成功的话,判断waitStatus!=0,则调用unpark方法,唤醒线程
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
//该方法会判断线程状态是不是!=0;
unparkSuccessor(h);
return true;
}
return false;
}
tryRelease(int releases):释放锁的逻辑
//释放锁的逻辑
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
AQS具备的特性
1、阻塞等待队列
2、共享、独占
3、公平、非公平
4、可重入
5、允许中断
AQS的重要属性state,同步状态量
state=0,表示锁还没有被占有;state不等于0时,thread表示的持有锁的线程,重入一次,state自增1。
/**
* The synchronization state.
*/
private volatile int state;
CAS的方式改变状态量的值:
protected final boolean compareAndSetState(int expect, int update) {
// See below for intrinsics setup to support this
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
独占/共享
static final class Node {
/** Marker to indicate a node is waiting in shared mode *
static final Node SHARED = new Node();
/** Marker to indicate a node is waiting in exclusive mode */
static final Node EXCLUSIVE = null;
EXCLUSIVE:独占,只有一个线程能执行,如ReentrantLock;
SHARED:共享,多个线程可以同时执行,比如Semaphore/CountDownLatch。
同步等待队列和条件等待队列
AQS中同步等待队列也叫CLH队列,是一种双向链表的结构,先进先出的线程等待队列。
condition是多线程间协调通信的工具类,线程只有当条件具备时,才会被唤醒,从而重新进行锁的争夺。
AQS源码分析
不管是CLH队列还是条件队列都是基于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;
/** 节点在等待队列中,节点的线程等待在condition上,当其他线程对condition调用了sinal()方法后,该节点会从等待队列转移到CLH队列中,加入同步状态的获取中*/
static final int CONDITION = -2;
/**表示下一次共享式同步状态获取将会被无条件地传播下去
*/
static final int PROPAGATE = -3;
节点加入同步队列
/*节点加入同步队列*/
private Node enq(final Node node) {
for (;;) { //自旋等待
Node t = tail;
if (t == null) { // 队列需要初始化,创建空的头节点
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
尝试获取共享锁
private void doAcquireShared(int arg) {
//入队
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
//前驱节点
final Node p = node.predecessor();
if (p == head) {
//头节点再次尝试获取锁,提高效率
int r = tryAcquireShared(arg);
//state等于0说明共享次数达到了,可以获取锁了
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
if (interrupted)
selfInterrupt();
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
头节点在节点阻塞之前再尝试一次获取锁:成功,出队,执行,并且head后移;阻塞:(1)首先第一轮循环修改head的状态signal为可以被唤醒;(2)第二轮循环,返回true,阻塞线程并且需要判断线程是否由中断信号唤醒的;(3)