我们在线程同步机制(一)--Synchronized和Lock简要介绍中学习了同步和临界区的概念,并且讨论了多个并发任务共享一个资源时的同步情况。访问共享资源的代码块叫临界区。
我们在线程同步机制(一)--Synchronized和Lock简要介绍中学习了一下内容:
- synchronized关键字
- Lock接口及其实现类,如ReentrantLock,ReentrantReadWriteLock.ReadLock和ReentrantReadWriteLock.WriteLock
本章我们将学习如何使用更高级的同步机制来实现多线程间的同步。
- 信号量(Semaphore):一种计数器,用来保护一个或多个共享资源的访问。它是并发编程的一个基础工具。
- CountDownLatch:是Java语言提供的同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许线程一直等待
- CyclicBarrier:Java语言提供的同步辅助类,允许多个线程在某个集合点处进行相互等待
- Phaser:它把并发任务分多个阶段运行,在开始下一个阶段之前,当前阶段中所有的线程都必须执行完成。
- Exchanger:它提供了两个线程之间的数据交换点。
在应用程序中,任何时候都可以使用Semaphore来保护临界区,因为它是一个基础的同步机制。
1、资源的并发访问控制。
Java语言提供了信号量(Semaphore)机制。信号量是一种计数器,用来保护一个或多个共享资源的访问。如果线程要访问一个共享资源,它必须要获得信号量。如果信号量的内部计数器大于0,信号量将减1,然后允许访问这个共享资源。计数器大于0意味着有可以使用的资源,因此线程被允许使用其中一个资源。否则,如果信号的计数器等于0,信号量就会把线程置入休眠直到计数器大于0.计数器等于0的时候意味着所有的共享资源已经被其他线程使用了,所以需要访问这个共享资源的线程必须等待。
当线程使用完某个共享资源时,信号量必须释放,以便其他线程能够访问共享资源。释放操作将使信号量内部计数器增加1.
使用信号量实现临界区必须遵循3个步骤,从而保护对资源的访问。
(1).必须通过acquire()方法获得信号量
(2).使用共享资源执行必要的操作
(3).必须通过release()方法释放信号量
2、等待多个并发事件的完成
Java并发API提供了CountDownLatch类。在完成一组正在其他线程中执行的操作之前,它允许线程一直等待。这个类使用一个整数进行初始化,这个整数是线程要等待完成的操作的数目。当一个线程要等待某些操作先完成时,需要调用await()方法。当一个线程操作完成后,它将调用countDown()方法使内部计数器减1.当计数器变成0的时候,CountDownLatch类将唤醒所有调用await()而进入休眠的线程
CountDownLatch类有三个基本元素:
- 一个初始值,即定义必须等待的先行完成的操作数据;
- await()方法,需要等待其他事件先完成的线程调用
- countDown()方法,每个被等待的事件在完成操作后调用,使内部计数器减1.当内部计数器到达0时,countDownLatch对象将唤醒所有在await()方法上等待的线程
3、并发阶段任务的运行
Java并发API还提供了Phaser,它允许执行并发多阶段任务。当我们有并发任务并且需要分解成几步执行时,这种机制就非常实用。Phaser类机制是在每一步结束的位置对线程进行同步,当所有线程都执行完了这一步,才允许执行下一步。Phaser类提供了onAdvance()方法,它在phaser阶段改变的时候自动执行。
4、并发任务之间的数据交换Exchanger。它允许在并发任务之间的数据交换,具体来说,Exchanger类允许两个线程定义同步点。当两个线程都达到同步点时,他们交换数据结构。