1. Lock
1.1 存在的理由
// 支持中断的 APIvoid lockInterruptibly() throws InterruptedException;// 支持超时的 APIboolean tryLock(long time, TimeUnit unit) throws InterruptedException;// 支持非阻塞获取锁的 APIboolean tryLock();
以上是Lock接口中的三个方法,提供了synchronized所不具备的能力
- 响应中断 使用synchronized时,假设一个线程持有锁A, 又需要获取锁B,一旦锁B被占用,那这个线程就会进入堵塞状态,一旦发生死锁,就没有任何机会来唤醒线程了,但是如果堵塞状态的线程能够响应中断信号被唤醒,就有可能可以释放锁A,避免死锁的出现。
- 支持超时 如果线程在指定时间内无法获取锁,不是进入堵塞状态,而是返回一个异常
- 非堵塞地获取锁 尝试获取锁,不进入堵塞状态,而是返回结果
java线程状态
- 初始(NEW):新创建了一个线程对象,但还没有调用start()方法。
- 运行(RUNNABLE):Java线程中将就绪(ready)和运行中(running)两种状态笼统的称为“运行”。 线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取CPU的使用权,此时处于就绪状态(ready)。就绪状态的线程在获得CPU时间片后变为运行中状态(running)。
- 阻塞(BLOCKED):表示线程阻塞于锁。
- 等待(WAITING):进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)。
- 超时等待(TIMED_WAITING):该状态不同于WAITING,它可以在指定的时间后自行返回。
- 终止(TERMINATED):表示该线程已经执行完毕。
1.2 实现原理
Java中 多线程的可见性是通过 Happens-Before 规则保证的,synchronized有一条规则synchronized解锁Happens-Before于后续对这个锁的加锁。而ReentrantLock内部也持有一个volatile的成员变量state,加锁和解锁时都会读写state值,从而来保证可见性。
Happens-Before规则 1.程序的顺序性规则:程序前面对某个变量的修改对后续操作是可见的 2.volatile变量规则:对volatile变量的写操作对于后续此变量的读操作是可见的 3.传递性:如果A Happens-Before B, 且B Happens-Before C, 那么 A Happens-Before C 4.锁的规则:对一个锁的解锁Happens-Before后续对于这个锁的加锁 5.线程start()规则:主线程A启动子线程B后,子线程B能够看到主线程启动子线程B前的操作 6.线程join()规则:主线程A等待子线程B完成(主线程A通过调用子线程B的join()方法实现),当子线程B完成后(主线程A中join()方法返回),主线程能够看到子线程的操作。
示例
class SampleLock {volatile int state;// 加锁lock() {// 省略许多代码state = 1;}// 解锁unlock() {// 省略许多代码state = 0;}}
1.3 公平锁与非公平锁
ReentrantLock有两个构造函数,一个是无参构造函数,一个是传入fair参数的构造函数,fair参数代表的是锁的公平策略,如果传入true就表示需要构造一个公平锁,反之则表示要构造一个非公平锁。
public ReentrantLock() {sync = new NonfairSync();}public ReentrantLock(boolean fair) {sync = fair ? new FairSync() : new NonfairSync();}

图来自Java并发编程实战 https://time.geekbang.org/column/article/87779
公平和非公平锁的队列都基于锁内部维护的一个双向链表,表结点Node的值就是每一个请求当前锁的线程。在公平锁中,如果有另一个线程持有锁或者有其他线程在等待队列中等待这个所,那么新发出的请求的线程将被放入到队列中。而非公平锁上,只有当锁被某个线程持有时,新发出请求的线程才会被放入队列中(此时和公平锁是一样的)。所以,它们的差别在于非公平锁会有更多的机会去抢占锁。
1.3.1 公平锁
公平锁的实现原理在于每次有线程来抢占锁的时候,当前线程就会执行如下步骤
//公平锁策略static final class FairSync extends Sync {private static final long serialVersionUID = -3000897897090466540L;final void lock() {acquire(1);}protected final boolean tryAcquire(int acquires) {final Thread current = Thread.currentThread();int c = getState();if (c == 0) {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;}}//查看是否有等待队列节点public final boolean hasQueuedPredecessors() {// The correctness of this depends on head being initialized// before tail and on head.next being accurate if the current// thread is first in queue.Node t = tail; // Read fields in reverse initialization orderNode h = head;Node s;return h != t &&((s = h.next) == null || s.thread != Thread.currentThread());}
1.3.2 非公平锁
非公平锁在实现的时候多次强调随机抢占
static final class NonfairSync extends Sync {private static final long serialVersionUID = 7316153563782823691L;final void lock() {if (compareAndSetState(0, 1))setExclusiveOwnerThread(Thread.currentThread());elseacquire(1);}protected final boolean tryAcquire(int acquires) {return nonfairTryAcquire(acquires);}}
1.3.3 示例
public class FairLockTest {//false表示非公平private ReentrantLock lock = new ReentrantLock(false);//true表示公平锁//private ReentrantLock lock = new ReentrantLock(true);public void testFair(){try {lock.lock();System.out.println(Thread.currentThread().getName() +"获得了锁");}finally {lock.unlock();}}public static void main(String[] args) {FairLockTest fairLockTest = new FairLockTest();Runnable runnable = () -> {System.out.println(Thread.currentThread().getName()+"启动");fairLockTest.testFair();};Thread[] threadArray = new Thread[10];for (int i=0; i<10; i++) {threadArray[i] = new Thread(runnable);}for (int i=0; i<10; i++) {threadArray[i].start();}}}//false 结果Thread-0启动Thread-4启动Thread-5启动Thread-3启动Thread-7启动Thread-2启动Thread-1启动Thread-8启动Thread-6启动Thread-0获得了锁Thread-9启动Thread-4获得了锁Thread-5获得了锁Thread-3获得了锁Thread-7获得了锁Thread-2获得了锁Thread-1获得了锁Thread-8获得了锁Thread-6获得了锁Thread-9获得了锁//true 结果Thread-0启动Thread-4启动Thread-5启动Thread-6启动Thread-3启动Thread-7启动Thread-8启动Thread-2启动Thread-1启动Thread-9启动Thread-0获得了锁Thread-4获得了锁Thread-5获得了锁Thread-6获得了锁Thread-3获得了锁Thread-7获得了锁Thread-8获得了锁Thread-2获得了锁Thread-1获得了锁Thread-9获得了锁
1.4 用锁的最佳实践
- 永远只在更新对象的成员变量时加锁
- 永远只在访问可变的成员变量时加锁
- 永远不在调用其他对象的方法时加锁
1.5 Lock和Condition 在Dubbo中的使用
TCP协议本身是异步的,以RPC为例,在TCP协议层面,发送完RPC请求后,线程是不会等待RPC的响应结果的。所以在Dubbo中就做了异步转同步的操作,主要操作就在 DefaultFuture 中, 源码在 https://github.com/apache/incubator-dubbo/blob/da69a9c8db15b6047cb80ed3d2251215784dcb0e/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/exchange/support/DefaultFuture.java
这里我简化下,提炼出关键代码
private final Lock lock = new ReentrantLock();private final Condition done = lock.newCondition();public Object get(int timeout) throws RemotingException {if (timeout <= 0) {timeout = Constants.DEFAULT_TIMEOUT;}if (!isDone()) {long start = System.currentTimeMillis();lock.lock();try {while (!isDone()) {done.await(timeout, TimeUnit.MILLISECONDS);if (isDone() || System.currentTimeMillis() - start > timeout) {break;}}} catch (InterruptedException e) {throw new RuntimeException(e);} finally {lock.unlock();}if (!isDone()) {throw new TimeoutException(sent > 0, channel, getTimeoutMessage(false));}}return returnFromResponse();}//RPC 结果是否已经返回public boolean isDone() {return response != null;}//RPC 结果返回private void doReceived(Response res) {lock.lock();try {response = res;done.signalAll();} finally {lock.unlock();}if (callback != null) {invokeCallback(callback);}}
调用线程通过使用get()方法等待RPC返回结果,这个方法里面,调用lock()获取锁,在finally里面调用unlock()释放锁,获取锁后,通过在循环中调用await()方法来实现等待。
在RPC结果返回时,调用doReceived(), 这个方法中,调用lock()获取锁,在finally里面调用unlock()释放锁,获取锁后调用signalAll()来通知所有持有这个锁的线程,结果已经返回。
