锁是用来控制多个线程访问共享资源的方式,一般来说,一个锁能够防止多个线程同时访问共享资源,在 Lock 接口出现之前,Java 应用程序只能依靠 synchronized 关键字来实现同步锁的功能,在 Java 5 以后,增加了 JUC 的并发包且提供了 Lock 接口用来实现锁的功能,它提供了与 synchroinzed 关键字类似的同步功能,只是它比 synchronized 更灵活,能够显示的获取和释放锁。

Lock 实现类

Lock 是一个接口,有两个核心方法 lock() 和 unlock(),它有很多实现类,这里介绍两个常用的实现类的使用,ReentrantLock 和 ReentrantReadWriteLock。

Lock 和 ReentrantLock 的类图如下所示。

image.png

AbstractQueuedSynchronizer 同步器是实现 Lock 锁机制的核心,后面会详细讲解
Sync、NonfairSync、FairSync 是 ReentrantLock 的内部类,继承了 AbstractQueuedSynchronizer 类

接下来介绍 ReentrantLock 和 ReentrantReadWriteLock 的基本使用

Lock 使用

ReentrantLock

重入锁,表示支持重新进入的锁,也就是说,如果当前线程 t1 通过调用 lock 方法获取了锁之后,再次调用 lock,是不会再阻塞去获取锁的,直接增加重试次数就行了。

  1. public class AtomicDemo {
  2. private static int count;
  3. private static Lock lock = new ReentrantLock();
  4. public static void main(String[] args) {
  5. for (int i = 0; i < 1000; i++) {
  6. new Thread(new Runner()).start();
  7. }
  8. try {
  9. Thread.sleep(4000);
  10. } catch (InterruptedException e) {
  11. e.printStackTrace();
  12. }
  13. System.out.println("运行结果:" + count);
  14. }
  15. private static class Runner implements Runnable {
  16. @Override
  17. public void run() {
  18. // 获取锁
  19. lock.lock();
  20. try {
  21. Thread.sleep(1);
  22. } catch (InterruptedException e) {
  23. e.printStackTrace();
  24. }
  25. count++;
  26. // 释放锁
  27. lock.unlock();
  28. }
  29. }
  30. }

ReentrantReadWriteLock

可重入读写锁,表示支持重新进入的锁,并且读写锁维护了一对锁,读锁和写锁,具有如下特征。

  • 读锁与读锁可以共享
  • 读锁与写锁不可以共享(排他)
  • 写锁与写锁不可以共享(排他)

适用于读多写少的场景,在该场景下读写锁能够提供比排它锁更好的并发性和吞吐量。

在执行写操作是,线程必须要获取写锁,当已经有线程持有写锁的情况下,当前线程会被阻塞,只有当写锁释放以后,读写操作才能继续执行。使用读写锁提升读操作的并发性,也保证每次写操作对所有的读写操作的可见性。

  1. public class ReadWriteLockDemo {
  2. private static Map<String, Object> cacheMap = new HashMap<>();
  3. private static ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
  4. private static Lock readLock = readWriteLock.readLock();
  5. private static Lock writeLock = readWriteLock.writeLock();
  6. /**
  7. * 获取缓存数据
  8. *
  9. * @param key
  10. * @return
  11. */
  12. public static Object get(String key) {
  13. readLock.lock();
  14. try {
  15. return cacheMap.get(key);
  16. } finally {
  17. readLock.unlock();
  18. }
  19. }
  20. /**
  21. * 保存缓存数据
  22. *
  23. * @param key
  24. * @param value
  25. * @return
  26. */
  27. public static Object put(String key, Object value) {
  28. writeLock.lock();
  29. try {
  30. return cacheMap.put(key, value);
  31. } finally {
  32. writeLock.unlock();
  33. }
  34. }
  35. }

synchronized 和 Lock 的区别

  • 从层次上,一个是关键字、一个是类,这个是最直观的的差异
  • 从使用上,Lock 具备更大的灵活性,可以控制锁的获取和释放,而 synchronized 锁的释放是被动的,当出现异常或者同步代码块执行完成后才会释放锁
  • Lock 可以判断锁的状态,而 synchronized 不可以
  • Lock 可以实现公平锁、非公平锁,而 synchronized 只有非公平锁

作者:殷建卫 链接:https://www.yuque.com/yinjianwei/vyrvkf/gsq47i 来源:殷建卫 - 架构笔记 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。