AbstractQueuedSynchronizer(AQS)
1.可重入锁(递归锁)
1.1 理论
概念:同一个线程在外层方法获得锁得时候,再进入该线程内层方法会自动获取锁(同一把锁);
举例:ReentrantLock,Synchronized都是可重入锁
优点:一定程度上避免死锁
可:可以
重:再次
入:进入
锁:同步锁
--一个线程中得多个流程可以获取同一把锁,持有这把同步锁可以再次进入;
自己获取自己的内部锁
-种类:
1)隐式锁(Synchronized默认可重入锁)
2)显示锁(Lock)有ReentraintLock
1.2 代码
- 隐式锁:分别用同步代码块和同步方法实验
public class ReEnterLockDemo {
static Object objectLockA = new Object();
public static void m1(){
new Thread(() -> {
synchronized (objectLockA){
System.out.println(Thread.currentThread().getName() + "\t" + "--外层调用");
synchronized (objectLockA){
System.out.println(Thread.currentThread().getName() + "\t" + "--中层调用");
synchronized (objectLockA){
System.out.println(Thread.currentThread().getName() + "\t" + "--内层调用");
}
}
}
}, "t1").start();
}
public static void main(String[] args) {
m1();
// 可重入性,不阻塞直接输出
// t1 --外层调用
// t1 --中层调用
// t1 --内层调用
}
}
public class ReEnterLockDemo2 {
public static synchronized void m1(){
System.out.println("外层");
m2();
}
public static synchronized void m2(){
System.out.println("中层");
m3();
}
public static synchronized void m3(){
System.out.println("内层");
}
public static void main(String[] args) {
m1();
//同步方法也同样没有阻塞
// 外层
// 中层
// 内层
}
2.LockSupport
- 来自java.utils.concurrent.locks.LockSupport:用于创建锁和其他同步类得基本线程阻塞原语(线程等待唤醒机制wait/notify得改良加强版park()/unpark());
2.1 wait/notify得缺陷
- 三种等待唤醒方法:
1)使用Object中的wait方法等待,使用Object中的notify方法唤醒线程
2)使用JUC包中的Condition得await方法等待,signal唤醒线程
3)LockSupport类可以阻塞当前线程以及唤醒指定被阻塞得线程;
过去得等待唤醒机制.png - 展开synchronized
public class LockSupportDemo1 {
static Object objectLock = new Object();
public static void main(String[] args) {
new Thread(() -> {
synchronized (objectLock){
System.out.println(Thread.currentThread().getName() + "\t" + "--comein");
try {
objectLock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "\t" + "被唤醒");
}
}, "A").start();
new Thread(() -> {
synchronized (objectLock){
objectLock.notify();
System.out.println(Thread.currentThread().getName() + "\t" + "--通知notify");
}
}, "B").start();
// A --comein
// B --通知notify
// A 被唤醒
}
}
- 缺点:
1)不能脱离synchronized,否则报java.lang.IllegalMonitorStateException
2)wait/notify执行顺序会影响最终输出,正常要先wait再notify; - 展开Lock
public class LockSupportDemo2 {
static Lock lock = new ReentrantLock();
static Condition condition = lock.newCondition();
public static void main(String[] args) {
new Thread(() -> {
lock.lock();
try{
System.out.println(Thread.currentThread().getName() + "\t" + "..comein");
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "\t" + "被唤醒");
}finally {
lock.unlock();
}
}, "A").start();
new Thread(() -> {
lock.lock();
try{
condition.signal();
System.out.println(Thread.currentThread().getName() + "\t" + "执行signal");
}finally {
lock.unlock();
}
}, "B").start();
// A ..comein
// B 执行signal
// A 被唤醒
}
}
- 缺点:
1)不能脱离Lock,否则报java.lang.IllegalMonitorStateException
2)await/signal执行顺序会影响最终输出,正常要先await再signal; - 总结:
1)线程先要获得锁,并且再锁块(synchronized和lock)中
2)必须要先等待后唤醒
2.2 LockSupport展开
- 理论:通过park和unpark方法执行阻塞和唤醒线程;
提供了一个Permit许可(0,1,且不许累加,最大为1,默认0) - 底层是UNSAFE类
public static void park() {
UNSAFE.park(false, 0L);
}
public static void unpark(Thread thread) {
if (thread != null)
UNSAFE.unpark(thread);
}
- demo
public class LockSupportDemo3 {
public static void main(String[] args) {
Thread a = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "\t" + "..comein");
LockSupport.park();//需要许可证
System.out.println(Thread.currentThread().getName() + "\t" + "被唤醒");
}, "A");
a.start();
Thread b = new Thread(() -> {
LockSupport.unpark(a);//对指定线程发放许可
System.out.println(Thread.currentThread().getName() + "\t" + "执行signal");
}, "B");
b.start();
// A ..comein
// B 执行signal
// A 被唤醒
优势:
1)不需要同步代码块或锁
2)unpark可以再park之前执行,同样支持总结:
1)LockSupport是一个线程阻塞工具类,所有方法都是static,可以让线程再任意位置阻塞,也有对应唤醒方法,底层调用得Unsafe中得native代码(底层原语)。
2)LockSupport和每个使用它的线程都有一个许可permit关联(相当于0,1开关,默认为0);
调用一次unpark就加1变为1;
调用一次park就消费permit,把1变为0,同时park立即返回;
再次调用park会阻塞(因为permit为0会阻塞在这里,直到permit变为1),这时调用unpark会把permit置为1;
每个线程都有一个相关得permit,permit最多只有一个,重复调用unpark也不会积累。多次调用unpark不累计permit
public static void main(String[] args) {
Thread a = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "\t" + "..comein");
LockSupport.park();//需要许可证
LockSupport.park();//需要许可证
System.out.println(Thread.currentThread().getName() + "\t" + "被唤醒");
}, "A");
a.start();
Thread b = new Thread(() -> {
LockSupport.unpark(a);//对指定线程发放许可
LockSupport.unpark(a);//对指定线程发放许可
System.out.println(Thread.currentThread().getName() + "\t" + "执行signal");
}, "B");
b.start();
// unpark只能置permit为1,更不上两个park得消耗
// A ..comein
// B 执行signal
线程阻塞需要消耗permit,而permit最多只有一个:
3 AQS
3.1 前置知识
ReentrantLock
java.util.concurrent.locks.AbstractQueuedSynchronizer:是一个抽象类、链表结构、
功能:构建锁或者其他同步器组件(抽取出公共功能组建抽象类):通过内置得FIFO队列完成资源获取线程得排队工作,并通过一个int类型变量表示持有锁得状态;
3.2 作用
- ReentrantLock
- CountDownLatch
- ReentrantReadWriteLock
- Semaphare
都有一个内部类Sync(继承自AQS)
abstract static class Sync extends AbstractQueuedSynchronizer
锁:面向锁得使用者,
同步器:面向锁的实现者
前者定义了程序和锁交互得使用层api,隐藏了实现细节,调用即可;
后者:统一的规范作用:
1)加锁会导致阻塞->排队->等待队列管理
3.3 AQS初步
- 使用一个volatile得int成员变量来表示同步状态,通过内置得FIFO队列来完成资源获取得排队工作,将每条要去抢占得线程封装为一个Node节点来实现锁得分配,通过CAS完成对State值得修改
实现类.png
image.png -
状态state,volatile保证可见性,CAS修改保证原子性
可见性.png
自旋原子性.png -
CLH双向队列
入队在尾,出队在头.png
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;
/** waitStatus value to indicate thread has cancelled */
static final int CANCELLED = 1;
/** waitStatus value to indicate successor's thread needs unparking */
static final int SIGNAL = -1;
/** waitStatus value to indicate thread is waiting on condition */
static final int CONDITION = -2;
/**
* waitStatus value to indicate the next acquireShared should
* unconditionally propagate
*/
static final int PROPAGATE = -3;
/**
* Status field, taking on only the values:
* SIGNAL: The successor of this node is (or will soon be)
* blocked (via park), so the current node must
* unpark its successor when it releases or
* cancels. To avoid races, acquire methods must
* first indicate they need a signal,
* then retry the atomic acquire, and then,
* on failure, block.
* CANCELLED: This node is cancelled due to timeout or interrupt.
* Nodes never leave this state. In particular,
* a thread with cancelled node never again blocks.
* CONDITION: This node is currently on a condition queue.
* It will not be used as a sync queue node
* until transferred, at which time the status
* will be set to 0. (Use of this value here has
* nothing to do with the other uses of the
* field, but simplifies mechanics.)
* PROPAGATE: A releaseShared should be propagated to other
* nodes. This is set (for head node only) in
* doReleaseShared to ensure propagation
* continues, even if other operations have
* since intervened.
* 0: None of the above
*
* The values are arranged numerically to simplify use.
* Non-negative values mean that a node doesn't need to
* signal. So, most code doesn't need to check for particular
* values, just for sign.
*
* The field is initialized to 0 for normal sync nodes, and
* CONDITION for condition nodes. It is modified using CAS
* (or when possible, unconditional volatile writes).
*/
volatile int waitStatus;
/**
* Link to predecessor node that current node/thread relies on
* for checking waitStatus. Assigned during enqueuing, and nulled
* out (for sake of GC) only upon dequeuing. Also, upon
* cancellation of a predecessor, we short-circuit while
* finding a non-cancelled one, which will always exist
* because the head node is never cancelled: A node becomes
* head only as a result of successful acquire. A
* cancelled thread never succeeds in acquiring, and a thread only
* cancels itself, not any other node.
*/
volatile Node prev;
/**
* Link to the successor node that the current node/thread
* unparks upon release. Assigned during enqueuing, adjusted
* when bypassing cancelled predecessors, and nulled out (for
* sake of GC) when dequeued. The enq operation does not
* assign next field of a predecessor until after attachment,
* so seeing a null next field does not necessarily mean that
* node is at end of queue. However, if a next field appears
* to be null, we can scan prev's from the tail to
* double-check. The next field of cancelled nodes is set to
* point to the node itself instead of null, to make life
* easier for isOnSyncQueue.
*/
volatile Node next;
/**
* The thread that enqueued this node. Initialized on
* construction and nulled out after use.
*/
volatile Thread thread;
/**
* Link to next node waiting on condition, or the special
* value SHARED. Because condition queues are accessed only
* when holding in exclusive mode, we just need a simple
* linked queue to hold nodes while they are waiting on
* conditions. They are then transferred to the queue to
* re-acquire. And because conditions can only be exclusive,
* we save a field by using special value to indicate shared
* mode.
*/
Node nextWaiter;
/**
* Returns true if node is waiting in shared mode.
*/
final boolean isShared() {
return nextWaiter == SHARED;
}
/**
* Returns previous node, or throws NullPointerException if null.
* Use when predecessor cannot be null. The null check could
* be elided, but is present to help the VM.
*
* @return the predecessor of this node
*/
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) { // Used by addWaiter
this.nextWaiter = mode;
this.thread = thread;
}
Node(Thread thread, int waitStatus) { // Used by Condition
this.waitStatus = waitStatus;
this.thread = thread;
}
}
Node封装了Thread对象,并有prev和next作为
AQS源码
- Lock接口得实现类,基本都是通过【聚合】一个【队列同步器】得子类完成线程访问控制得
1.用户表面使用的接口(比如lock()unlock())在内部实际调用的是Sync内部类得方法
public void unlock() {
sync.release(1);
}
底层都是用的sync类得方法:
而Sync内部类是实现得AQS:
abstract static class Sync extends AbstractQueuedSynchronizer
2.定义公平还是非公平还是用的Sync子类
public ReentrantLock() {
sync = new NonfairSync();
默认非公平
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
而公平和非公平同步器继承自Sync(AQS实现类)
是Sync得子类
static final class FairSync extends Sync
static final class NonfairSync extends Sync
二者得主要区别:
因为公平锁讲究先来先到
(ctrl+alt+b查看实现类)
区别:
公平锁:先来先得,线程在获取锁时候,如果这个锁的等待队列中已经有线程在等待,那么当前线程就会进入等待队列。
非公平锁:不管是否有等待队列,只要可以获取锁,就立刻占有锁对象银行案例
进入非公平锁的lock()
final void lock() {
if (compareAndSetState(0, 1))
一个CAS的compareAndSwapInt,确保原子性。如果确实没人占用,就设为1
setExclusiveOwnerThread(Thread.currentThread());
设置为当前线程
else
acquire(1);
如果state不为0,也就是被占用,调用acquire方法
}
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
三个方法:
- tryAcquire方法
AQS中的tryAcquire方法:
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
典型的模板方法设计模式,实现类类必须实现该方法,否则抛出异常。
--------------------------------------------------------------------------------------------
NonfairSync:实现了这个方法
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();//当前线程
int c = getState();//得到state的值
if (c == 0) {//而如果state为0,锁没有被占用
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;//当前线程占用锁
}
}
else if (current == getExclusiveOwnerThread()) {//当前线程是否是持有锁的线程
int nextc = c + acquires;//线程重入,占用了两次
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
- addWaiter方法
AQS中的第二个条件判断:
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
------------------------------------------------
AQS:
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,加到tail后,不需要进enq方法
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);//放入cls队列,tail后;空队列会创建傀儡节点
return node;
}
//入队功能,头节点是一个傀儡节点
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))//新建一个node作为头节点,不是B线程节点
tail = head;//一个null的空节点,并把头尾指针指向它
} else {//当前节点插入到哨兵节点或tail节点后,并更新tail节点指向
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
- 第三个方法:acquireQueued
AQS:
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();//node的前一个节点
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&//如果前驱节点的waitstatus是-1(SIGNAL)就返回true
parkAndCheckInterrupt())//LockSupport.park(this),线程被阻塞,真正入队
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
在这一步才真正park阻塞
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
- 下面看unlock方法
public void unlock() {
sync.release(1);
}
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;//头指针,傀儡节点
if (h != null && h.waitStatus != 0)//也就是判断队列中是否存在阻塞的候选节点
unparkSuccessor(h);//唤醒head.next一个阻塞节点,并且头节点waitStatus设回0,B继续执行
return true;
}
return false;
}
protected final boolean tryRelease(int releases) {
int c = getState() - releases;//state - 1
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);//且设置当前持有锁为null
}
setState(c);//重新设置state
return free;
}
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {//此时tryAcquire成立了,占用锁
setHead(node);//设置node为头节点,并把node的Thread设置为null,也就是说当前出队节点变成新的头傀儡节点
p.next = null; // help GC,帮助头节点出队
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())//unpark后可以继续执行下去,进入下一个内循环
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}