多线程学习笔记二之JUC组件 - Java闲谈的个人空间 - 开源中国

277 阅读4分钟
原文链接: my.oschina.net

概述

  为了对共享资源提供更细粒度的同步控制,JDK5新增了java.util.concurrent(JUC)并发工具包,并发包新增了Lock接口(以及相关实现类)用来实现锁功能,它提供了与synchronized关键字相似的同步功能,只是在使用时需要显式地获取和释放锁,还具备内置锁不具备的自由操作锁获取和释放、可中断地获取锁以及超时获取锁等多种synchronized关键字不具备的同步特性。
  为了实现JUC提出的各种功能的锁,JUC包的作者,并发大师Doug Lee提出了同步器 synchronizer的概念,在同步器中定义了共享资源的同步状态,维护了一个双端的先入先出的同步队列用于存放获取共享资源失败而等待的线程,线程利用同步器实现的锁获取共享资源流程如下:

为了实现上述操作,需要下面三个基本组件的相互协作:

  • 对共享资源同步状态进行原子性管理 ---> 利用CAS对同步状态进行更新
  • 线程的阻塞与唤醒 ---> 调用native方法
  • 等待队列的管理 ---> 维护FIFO队列

  由此可以看出,同步器是实现锁的关键,同步器面向的是线程访问和资源控制,它定义了线程对资源是否能够获取以及线程的排队等操作。关于同步器的详细解释会在AQS(AbstractQueuedSynchronizer)解析里给出。

JUC锁框架图

  JUC中Lock接口定义了锁的规范,各种功能的锁都实现了Lock接口,各个锁以内部类继承AQS同步器的方式聚合了同步器,从而以同步器为基石实现具体功能的锁。

  1. Lock
      Lock接口为独占锁(同一时间共享资源只能由一个线程获取),共享锁(同一时间共享资源可由多个线程获取),公平锁(各个线程获得锁的机会是公平的),非公平锁(各个线程获得锁的机会是公平的),重入锁(线程在获取到锁之后,再次获取该锁而不会被该锁所阻塞,不会自己把自己锁在外面)提供了实现规范,接口中定义的方法如下:
  2. AbstractQueuedSynchronizer
      AbstractQueuedSynchronizer就是被称之为AQS的类,可以用于构建锁或者其他相关同步装置的基础框架。从图中也可以看出,ReentrantLock,ReentrantReadWriteLock,CountDownLatch,CyclicBarrier和Semaphore这些类通过内部类继承AQS的方式来实现锁的功能。
  3. Condition
      Condition需要和Lock联合使用,它的作用是代替Object监视器方法,可以通过await(),signal()来休眠/唤醒线程。Condition 接口描述了可能会与锁有关联的条件变量。这些变量在用法上与使用 Object.wait 访问的隐式监视器类似,但提供了更强大的功能,接口中定义的方法如下:
  4. LockSurport
      LockSupport中的park() 和 unpark()调用native方法将线程休眠。
  5. ReentrantLock
      ReentrantLock对与共享资源采取的是较为保守的独占策略,即只有一个线程能够获得锁;ReentrantLock支持公平锁和非公平锁,默认是非公平锁;从名称也能看出,ReentrantLock是可重入锁。
  6. ReentrantReadWriteLock
      ReentrantReadWriteLock是读写锁接口ReadWriteLock的实现类,它包括子类ReadLock和WriteLock。ReentrantLock是共享锁,而WriteLock是独占锁。
  7. CountDownLatch
      CountDownLatch是一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。
  8. CyclicBarrier
      CyclicBarrier是一个同步辅助类,允许一组线程互相等待,直到到达某个公共屏障点(common barrier point)。因为该barrier在释放等待线程后可以重用,所以称它为循环的barrier。喜欢的朋友可以进圈:609164807 一起交流 一起进步
  9. Semaphore
      Semaphore是一个计数信号量,它的本质是一个"共享锁"。信号量维护了一个信号量许可集。线程可以通过调用acquire()来获取信号量的许可;当信号量中有可用的许可时,线程能获取该许可;否则线程必须等待,直到有可用的许可为止。 线程可以通过release()来释放它所持有的信号量许可。

使用内置锁还是JUC显示锁?

  JUC中的显示锁提供了与synchronized内置锁相同的互斥性与内存可见性,那么我们的多线程代码到底使用哪一种锁来实现同步呢?首先从性能角度考虑,在JDK5 显示锁刚推出时,性能是大幅领先于内置锁的,在随后的JDK版本中,JVM对内置锁进行了性能优化,现在二者的性能已经没有明显优劣之分;从功能使用上,内置锁的使用较为简单,无需手动获得以及释放锁,而显示锁的功能更为强大,具有更高的灵活性,当我们需要使用到锁的高级功能,如以响应中断/支持超时的方式获取锁或者自定义实现锁,这时候可以考虑内置锁。