一个需求
我想要在代码层面实现一把锁。
一个变量
假如是一把排他锁,获取锁,相当于争夺一份公共资源,即:一个共享变量。
那么就可以是一个Boolean类型的变量,false就是锁空闲等待获取,true就是已经被锁,为了在获取锁的时候保证原子性,可以使用AtomicBoolean.
这样就实现了不可重入锁,那如果想重入呢,就不能用非错即对的Boolean类型了,可以用integer类型。
相应的,为了保证获取锁的原子性,可以使用AtomicInteger。
而AtomicInteger是一个比较复杂的工具类,而这里只需要用到一个cas的set方法,所以呢,可以单独从AtomicInteger里面把set的方法抽出来,减少对AtomicInteger的依赖的同时,也增加了锁的灵活性。
阻塞/唤醒
在多个线程在获取锁的过程中,没有获取到锁的线程怎么办呢?
直接返回失败丢掉吗?也不是不可以,起码有这样的使用场景,但比较少。更多的是不能丢掉,比如说客户端的一个请求,要先获取锁再获取资源,没获取到锁就丢掉吗?显然不行,这个时候最好的方式就是等待继续获取锁。
这个时候就有了几个问题,在哪里等待?如何等?
比较通用的方式就是使用队列,如果获取不到锁,就放到队列中去阻塞等待。当锁被释放之后,立即去唤醒。
那如何等待呢?又怎么唤醒呢?
-
wait/notify?只能在同步方法或者同步代码块中使用,而且notify()方法只能随机唤醒一个 wait 线程,并不能唤醒特定线程;
-
sleep? 只能睡眠当前线程,睡眠指定时间,无法特定时间唤醒;
这个时候有个专门处理这个问题的工具类就需要被造出来了,LockSupport!
LockSupport的注释:
Basic thread blocking primitives for creating locks and other synchronization classes.
翻译:用于创建锁和其他同步类的基本线程阻塞原语。
看起来,这个类就是干这个的。它的实现也是使用Unsafe类。
而 aqs(AbstractQueuedSynchronizer)也大致就是这样的结构。
共享变量:
/**
* The synchronization state.
*/
private volatile int state;
双向链表队列:
static final class Node {
volatile Node prev;
volatile Node next;
}
应用
在JDK中有下面几个使用场景:
ReentrantLock中根据AQS实现了可重入锁。
ThreadPoolExecutor中的Work类,实现了不可重入锁。
CountDownLatch,允许一个或多个线程等待,直到在其他线程中执行的一组操作完成为止。
引用
下面是几个比较好的博文:
AbstractQueuedSynchronizer源码解读
AQS
阻塞和唤醒线程——LockSupport功能简介及原理浅析
ThreadPoolExecutor核心实现原理和源码解析<二>