线程协作与并发流程控制&AQS
什么是控制并发流程
控制并发流程的工具类:
-
Semaphore
信号量,可以通过控制“许可证”的数量,来保证线程之间的配合。
线程只有在拿到“许可证”后才能继续运行。相比于其他的同步器,更加灵活。
-
CyclicBarrier
线程会等待,直到足够多线程达到了事先规定的数目。一旦达到了触发条件,就可以进行下一步的动作。
适用于线程之间相互等待处理结果就绪的场景。
-
Phaser
和CyclicBarrier类似,但是计数可变。
Java7加入的。
-
CountDownLatch
和CyclicBarrier类似,数量递减到0时,触发动作。
不可重复使用。
-
Exchanger
让两个线程再合适时交换对象。
适用场景:当两个线程工作在同一个类的不同实例上时,用于交换数据。
-
Condition
可以控制线程“等待”和“唤醒”。
是Object.wait()的升级版。
CountDownLatch倒计时门闩
-
作用
并发流程控制工具,倒数门闩,“拼团购物,人满发货”。倒数结束之前,一直处于等待状态,直到倒计时结束了,次线程才继续工作。
-
两种典型用法
CountDownLatch(int count)是仅有的一个的构造函数,参数count为需要倒数的数值。
调用await()方法的线程会被挂起,它会等待直到count值为0才继续执行。
countDown()方法将count值减1,直到为0时,等待的线程会被唤醒。
用法一:一个线程等待多个线程都执行完毕,再继续自己的工作。一等多
用法二:多个线程等待某一个线程的信号,同时开始执行。多等一
扩展用法:多个线程等多个线程执行完后,再同时执行。多等多
注意点:CountDownLatch是不能重用的,如果需要重新计数,可以考虑使用CyclicBarrier或者创建新的CountDownLatch实例。
Semaphore信号量
模拟了操作系统中的信号量功能,可以用来限制或者管理数量有限的资源的使用情况。信号量的作用是维护一个“许可证”的计数,线程可以“获取”许可证,那信号量剩余的许可证就减一,线程也可以“释放”一个许可证,那信号量剩余的许可证就加一,当信号量所拥有的许可证数量为0,那么下一个还想获取许可证的线程就需要等待,直到另外的线程释放了许可证。
-
使用流程:
-
初始化Semaphore并指定许可证数量。
-
在需要前在代码前加acquire()或者acquireUninterrptibly()方法。
-
任务结束后,调用release()来释放许可证。
-
-
主要方法:
-
new Semaphore(int permit,boolean fair):这里设置是否使用公平策略,如果传入true,那么Semaphore会把之前等待的线程放入FIFO的队列里,以便当有了新的许可证,可以分发给之前等了最长时间的线程。
-
acquire():可以响应中断的获取。可以传入参数表示获取多少许可证,但是也要记得归还。
-
acquireUninterrptibly():不能响应中断的获取。
-
tryAcquire():看看现在有没有空闲的许可证返回布尔值,如果有就去获取,如果没有也没关系,不必陷入阻塞,可以去做其它的事情。
-
tryAcquire(timeout):和tryAcquire()一样,但是多了一个超时时间。
-
release():不能忘记归还许可证,可以传入参数。
-
-
特殊用法
-
一次获取或者释放多个许可证
比如TaskA会调用很消耗资源的method1(),而TaskB调用的是不太消耗资源的额method2(),假设我们一共有5个许可证。那么我们就可以要求TaskA获取5个许可证才能执行,而TaskB只需要获取一个许可证就能执行,这样就避免了A和B同时运行的情况,可以根据自己的需求合理分配资源。
-
-
注意点
-
获取和释放的许可证数量必须一致,否则比如每次都获取2个但是只释放1个甚至不释放,随着时间的推移,到最后许可证书数量不够用,会导致程序卡死。虽然信号量并不对是否和获取的数量做规定,但是这是编程规范,否则容易出错。
-
注意在初始化Semaphore的时候设置公平性,一般设置为true会更合理。
-
并不是必须由获取许可证的线程释放那个许可证,事实上,获取和释放许可证对线程并无要求,也许是A获取了,然后由B释放,只要逻辑合理即可。可以跨线程、跨线程池。
-
信号量的作用,除了控制临界区最多同时有N个线程访问以外,另一个作用是可以实现“条件等待”,例如线程1需要在线程2完成准备工作后才能开始工作,那么线程1acquire(),而线程2完成任务后release(),这样的话,相当于是轻量级的CountdownLatch。
-
Condition接口(条件对象)
-
作用
当线程1需要等待某个条件的时候,就去执行condition.await()方法,一旦执行了await()方法,线程就进入阻塞状态。
然后通常会有另外一个线程,假设是线程2,去执行响应的条件,直到这个条件达成的时候,线程2就会去执行condition.signal()方法,这时JVM就会从阻塞的线程中找,找到那些等待该condition的线程,当线程1收到可执行信号的时候,它的线程状态会变成Runable可执行状态。
signalAll()和signal()的区别:
signalAll()会唤醒所有正在等待的线程。
signal()是公平的,只会唤醒那个等待时间最长的线程。
-
使用
-
普通用法
-
使用Condition实现生产者消费者模式
public class ConditionDemo {
private int queueSize = 10;
private PriorityQueue<Integer> queue = new PriorityQueue<Integer>(queueSize);
private Lock lock = new ReentrantLock();
private Condition notFull = lock.newCondition();
private Condition notEmpty = lock.newCondition();
public static void main(String[] args) {
ConditionDemo2 conditionDemo2 = new ConditionDemo2();
Producer producer = conditionDemo2.new Producer();
Consumer consumer = conditionDemo2.new Consumer();
producer.start();
consumer.start();
}
class Consumer extends Thread {
-