目录
一、Lock类层次结构及相关API
1、Lock类层级结构
2、Lock接口相关API
3、关于Condition
二、synchronized VS Lock
1、synchronized实现的锁优缺点
2、Lock实现的锁优缺点
三、手撸一把简单的ReentrantLock
1、ReentrantLock实现简单流程
2、代码示例
3、测试用例
一、Lock类层次结构及相关API
1、Lock类层级结构
ReentrantLock和ReentrantReadWriteLock都是java.util.concurrent并发包下的工具类,ReentrantLock实现了Lock接口,ReentrantReadWriteLock实现了ReadWriteLock接口,而其中的ReadLock和WriteLock又实现了Lock接口。
2、Lock接口相关API
为了区别synchronized和ReentrantLock,我们先了解一下Lock接口相关的API。
结论:
1、lock()最常用。
2、lockInterruptibly()方法一般更昂贵,有的impl可能没有实现lockInterruptibly,只有真的需要响应中断时才使用。
3、关于Condition
Object中的wait()、notify()、notifyAll()只能和synchronized关键字配合使用,可以唤醒一个或全部线程。Condition需要与Lock配合使用,提供多个等待集合,更精确的控制。
备注:如果说Lock代替了同步代码块或同步方法的加解锁逻辑,那么Condition则是代替了Object的等待和唤醒逻辑。
二、synchronized VS Lock
1、synchronized实现的锁优缺点
我们先说说synchronized实现的平台级锁的优点:
使用简单,语义清晰,在方法上加上synchronized关键字或者使用同步代码块即可。
由JVM提供,提供了多种优化方案,如锁粗化、锁消除、偏向锁、轻量级锁、重量级锁等,关于这些优化特性请参考:Java面试题之synchronized关键字原理以及锁相关。
锁的释放由JVM完成,不用人工干预,也降低了死锁的可能性。
再说说它的缺点:
无法实现一些锁的高级功能,如超时锁、中断锁、读写锁、共享锁、公平锁。
2、Lock实现的锁优缺点
Lock实现的锁主要弥补了synchronized的缺点,比如上面提到的锁的高级功能,如超时锁、中断锁、读写锁、共享锁、公平锁这些。
再说一下它的缺点:
需手动释放锁,新手使用不当可能造成死锁。
没有synchronized实现的锁那么多优化项。
三、手撸一把简单的ReentrantLock
1、ReentrantLock实现简单流程
先介绍一下一些关键属性,如下:
waiters
代表锁池,说白了就是抢锁失败线程的等待队列。owner
代表成功获取到锁的线程。count
用来标记锁的可重入次数。- 先描述下加锁流程:
如果可重入次数为0代表锁还没有被任何线程持有,这时可以通过CAS(0, count + 1)操作进行抢锁。
如果可重入次数不为0,则判断当前抢锁的线程是不是持有锁的线程,如果是则将可重入次数+1即可。
如果如上两种条件都不满足,则直接算抢锁失败。
抢锁失败的线程直接进入等待队列,并阻塞等待。
再描述下解锁流程:
如果调用解锁方法的线程不是持有锁的线程,则抛出IllegalMonitorStateException,否则第2步。
将可重入次数-1,如果可重入次数为0则代表解锁成功。
解锁成功后唤醒队列头部等待的抢锁线程。
2、代码示例
public class NicksReentrantLock implements Lock { // 用来标识哪个线程获取到锁 private Thread owner; // 重入次数 private AtomicInteger counter = new AtomicInteger(0); // 等待队列 private BlockingQueue<Thread> waiters = new LinkedBlockingQueue<>(); @Override public void lock() { if (!tryLock()) { waiters.offer(Thread.currentThread()); // 循环解决伪唤醒问题 while (true) { // 如果队列头部是当前线程说明可以抢锁 if (Thread.currentThread() == waiters.peek()) { // 若抢锁成功则出队列 if (tryLock()) { waiters.poll(); return; } } // 若当前线程不在队列头部或者抢锁失败则挂起 LockSupport.park(); } } } @Override public void lockInterruptibly() throws InterruptedException { } @Override public boolean tryLock() { int ct = counter.get(); if (ct == 0) { // 重入次数为0说明当前线程可以通过CAS操作获取锁 if (counter.compareAndSet(0, ct + 1)) { owner = Thread.currentThread(); return true; } return false; } // 不为0则判断获取锁的线程是否是当前线程,如果是当前线程,则将可重入次数加1 if (owner == Thread.currentThread()) { counter.set(ct + 1); return true; } return false; } @Override public boolean tryLock(long time, TimeUnit unit) throws InterruptedException { return false; } @Override public void unlock() { // 解锁成功应该唤醒队列头部的线程 if (tryUnlock()) { Optional.ofNullable(waiters.peek()).ifPresent(LockSupport::unpark); } } public boolean tryUnlock() { // 如果当前解锁的线程不是获取到锁的线程则抛异常 if (Thread.currentThread() != owner) { throw new IllegalMonitorStateException(); } int ct = counter.get(); int nextCt = ct - 1; counter.set(nextCt); if (nextCt == 0) { owner = null; return true; } return false; } @Override public Condition newCondition() { return null; } }
3、测试用例
public class Test { private static int count = 0; public static void main(String[] args) { NicksReentrantLock lock = new NicksReentrantLock(); for (int index = 0; index < 10000; index++) { new Thread(() -> { try { lock.lock(); count++; } finally { lock.unlock(); } }).start(); } LockSupport.parkNanos(1000 * 1000 * 1000); System.out.println("累加后的值为:" + count); } }
备注:控制台输出为: 累加后的值为:10000
。