ReentrantReadWriteLock

应用场景

在一些场景中,这是没有必要的,多个线程的读操作完全可以并行,在读多写少的场景中,让读操作并行可以明显提高性能

通过一个ReadWriteLock产生两个锁:一个读锁,一个写锁。读操作使用读锁,写操作使用写锁。需要注意的是,只有“读-读”操作是可以并行的,“读-写”和“写-写”都不可以。只有一个线程可以进行写操作,在获取写锁时,只有没有任何线程持有任何锁才可以获取到,在持有写锁时,其他任何线程都获取不到任何锁。在没有其他线程持有写锁的情况下,多个线程可以获取和持有读锁。

读锁和写锁看上去是两个锁,它们是怎么协调的?

具体实现比较复杂,我们简述下其思路。内部,它们使用同一个整数变量表示锁的状态,16位给读锁用,16位给写锁用,使用一个变量便于进行CAS操作,锁的等待队列其实也只有一个。写锁的获取,就是确保当前没有其他线程持有任何锁,否则就等待。写锁释放后,也就是将等待队列中的第一个线程唤醒,唤醒的可能是等待读锁的,也可能是等待写锁的。读锁的获取不太一样,首先,只要写锁没有被持有,就可以获取到读锁,此外,在获取到读锁后,它会检查等待队列,逐个唤醒最前面的等待读锁的线程,直到第一个等待写锁的线程。如果有其他线程持有写锁,获取读锁会等待。读锁释放后,检查读锁和写锁数是否都变为了0,如果是,唤醒等待队列中的下一个线程。

信号量类Semaphore

信号量类Semaphore就是用来限制对资源的并发访问数。

构造方法

        public Semaphore(int permits)
        public Semaphore(int permits, boolean fair)

主要方法:

        //阻塞获取许可
        public void acquire() throws InterruptedException
        //阻塞获取许可,不响应中断
        public void acquireUninterruptibly()
        //批量获取多个许可
        public void acquire(int permits) throws InterruptedException
        public void acquireUninterruptibly(int permits)
        //尝试获取
        public boolean tryAcquire()
        //限定等待时间获取
        public boolean tryAcquire(int permits, long timeout,
            TimeUnit unit) throws InterruptedException
        //释放许可
        public void release()

倒计时门栓CountDownLatch

CountDownLatch。它相当于是一个门栓,一开始是关闭的,所有希望通过该门的线程都需要等待,然后开始倒计时,倒计时变为0后,门栓打开,等待的所有线程都可以通过,它是一次性的,打开后就不能再关上了。

构造方法:

public CountDownLatch(int count)

主要方法

        public void await() throws InterruptedException
        public boolean await(long timeout, TimeUnit unit) throws InterruptedException
        public void countDown()

await检查计数是否为0,如果大于0,就等待,await可以被中断,也可以设置最长等待时间。countDown检查计数,如果已经为0,直接返回,否则减少计数,如果新的计数变为0,则唤醒所有等待的线程

循环栅栏CyclicBarrier

CyclicBarrier它相当于是一个栅栏,所有线程在到达该栅栏后都需要等待其他线程,等所有线程都到达后再一起通过,它是循环的,可以用作重复的同步。

CyclicBarrier特别适用于并行迭代计算,每个线程负责一部分计算,然后在栅栏处等待其他线程完成,所有线程到齐后,交换数据和计算结果,再进行下一次迭代。

构造方法

public CyclcBarrier(int parties) //有一个数字,但表示的是参与的线程个数
    
public cyclicBarrier(int parties, Runnable barrierAction) // Runnable 这个参数表示栅栏动作,当所有线程到达栅栏后,在所有线程执行下一步动作前,运行参数中的动作,这个动作由最后一个到达栅栏的线程执行。

主要方法

        public int await() throws InterruptedException, BrokenBarrierException
        public int await(long timeout, TimeUnit unit) throws InterruptedException,
            BrokenBarrierException, TimeoutException

await可以被中断,可以限定最长等待时间,中断或超时后会抛出异常。需要说明的是异常BrokenBarrierException,它表示栅栏被破坏了,什么意思呢?在CyclicBarrier中,参与的线程是互相影响的,只要其中一个线程在调用await时被中断了,或者超时了,栅栏就会被破坏。此外,如果栅栏动作抛出了异常,栅栏也会被破坏。被破坏后,所有在调用await的线程就会退出,抛出BrokenBarrierException。

CyclicBarrier与CountDownLatch可能容易混淆,我们强调下它们的区别

1)CountDownLatch的参与线程是有不同角色的,有的负责倒计时,有的在等待倒计时变为0,负责倒计时和等待倒计时的线程都可以有多个,用于不同角色线程间的同步。

2)CyclicBarrier的参与线程角色是一样的,用于同一角色线程间的协调一致。

3)CountDownLatch是一次性的,而CyclicBarrier是可以重复利用的。

参考文章

java编程的逻辑基础(马俊昌)

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐