1 ReentrantLock
ReentrantLock是一个互斥锁,也是一个可重入锁。ReentrantLock锁在同一个时间点只能被一个线程锁持有,但是它可以被同一个线程多次获取,每获取一次AQS的state就加1,每释放一次state就减1。还记得synchronized嘛,它也是可重入的,一个同步方法调用另外一个同步方法是没有问题的。
在使用上无非就是获取锁和释放锁,我们完全可以用它来实现synchronized的功能。
实现synchronized功能
1.1ReentrantLock和synchronized都是独占锁,只允许线程互斥的访问临界区。但是实现上两者不同:synchronized加锁解锁的过程是隐式的,用户不用手动操作,优点是操作简单,但显得不够灵活。一般并发场景使用synchronized的就够了;ReentrantLock需要手动加锁和解锁,且解锁的操作尽量要放在finally代码块中,保证线程正确释放锁。ReentrantLock操作较为复杂,但是因为可以手动控制加锁和解锁过程,在复杂的并发场景中能派上用场。
1.2.ReentrantLock和synchronized都是可重入的。synchronized因为可重入因此可以放在被递归执行的方法上,且不用担心线程最后能否正确释放锁;而ReentrantLock在重入时要却确保重复获取锁的次数必须和重复释放锁的次数一样,否则可能导致其他线程无法获得该锁。
需要注意的是:必须要手动释放锁,使用syn锁定的话如果遇到异常,jvm会自动释放锁,但是lock必须手动释放锁,因此经常在finally中进行锁的释放
1.3 reentrantlock可以进行“尝试锁定”tryLock,这样无法锁定,或者在指定时间内无法锁定,线程可以决定是否继续等待。如下,可以指定在5秒内获取锁,如果获取不到,线程可以继续执行。
ReentrantLock还可以调用lockInterruptibly方法,可以对线程interrupt方法做出响应,在一个线程等待锁的过程中,可以被打断,如下:
1.4 ReentrantLock默认是非公平锁,可以指定为公平锁。
2 CountDownLatch
一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。
用给定的计数 初始化 CountDownLatch。由于调用了 countDown() 方法,所以在当前计数到达零之前,await 方法会一直受阻塞。之后,会释放所有等待的线程,await 的所有后续调用都将立即返回。这种现象只出现一次——计数无法被重置。如果需要重置计数,请考虑使用 CyclicBarrier。
在一些应用场合中,需要等待某个条件达到要求后才能做后面的事情;同时当线程都完成后也会触发事件,以便进行后面的操作。 这个时候就可以使用CountDownLatch。CountDownLatch最重要的方法是countDown()和await(),前者主要是倒数一次,后者是等待倒数到0,如果没有到达0,就只有阻塞等待了。
3 CyclicBarrier
从字面上的意思可以知道,这个类的中文意思是“循环栅栏”。大概的意思就是一个可循环利用的屏障。
它的作用就是会让所有线程都等待完成后才会继续下一步行动。
举个例子,就像生活中我们会约朋友们到某个餐厅一起吃饭,有些朋友可能会早到,有些朋友可能会晚到,但是这个餐厅规定必须等到所有人到齐之后才会让我们进去。这里的朋友们就是各个线程,餐厅就是 CyclicBarrier。
public CyclicBarrier(int parties)
public CyclicBarrier(int parties, Runnable barrierAction)
parties 是参与线程的个数
第二个构造方法有一个 Runnable 参数,这个参数的意思是最后一个到达线程要做的任务
public int await() throws InterruptedException, BrokenBarrierException
public int await(long timeout, TimeUnit unit) throws InterruptedException, BrokenBarrierException, TimeoutException
一个线程组的线程需要等待所有线程完成任务后再继续执行下一次任务
4 phase
phase 是阶段的意思。Phaser是按照不同阶段执行线程的,就像是结合了CountDownLatch和CyclicBarrier,它本身维护着一个叫 phase 的成员变量代表当前执行的阶段。
如果程序中遇到分好几个阶段执行,并且有的阶段需要多个线程共同参与的情况下可能会用到Phase。
模拟一个结婚场景:
自定义一个MarriagePhaser 继承 Phaser 重写Phaser的onAdvance方法定义了4个阶段(进入下一个阶段时该方法被自动调用)。
阶段必须从0开始。onAdvice的两个参数 phase是第几个阶段,registeredParties是目前有几个已注册线程参加。
最后返回值为false表示流程未结束,继续执行下一阶段,返回true表示流程结束。
Persion的run方法中有四种行为对应四个阶段
public class PhaserTest2 {
static Random r = new Random();
static PhaserTest.MarriagePhaser phaser = new PhaserTest.MarriagePhaser();
static void milliSleep(int milli) {
5 ReentrantReadWriteLock
类ReentrantLock具有完全互斥排他的效果,即同一时间只有一个线程在执行ReentrantLock.lock()后面的代码。这样虽然保证了线程的安全性,但是效率低下。JDK提供了ReentrantReadWriteLock读写锁,使用它可以加快效率,在某些不需要操作实例变量的方法中,完全可以使用读写锁ReemtrantReadWriteLock来提升该方法的运行速度。
读写锁表示有两个锁,一个是读操作相关的锁,也称为共享锁;另一个是写操作相关的锁,也叫排他锁。也就是多个读锁之间不互斥,读锁与写锁互斥、写锁与写锁互斥。在没有线程Thread进行写入操作时,进行读取操作的多个Thread都可以获取读锁,而进行写入操作的Thread只有在获取写锁后才能进行写入操作。即多个Thread可以同时进行读取操作,但是同一时刻只允许一个Thread进行写入操作。
读锁与读锁可以共享,这种锁一般用于只读操作,不对变量进行修改操作。
写锁与写锁互斥,这就类似于ReentrantLock的作用效果。
6 Semaphore
Semaphore也是一个线程同步的辅助类,可以维护当前访问自身的线程个数,并提供了同步机制。使用Semaphore可以控制同时访问资源的线程个数,例如,实现一个文件允许的并发访问数。
Semaphore 是 synchronized 的加强版,作用是控制线程的并发数量。就这一点而言,单纯的synchronized 关键字是实现不了的。
Semaphore的主要方法:
void acquire():从此信号量获取一个许可,在提供一个许可前一直将线程阻塞,否则线程被中断。
void release():释放一个许可,将其返回给信号量。
7 Exchanger
java.util.concurrent包中的Exchanger类可用于两个线程之间交换信息。可简单地将Exchanger对象理解为一个包含两个格子的容器,通过exchanger方法可以向两个格子中填充信息。当两个格子中的均被填充时,该对象会自动将两个格子的信息交换,然后返回给线程,从而实现两个线程的信息交换。
Exchanger可以在两个线程之间交换数据,只能是2个线程,他不支持更多的线程之间互换数据。今天我们就通过实例来学习一下Exchanger的用法。
8 LockSupport
LockSupport定义了一组以park开头的方法来阻塞当前线程,unpark来唤醒被阻塞的线程。
LockSupprot是线程的阻塞原语,用来阻塞线程和唤醒线程。每个使用LockSupport的线程都会与一个许可关联,如果该许可可用,并且可在线程中使用,则调用park()将会立即返回,否则可能阻塞。如果许可尚不可用,则可以调用 unpark 使其可用。但是注意许可不可重入,也就是说只能调用一次park()方法,否则会一直阻塞。