前言

内置锁虽然方便但限制很多:

  1. 一个线程因为等待内置锁而进入阻塞之后,就无法中断该线程了
  2. 尝试获取内置锁时,无法设置超时
  3. 获得内置锁,必须使用synchronized块

​...​


一、使用ReetrantLock


ReetrantLock 提供显式的 ​​lock​​​ 和 ​​unlock​​ 方法

// ReetrantLock 代替 synchronized
Lock lock = new ReetrantLock();
lock.lock();
try {
// 使用共享资源
} finally {
lock.unlock();
}


(1)可中断的锁

【Concurrency】之 超越内置锁(使用ReentrantLock)_ReentrantLock


(2)超时

ReentrantLock 可以为获取锁的操作设置超时时间
​​​lock.tryLock(1000, TimeUnit.MILLISECONDES);​


(3)交替锁(head-over-hand locking)

在链表中插入一个节点。

1. 用锁保护整个链表(但链表加锁时其他使用者无法访问链表)

2. 交替锁可以只锁住链表的一部分(允许不涉及被锁部分的其他线程自由访问链表)

【Concurrency】之 超越内置锁(使用ReentrantLock)_链表_02


(4)条件变量

并发编程经常需要等待某个事件发生。
  1. 从队列删除元素前需要等待队列非空
  2. 向缓存添加数据前需要等待缓存有足够的空间
// 建议按照下面的模式使用条件变量:
ReentrantLock lock = new ReentrantLock();
Condition condition = lock.newCondition();

lock.lock();
try {
while (! <<条件为真>>) {
condition.await();
}
// 使用共享资源
} finally {
lock.unlock();
}

当另一个线程调用了signal() 或者 signalAlll(),意味着对应的条件可能变为真,await()将原子地回复运行并重新加锁。


二、原子变量


关于之前自增,java.util.concurrent.atomic包提供了更好的方案:

【Concurrency】之 超越内置锁(使用ReentrantLock)_超时_03


AtomicInteger 的 incrementAndGet()方法功能上等价于++count(AtomicInteger 也提供了getAndIncrement方法,等价于count++)。不过与++count 不同,incrementAndGet()方法是原子操作。

原子变量是无锁(lock-free)非阻塞(non-blocking)算法的基础,这种算法可以不用锁和阻塞来达到同步的目的。


三、再想想


  1. ReentrantLock创建时可以设置一个描述公平性的变量。什么是“公平”的锁?何时适合使用公平的锁?使用非公平的锁会怎样?
  2. 什么是ReentrantReadWriteLock?它与ReentrantLock有什么区别?使用与什么场景?
  3. 什么是“虚假唤醒”(spurious wakeup)什么时候发生虚假唤醒?为什么符合规范的代码不用担心虚假唤醒?
  4. 什么是AtomicIntegerFieldUpdater?它与AtomicInteger有什么区别?适合用于什么场景?


四、小结


ReentrantLock和java.util.concurrent.atomic可以做:
  1. 在线程获取锁时中断它
  2. 设置线程获取锁的超时时间
  3. 按任意顺序获取和释放锁
  4. 用条件变量等待某个条件变成真
  5. 使用原子变量避免锁的使用