多线程系列四:AQS-AbstractQueuedSynchronizer
什么是AbstractQueuedSynchronizer?为什么我们要分析它?
AbstractQueuedSynchronizer(AQS):抽象队列同步器
抽象队列同步器原理:当多个线程去获取锁的时候,如果获取锁失败了,获取锁失败的当前线程就会被打包成一个node节点放入同步队列里面使用LockSuport的park方法阻塞起来,如果有线程释放了锁,放入同步队列的线程就会被LockSupport的unpark方法唤醒再次去获取锁,如果获取锁又失败了就再次打包成node节点放入同步队列,这样不断的循环直到拿到锁
为什么要分析AQS:因为线程都是基于AQS实现的
AQS的基本使用方法
抽象队列同步器的主要使用方式是继承,子类通过继承抽象队列同步器并实现它的抽象方法来管理线程状态。可重写的方法有:
tryAcquire 独占锁获取
tryRelease 独占锁释放
tryAcquireShared 共享锁获取
tryReleaseShared 共享锁释放
isHeldExclusively 快速判断锁是不是被线程独占
抽象队列同步器的设计是基于模板方法模式, 使用者需要继承抽象队列同步器并重写指定的方法,随后将抽象队列同步器组合在自定义同步组件的实现中,并调用抽象队列同步器提供的模板方法,而这些模板方法将会调用使用者重写的方法。
模板方法设计模式举例:
对同步状态进行更改,这时就需要使用同步器提供的3个方法
getState() 获取同步状态
setState 设置同步状态
compareAndSetState 原子的设置同步状态来进行操作,是线程安全的
用AQS实现自己的一个独占锁
独占锁代码:
1 package com.lgs.aqs; 2 3 import java.util.concurrent.TimeUnit; 4 import java.util.concurrent.locks.AbstractQueuedSynchronizer; 5 import java.util.concurrent.locks.Condition; 6 import java.util.concurrent.locks.Lock; 7 8 // 用AQS实现自己的一个独占锁 9 public class SingleLock implements Lock { 10 11 // 在内部类里面实现AQS 12 static class Sync extends AbstractQueuedSynchronizer{ 13 14 // 独占锁获取 15 public boolean tryAcquire(int arg) { 16 // 期望没有线程拿到独占锁,把当前线程状态由0改到1拿到独占锁 17 // 获取独占锁时存在多线程获取的情况 存在线程安全问题 所以用compareAndSetState 18 if (compareAndSetState(0, 1)) { 19 // 如果改成功就把当前线程设置进去 20 // setExclusiveOwnerThread的意思是如果是独占锁就把当自己的线程传进去 21 // 表示由自己的线程拿到独占的锁 22 setExclusiveOwnerThread(Thread.currentThread()); 23 return true; 24 } 25 return false; 26 } 27 28 // 独占锁释放 29 public boolean tryRelease(int arg) { 30 // 置为null 表示当前线程已经不是占据锁的线程 31 setExclusiveOwnerThread(null); 32 // 修改独占锁状态0,表示独占删不被当前线程占用了 33 // 释放时不存在多线程释放 不存在线程安全问题 所以用setState 34 setState(0); 35 return false; 36 37 } 38 39 // 快速判断独占锁是不是被线程独占 40 public boolean isHeldExclusively() { 41 // 如果等于1就表示被线程独占 42 return getState() == 1; 43 } 44 45 // 46 Condition newCondition() { 47 return new ConditionObject(); 48 } 49 } 50 51 private final Sync sync = new Sync(); 52 53 @Override 54 public void lock() { 55 // 获取锁 1表示当前线程获取的是独占锁 56 sync.acquire(1); 57 } 58 59 @Override 60 public void lockInterruptibly() throws InterruptedException { 61 // 可中断获取锁 62 sync.acquireInterruptibly(1); 63 64 } 65 66 @Override 67 public Condition newCondition() { 68 return sync.newCondition(); 69 } 70 71 @Override 72 public boolean tryLock() { 73 // 尝试获取锁 74 return sync.tryAcquire(1); 75 } 76 77 @Override 78 public boolean tryLock(long time, TimeUnit unit) throws InterruptedException { 79 // 超时获取锁 80 return sync.tryAcquireNanos(1, unit.toNanos(time)); 81 } 82 83 @Override 84 public void unlock() { 85 // 释放锁 86 sync.release(1); 87 88 } 89 90 }
测试自己的独占锁:
1 package com.lgs.aqs; 2 3 import java.util.concurrent.locks.Lock; 4 5 // 测试自己写的锁 6 public class TestMyLock { 7 8 public void test() { 9 final Lock lock = new SingleLock(); 10 class Worker extends Thread { 11 public void run() { 12 while (true) { 13 lock.lock(); 14 try { 15 Thread.sleep(1000); 16 System.out.println(Thread.currentThread().getName()); 17 Thread.sleep(1000); 18 } catch (InterruptedException e) { 19 e.printStackTrace(); 20 } finally { 21 lock.unlock(); 22 } 23 try { 24 Thread.sleep(2000); 25 } catch (InterruptedException e) { 26 e.printStackTrace(); 27 } 28 } 29 } 30 } 31 // 启动10个子线程 32 for (int i = 0; i < 10; i++) { 33 Worker w = new Worker(); 34 // w.setDaemon(true); 35 w.start(); 36 } 37 // 主线程每隔1秒换行 38 for (int i = 0; i < 10; i++) { 39 try { 40 Thread.sleep(1000); 41 } catch (InterruptedException e) { 42 e.printStackTrace(); 43 } 44 System.out.println(); 45 } 46 } 47 48 public static void main(String[] args) { 49 TestMyLock testMyLock = new TestMyLock(); 50 testMyLock.test(); 51 } 52 }
什么是LockSupport
LockSupport定义了一组的公共静态方法,这些方法提供了最基本的线程阻塞和唤醒功能,而LockSupport也成为构建同步组件的基础工具。
LockSupport定义了一组以park开头的方法用来阻塞当前线程,以及unpark(Thread thread)方法来唤醒一个被阻塞的线程。
简单地说:LockSupport就是用来和AQS配合阻塞和唤醒线程的
同步队列
抽象队列同步器依赖内部的同步队列(一个FIFO双向队列)来完成同步状态的管理
当前线程获取锁失败时,抽象队列同步器会将当前线程以及等待状态等信息构造成为一个节点(Node)并将其加入同步队列。同步器拥有首节点(head)和尾节点(tail),没有成功获取同步状态的线程将会成为节点加入该队列的尾部。
同步队列的结构:
节点加入到同步队列:
首节点的设置:
独占式同步状态获取与释放
同步状态获取:
通过调用抽象队列同步器的acquire(int arg)方法可以获取同步状态,
java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(int)
其主要逻辑是:
首先调用自定义抽象队列同步器实现的tryAcquire(int arg)方法,该方法保证线程安全的获取同步状态,
如果同步状态获取失败,则构造同步节点(独占式Node.EXCLUSIVE:同一时刻只能有一个线程成功获取同步状态)并通过addWaiter(Node node)方法将该节点加入到同步队列的尾部,
java.util.concurrent.locks.AbstractQueuedSynchronizer.addWaiter(Node)
最后调用acquireQueued(Node node,int arg)方法,使得该节点以“死循环”的方式获取同步状态。
java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireQueued(Node, int)
如果获取不到则阻塞节点中的线程,
java.util.concurrent.locks.AbstractQueuedSynchronizer.shouldParkAfterFailedAcquire(Node, Node) 获取不成功就阻塞当前线程node
而被阻塞线程的唤醒主要依靠前驱节点的出队或阻塞线程被中断来实现。
同步状态释放:
java.util.concurrent.locks.AbstractQueuedSynchronizer.release(int)
调用自定义同步器实现的tryRelease(int arg)方法
如果能释放就唤醒继任者:
java.util.concurrent.locks.AbstractQueuedSynchronizer.unparkSuccessor(Node)
共享式同步状态获取与释放
同步状态获取:
共享式获取与独占式获取最主要的区别在于同一时刻能否有多个线程同时获取到同步状态。
在acquireShared(int arg)方法中,同步器调用tryAcquireShared(int arg)方法尝试获取同步状态,tryAcquireShared(int arg)方法返回值为int类型,当返回值大于等于0时,表示能够获取到同步状态。因此,在共享式获取的自旋过程中,成功获取到同步状态并退出自旋的条件就是tryAcquireShared(int arg)方法返回值大于等于0。
java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireShared(int)
获取失败:
java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireShared(int)
获取失败打包成节点阻塞:
java.util.concurrent.locks.AbstractQueuedSynchronizer.shouldParkAfterFailedAcquire(Node, Node)
同步状态释放:
java.util.concurrent.locks.AbstractQueuedSynchronizer.releaseShared(int)
java.util.concurrent.locks.AbstractQueuedSynchronizer.doReleaseShared()
独占式超时同步状态获取
调用同步器的doAcquireNanos(int arg,long nanosTimeout)方法可以超时获取同步状态,即在指定的时间段内获取同步状态,如果获取到同步状态则回true,否则,返回false。
用AQS实现自己的一个共享锁
1 package com.lgs.aqs; 2 3 import java.util.concurrent.TimeUnit; 4 import java.util.concurrent.locks.AbstractQueuedSynchronizer; 5 import java.util.concurrent.locks.Condition; 6 import java.util.concurrent.locks.Lock; 7 8 //用AQS实现自己的一个共享锁 这个共享锁支持两个线程同时获取锁 9 public class ShareLock implements Lock { 10 11 // 共享锁支持两个线程同时获取锁 12 private final Sync sync = new Sync(2); 13 14 // 在内部类里面实现AQS 15 private static final class Sync extends AbstractQueuedSynchronizer { 16 //private static final long serialVersionUID = -7889272986162341211L; 17 18 // 设置共享锁同时支持几个线程获取锁 这里设置为2 19 Sync(int count) { 20 if (count <= 0) { 21 throw new IllegalArgumentException("count must large than zero."); 22 } 23 setState(count); 24 } 25 26 // 获取共享锁 每获取一个锁state减reduceCount 27 public int tryAcquireShared(int reduceCount) { 28 for (;;) { 29 int current = getState(); 30 int newCount = current - reduceCount; 31 if (newCount < 0 || compareAndSetState(current, newCount)) { 32 return newCount; 33 } 34 } 35 } 36 37 // 释放共享锁 每释放一个锁state+returnCount 38 public boolean tryReleaseShared(int returnCount) { 39 for (;;) { 40 int current = getState(); 41 int newCount = current + returnCount; 42 if (compareAndSetState(current, newCount)) { 43 return true; 44 } 45 } 46 } 47 48 final ConditionObject newCondition() { 49 return new ConditionObject(); 50 } 51 } 52 53 public void lock() { 54 sync.acquireShared(1); 55 } 56 57 public void unlock() { 58 sync.releaseShared(1); 59 } 60 61 public void lockInterruptibly() throws InterruptedException { 62 sync.acquireSharedInterruptibly(1); 63 } 64 65 public boolean tryLock() { 66 return sync.tryAcquireShared(1) >= 0; 67 } 68 69 public boolean tryLock(long time, TimeUnit unit) throws InterruptedException { 70 return sync.tryAcquireSharedNanos(1, unit.toNanos(time)); 71 } 72 73 @Override 74 public Condition newCondition() { 75 return sync.newCondition(); 76 } 77 }
测试自己写的锁:
1 package com.lgs.aqs; 2 3 import java.util.concurrent.locks.Lock; 4 5 // 测试自己写的锁 6 public class TestMyLock { 7 8 public void test() { 9 // 测试独占锁 10 // final Lock lock = new SingleLock(); 11 12 // 测试共享锁 13 final Lock lock = new ShareLock(); 14 class Worker extends Thread { 15 public void run() { 16 while (true) { 17 lock.lock(); 18 try { 19 Thread.sleep(1000); 20 System.out.println(Thread.currentThread().getName()); 21 Thread.sleep(1000); 22 } catch (InterruptedException e) { 23 e.printStackTrace(); 24 } finally { 25 lock.unlock(); 26 } 27 try { 28 Thread.sleep(2000); 29 } catch (InterruptedException e) { 30 e.printStackTrace(); 31 } 32 } 33 } 34 } 35 // 启动10个子线程 36 for (int i = 0; i < 10; i++) { 37 Worker w = new Worker(); 38 // w.setDaemon(true); 39 w.start(); 40 } 41 // 主线程每隔1秒换行 42 for (int i = 0; i < 10; i++) { 43 try { 44 Thread.sleep(1000); 45 } catch (InterruptedException e) { 46 e.printStackTrace(); 47 } 48 System.out.println(); 49 } 50 } 51 52 public static void main(String[] args) { 53 TestMyLock testMyLock = new TestMyLock(); 54 testMyLock.test(); 55 } 56 }
锁的可重入
什么叫锁的可重入:当一个线程获得锁之后,再次进入同步快任然能获取到锁执行。
例如递归调用同步方法就会发生锁的可重入:
1 public synchronized void add(int i) { 2 int sum = i + 1; 3 // 递归调用 4 add(sum); 5 }
锁的可冲入实现原理:先判断当前线程是不是已经获取到锁的线程,如果是就把状态加1;释放锁的时候拿了几次锁就要释放几次
可重入锁获取锁实现源码:
java.util.concurrent.locks.ReentrantLock.Sync.nonfairTryAcquire(int)
/** * Performs non-fair tryLock. tryAcquire is implemented in * subclasses, but both need nonfair try for trylock method. */ final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { if (compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } // 先判断当前线程是不是已经获取锁的线程,如果是就把状态加1 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; }
可重入锁释放锁源码实现:
java.util.concurrent.locks.ReentrantLock.Sync.tryRelease(int)
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; }
没有实现锁的可重入会怎么样呢:如果没有实现锁的可重入则会出现一个线程拿到了锁之后再次获取锁,这样就会自己等待自己出现死锁
公平锁和非公平锁
公平锁:一个线程去竞争锁的时候先看前面有没有其他线程在等待获取锁,如果有就不参与竞争锁
公平锁获取锁的源码实现:
java.util.concurrent.locks.ReentrantLock.FairSync.tryAcquire(int)
protected final boolean tryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { // hasQueuedPredecessors先判断前面有没有其他线程在等待获取锁,如果有就不参与竞争锁 if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; }
非公平锁:一个线程去获取锁的时候不管前面有没有线程在等待获取锁都有参与竞争
非公平锁获取锁源码实现:
java.util.concurrent.locks.ReentrantLock.Sync.nonfairTryAcquire(int)
/** * Performs non-fair tryLock. tryAcquire is implemented in * subclasses, but both need nonfair try for trylock method. */ final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 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; }
了解可重入锁ReentrantLock的实现
可重入是指任意线程在获取到锁之后能够再次获取该锁而不会被锁所阻塞,该特性的实现需要解决以下两个问题:
1)线程再次获取锁。锁需要去识别获取锁的线程是否为当前占据锁的线程,如果是,则再次成功获取。
可重入锁获取锁实现源码:
java.util.concurrent.locks.ReentrantLock.Sync.nonfairTryAcquire(int)
/** * Performs non-fair tryLock. tryAcquire is implemented in * subclasses, but both need nonfair try for trylock method. */ final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { if (compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } // 先判断当前线程是不是已经获取锁的线程,如果是就把状态加1再次获取锁 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; }
2)锁的最终释放。
nonfairTryAcquire方法增加了再次获取同步状态的处理逻辑:通过判断当前线程是否为获取锁的线程来决定获取操作是否成功,如果是获取锁的线程再次请求,则将同步状态值进行增加并返回true,表示获取同步状态成功。同步状态表示锁被一个线程重复获取的次数。
可重入锁获取锁实现源码:
java.util.concurrent.locks.ReentrantLock.Sync.nonfairTryAcquire(int)
/** * Performs non-fair tryLock. tryAcquire is implemented in * subclasses, but both need nonfair try for trylock method. */ final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { if (compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } // 先判断当前线程是不是已经获取锁的线程,如果是就把状态加1再次获取锁 else if (current == getExclusiveOwnerThread()) {
// 如果是获取锁的线程再次请求,则将同步状态值进行增加并返回true int nextc = c + acquires; if (nextc < 0) // overflow throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; }
如果该锁被获取了n次,那么前(n-1)次tryRelease(int releases)方法必须返回false,而只有同步状态完全释放了,才能返回true。可以看到,该方法将同步状态是否为0作为最终释放的条件,当同步状态为0时,将占有线程设置为null,并返回true,表示释放成功。
可重入锁释放锁源码实现:
java.util.concurrent.locks.ReentrantLock.Sync.tryRelease(int)
protected final boolean tryRelease(int releases) { // 释放锁的时候拿了几次就释放几次 int c = getState() - releases; if (Thread.currentThread() != getExclusiveOwnerThread()) throw new IllegalMonitorStateException();
// 如果该锁被获取了n次,那么前(n-1)次tryRelease(int releases)方法必须返回false boolean free = false;
// 当同步状态为0时,将占有线程设置为null,并返回true if (c == 0) { free = true; setExclusiveOwnerThread(null); } setState(c); return free; }
了解可重入读写锁ReentrantReadWriteLock的实现原理
可重入读写锁的自定义同步器需要在同步状态(一个整型变量)上维护多个读线程和一个写线程的状态,使得该状态的设计成为读写锁实现的关键。
如果在一个整型变量上维护多种状态,就一定需要“按位切割使用”这个变量,读写锁将变量切分成了两个部分,
高16位表示读:
java.util.concurrent.locks.ReentrantReadWriteLock.Sync.SHARED_UNIT
低16位表示写:
java.util.concurrent.locks.ReentrantReadWriteLock.Sync.EXCLUSIVE_MASK
读状态是所有线程获取读锁次数的总和,而每个线程各自获取读锁的次数只能选择保存在ThreadLocal中,由线程自身维护。
可重入读写锁的读锁和写锁的区别:
每个读线程需要自己用ThreadLocal记录自己获取了多少次锁,这是与写锁的最大的区别
java.util.concurrent.locks.ReentrantReadWriteLock.Sync.exclusiveCount(int)记录写锁执行了多少次锁,因为只有一个写线程,所以可以不需要用ThreadLocal记录
读写锁的同步器都是同一个Sync
Condition的实现原理
等待队列是一个FIFO的队列,在队列中的每个节点都包含了一个线程引用,该线程就是在Condition对象上等待的线程,如果一个线程调用了Condition.await()方法,那么该线程将会释放锁、构造成节点加入等待队列并进入等待状态。源码实现:
java.util.concurrent.locks.AbstractQueuedSynchronizer.ConditionObject.await()
public final void await() throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); // 构成节点 Node node = addConditionWaiter(); // 释放锁 int savedState = fullyRelease(node); int interruptMode = 0; while (!isOnSyncQueue(node)) { // 加入等待队列 LockSupport.park(this); if ((interruptMode = checkInterruptWhileWaiting(node)) != 0) break; } if (acquireQueued(node, savedState) && interruptMode != THROW_IE) interruptMode = REINTERRUPT; if (node.nextWaiter != null) // clean up if cancelled unlinkCancelledWaiters(); if (interruptMode != 0) reportInterruptAfterWait(interruptMode); }
一个Condition包含一个等待队列,新增节点只需要将原有的尾节点nextWaiter指向它,并且更新尾节点即可。
调用Condition的signal()方法,将会唤醒在等待队列中等待时间最长的节点(首节点),在唤醒节点之前,会将节点移到同步队列中。源码实现:
java.util.concurrent.locks.AbstractQueuedSynchronizer.ConditionObject.signal()
java.util.concurrent.locks.AbstractQueuedSynchronizer.ConditionObject.doSignal(Node)
java.util.concurrent.locks.AbstractQueuedSynchronizer.transferForSignal(Node)
/** * Transfers a node from a condition queue onto sync queue. * Returns true if successful. * @param node the node * @return true if successfully transferred (else the node was * cancelled before signal) */ final boolean transferForSignal(Node node) { /* * If cannot change waitStatus, the node has been cancelled. */ if (!compareAndSetWaitStatus(node, Node.CONDITION, 0)) return false; /* * Splice onto queue and try to set waitStatus of predecessor to * indicate that thread is (probably) waiting. If cancelled or * attempt to set waitStatus fails, wake up to resync (in which * case the waitStatus can be transiently and harmlessly wrong). */ // 唤醒前将节点移动到同步队列中 Node p = enq(node); int ws = p.waitStatus; if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL)) // 唤醒 LockSupport.unpark(node.thread); return true; }
调用该方法的前置条件是当前线程必须获取了锁,可以看到signal()方法进行了isHeldExclusively()检查,也就是当前线程必须是获取了锁的线程。接着获取等待队列的首节点,将其移动到同步队列并使用LockSupport唤醒节点中的线程。
通过调用同步器的enq(Node node)方法,等待队列中的头节点线程安全地移动到同步队列。
Condition的signalAll()方法,相当于对等待队列中的每个节点均执行一次signal()方法,效果就是将等待队列中所有节点全部移动到同步队列中,并唤醒每个节点的线程。源码实现:
java.util.concurrent.locks.AbstractQueuedSynchronizer.ConditionObject.signalAll()
java.util.concurrent.locks.AbstractQueuedSynchronizer.ConditionObject.doSignalAll(Node)
/** * Removes and transfers all nodes. * @param first (non-null) the first node on condition queue */ private void doSignalAll(Node first) { lastWaiter = firstWaiter = null; // 相当于对等待队列中的每个节点均执行一次signal()方法 do { Node next = first.nextWaiter; first.nextWaiter = null; transferForSignal(first); first = next; } while (first != null); }
java.util.concurrent.locks.AbstractQueuedSynchronizer.ConditionObject.signal()