目录
Java SE 5之后,并发包中新增 了Lock接口(以及相关实现类)用来实现锁功能,它提供了与synchronized关键字类似的同步功能,只是在使用时需要显式地获取和释放锁。
一个简单的Lock使用方式的示例:
// ReentrantLock实现简单的独占锁
public class LockDemo {
private Lock lock = new ReentrantLock();
private int count = 0;
public void doSth () {
lock.lock();
try {
Thread.sleep(1);
count++;
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
final LockDemo lockDemo = new LockDemo();
for (int i = 0; i < 1000; i++) {
new Thread(()->{lockDemo.doSth();}).start();
}
Thread.sleep(3000);
System.out.println("result:"+lockDemo.count);
}
}
ReentrantLock是独占锁,就是能够防止多个线程同时访问共享资源的锁。当然,也有共享锁,比如ReadWriteLock,这种读写锁的实现允许多个线程同时读取数据,但只允许一个线程写入数据。这在读多写少的场景中可以提高性能和吞吐量。
public class RwLockDemo {
private ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private Map cacheMap = new HashMap<>();
private Lock read = readWriteLock.readLock();
private Lock write = readWriteLock.writeLock();
public Object get(String key) {
System.out.println("start read ...");
read.lock();
try {
return cacheMap.get(key);
} finally {
read.unlock();
}
}
public Object put(String key,Integer value){
System.out.println("start write ...");
write.lock();
try {
return cacheMap.put(key, value);
} finally {
write.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
final RwLockDemo rwLockDemo = new RwLockDemo();
rwLockDemo.put("a",1);
rwLockDemo.put("b",2);
System.out.println(rwLockDemo.cacheMap.get("a"));
}
}
锁的实现:synchronized是java语言的关键字,基于JVM实现。ReentrantLock基于JDK的API实现的(lock()和unlock()配合finally语句块实现的。)
性能:JDK1.6以前,synchronized重量级锁性能略差;1.6以后加入自适应自旋、锁消除等,两者性能就差不多了。
功能特点:ReentrantLock提供了些高级功能,如等待可中断、可实现公平锁、可实现选择性通知。
| 区别 | synchronized | ReentrantLock |
|---|---|---|
| 锁实现机制 | 对象头监视器模式 | 依赖AQS |
| 灵活性 | 不灵活 | 支持响应中断、超时、尝试获取锁 |
| 释放锁形式 | 自动释放锁 | 显式调用unlock() |
| 支持锁类型 | 非公平锁 | 公平锁和非公平锁 |
| 条件队列 | 单条件队列 | 多条件队列 |
| 可重入支持 | 支持 | 支持 |
ReentrantLock的一些关键实现原理:
CAS操作:ReentrantLock的实现依赖于CAS(Compare-And-Swap)操作,这是一种原子操作,用于在多线程环境中修改共享变量。CAS操作允许一个线程比较内存中的值与预期值,如果相等,则更新为新的值。
锁状态:ReentrantLock内部维护了一个锁状态(lock state),这个状态可以告诉锁当前是被哪个线程占用的,以及该线程占用了几次锁。这允许同一个线程多次获取同一把锁而不会发生死锁。当一个线程首次获取锁时,锁状态设置为1,每次再次获取锁时,状态递增。
AQS(AbstractQueuedSynchronizer):ReentrantLock的实现基于AQS,这是一个用于构建锁和其他同步器的抽象框架。AQS提供了队列、状态管理、等待线程管理等功能。ReentrantLock通过扩展AQS并根据自身需求实现其中的一些方法来实现锁的功能。
公平性和非公平性:ReentrantLock支持公平性和非公平性。在公平模式下,等待线程按照FIFO顺序获取锁。在非公平模式下,等待线程有可能插队,当锁释放时,可能不是等待时间最长的线程获得锁。这个选择可以通过ReentrantLock的构造函数来指定。
可中断性:ReentrantLock允许等待锁的线程响应中断信号,通过使用lockInterruptibly()方法可以实现这一点。
条件变量:ReentrantLock还提供了条件变量(Condition),允许线程在某些条件下等待和唤醒。条件变量是ReentrantLock的一部分,可以通过newCondition()方法创建。
ReentrantLock可以用于实现公平锁和非公平锁,它们的主要区别在于线程获取锁的顺序和策略。
公平锁(Fair Lock):
公平锁确保线程按照请求锁的顺序获得锁资源,即按照FIFO(先进先出)的顺序分配锁。
当一个线程请求锁但锁已经被其他线程占用时,该线程会进入等待队列,并按照请求锁的顺序排队等待。
当锁释放时,等待队列中的线程中最早请求锁的线程将获得锁,以确保公平性。
优点:公平锁能够避免线程饥饿,即某个线程无限期地等待获取锁的情况。每个线程最终都有机会获取锁。
缺点:因为需要维护等待队列和按照顺序分配锁,公平锁的性能可能相对较差,特别是在高并发情况下。
使用方式:通过在创建 ReentrantLock 实例时传递 true 参数来创建公平锁,例如:ReentrantLock fairLock = new ReentrantLock(true);
非公平锁(Non-Fair Lock):
非公平锁不考虑线程请求锁的顺序,当锁可用时,任何等待的线程都有机会立即获得锁。
当锁被释放时,如果有等待的线程,JVM会从等待线程中随机选择一个线程来获得锁。
优点:非公平锁的性能通常比公平锁更好,因为它减少了维护等待队列和按照顺序分配锁的开销。
缺点:非公平锁可能导致某些线程一直无法获取锁,因为它们不会按照请求锁的顺序获得锁,可能会导致线程饥饿的情况。
使用方式:默认情况下,ReentrantLock 创建的是非公平锁,可以不传递参数或传递 false 来明确使用非公平锁。
选择公平锁还是非公平锁取决于应用的需求和性能考虑。如果你需要确保线程以公平的方式获得锁资源,可以使用公平锁。如果性能更为重要,而且你不担心某些线程可能被饿死,那么非公平锁可能是更好的选择。一般来说,非公平锁在高并发场景下表现更好,但可能会导致某些线程长时间等待。

入口:ReentrantLock.java中的lock()方法调用了继承自AbstractQueuedSynchronizer的抽象静态类Sync的lock()方法,默认情况下创建的是非公平锁。(部分源码)
private final Sync sync;
abstract static class Sync extends AbstractQueuedSynchronizer {}
public void lock() {
sync.lock();
} 获取同步状态:非公平锁直接CAS尝试抢占,如果锁没有被抢占,直接抢占成功;大部分时候是抢占失败的,调用acquire()独占式获取同步状态会调用继承自AQS进行重写的acquire()判断tryAcquire()和addWaiter。
public final void acquire(int arg) if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }
判断结果,同步失败,线程进入同步队列挂起状态等待
就是acquire(int arg)方法调用流程。如下所示:
