《Java并发编程的艺术》第5章 Java中的锁(下)
AQS
获取锁失败,入队,加入到同步队列尾部
头节点获取锁成功,释放锁时,头节点将出队
重入锁ReentrantLock
重入锁的特性
-
可重入
-
公平性获取锁
1.可重入实现原理
同步状态(可认为就是锁) state 表示锁被一个线程重复获取的次数
-
state == 0 表示当前锁未被线程持有
-
state !=0 计数器,表示锁被某个线程重进入的次数
1. 锁的获取
如果尝试获取锁的线程为当前持有锁的线程,可再次获取成功,并将计数器加1。
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. 锁的释放
protected final boolean tryRelease(int releases) { //释放锁时,将计数器减1. int c = getState() - releases; if (Thread.currentThread() != getExclusiveOwnerThread()) throw new IllegalMonitorStateException(); boolean free = false; //如果计数器的值变为0,将持有锁的线程设为null,表示锁未被持有 if (c == 0) { free = true; setExclusiveOwnerThread(null); } setState(c); return free; }
2.公平锁与非公平锁实现原理
非公平锁
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; }
公平锁
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; }
读写锁ReadWriteLock
什么是读写锁
-
读锁:共享锁,可以多个线程同时持有。约束条件:当读锁被持有,后续线程只能获取读锁。
-
写锁:排他锁,同一时刻只能同一个线程持有。约束条件:当写锁被持有,后续获取读锁或写锁的线程都将被阻塞。
1.读写状态的设计
将同步状态state“按位切割使用”。
高16位为读状态,表示读锁被获取的次数;低16位为写状态,表示写锁被获取的次数。
读写锁支持重进入,以读写线程为例:读线程获取读锁之后,能够再次获取读锁;写线程获取写锁后,能够再次获取写锁,也能够获取读锁。
以上状态表示某线程获取了写锁3次,获取了读锁2次。
2.写锁的获取与释放
2.1写锁的获取
每次写锁获取成功时将写状态加1。
据读写锁的约束条件,获取写锁时有如下限制:
1)如果读锁被持有了,无法获取写锁
2)如果读锁没有没持有,但是写锁的持有线程不是当前线程,无法获取写锁
protected final boolean tryAcquire(int acquires) { /* * Walkthrough: * 1. If read count nonzero or write count nonzero * and owner is a different thread, fail. * 2. If count would saturate, fail. (This can only * happen if count is already nonzero.) * 3. Otherwise, this thread is eligible for lock if * it is either a reentrant acquire or * queue policy allows it. If so, update state * and set owner. */ Thread current = Thread.currentThread(); int c = getState(); //获取写锁的获取次数 int w = exclusiveCount(c); /** * c!=0,说明读锁或写锁被占有 */ if (c != 0) { // (Note: if c != 0 and w == 0 then shared count != 0) /** * 存在读锁或者当前线程不是已经获取写锁的线程,获取写锁失败 */ if (w == 0 || current != getExclusiveOwnerThread()) return false; if (w + exclusiveCount(acquires) > MAX_COUNT) throw new Error("Maximum lock count exceeded"); // Reentrant acquire setState(c + acquires); return true; } /** * c==0,尝试获取写锁,获取成功后将写状态+1 */ if (writerShouldBlock() || !compareAndSetState(c, c + acquires)) return false; setExclusiveOwnerThread(current); return true; }
2.2写锁的释放
每次写锁释放成功时将写状态减1,当写状态为0时,表示写锁被释放,从而等待的读写线程能够继续访问读写锁。
3.读锁的获取与释放
3.1读锁的获取
每次读锁获取成功时将写状态加1。
据读写锁的约束条件,获取读锁时有如下限制:
如果其他线程获取了写锁,则当前线程获取读锁失败。
(说明:读状态是所有线程获取读锁的次数总和,每个线程各自获取读锁的次数保存在ThreadLocal中,由线程自己维护。以下代码做了精简,仅保留了必要的部分)
protected final int tryAcquiredShared(int unused) { for (;;) { int c = getState(); int nextc = c + (1 << 16); if (nextc < c) { throw new Error("Maxinum lock count exceeded"); } /** * 如果其他线程获取了写锁,当前线程获取写锁失败。 */ if (exclusiveCount(c) != 0 && owner != Thread.currentThread()) { return -1; } if (compareAndSetState(c, nextc)) { return 1; } } }
3.2读锁的释放
每次读锁释放成功时将读状态减1。
Condition实现原理
什么是Condition
Condition接口提供了await()、signal()、signalAll()等方法,配合Lock可以实现等待/通知模式。比如用于实现生产者/消费者。
1.等待队列
每个Condition对象都维持着一个等待队列,维持着当前等待在当前condition对象上的线程队列。
一个Lock可对应多个Condition对象,每个condition对象维持一个等待队列。
2.等待(await()方法)
调用await方法(或以await开头的方法),线程将释放锁,加入到等待队列的尾部。
从同步队列和等待队列的角度来看,调用await()方法时将释放锁,导致节点从同步队列移除,然后构造节点加入到等待队列找中。
整个过程相当于同步队列的首节点(获取了锁的节点)移动到等待队列尾部。
3.通知(signal()、signalAll()方法)
调用signal()方法,将会唤醒在等待队列中等待时间最长的节点(首节点)。
从同步队列和等待队列的角度来看,被唤醒的线程将加入到同步队列中,一旦线程获取锁成功,将从await()方法返回。
调用signal()方法,相当于对等待队列中的每一个线程执行了signal()方法,相当于将等待队列中的所有节点都移动到同步队列中。
(顺序问题:如果为非公平模式,所有从等待队列中唤醒的线程都将先尝试获取锁,如果获取失败才加入到同步队列中)