并发包
java中的并发包指的就是java.util.concurrent—>JUC
CountDownLatch
CountDownLatch:是java.util.concurrent并发包下的一个工具类,其意思“倒计时门闩”,它允许一个或多个线程一直等待直到其他线程执行完毕才开始执行
countDown
方法和await
方法,CountDownLatch在初始化时,需要指定用给定一个整数作为计数器。当调用countDown
方法时,计数器会被减1;当调用await
方法时,如果计数器大于0时,线程会被阻塞,一直到计数器被countDown
方法减到0时,线程才会继续执行。计数器是无法重置的,当计数器被减到0时,调用await
方法都会直接返回。
不足:
1)CountDownLatch是不能够重用的
根据上面的解析,大家也发现,共享锁加锁的操作并不会增加state的值。CountDownLatch中state一旦变成0就没有提供其他方式增长回去了。
所以CountDownLatch是一次性消耗品,用完就得换新的。这样设计也是比较正常的,对状态的要求比较严格,例如已经开始比赛了你再告诉裁判说,这次再加5个人,比赛都进行一半了,不会考虑再回去的。随意修改约束值会带来很多逻辑上的问题。
2)CountDownLatch无限等待
countDown没有被调用,那么await就会一直等下去。countDown常见没有被调用的情况:异常中断,线程池拒绝策略。
可以使用:public boolean await(long timeout, TimeUnit unit)
根据业务情况,增加一个最大等待时间。使用这种方式,需要对失败的各种情况作出业务上的对应处理,否则就出现各种数据不正确的问题
例子:
package com.rj.bd.threads.thread19;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @desc 4名运动员等待口令(用mian线程充当裁判,新建的4个线程作为运动员)
* @time 2019-10-22
*/
public class Test02 {
public static void main(String[] args) {
//1.开启一个可缓存的线程池
ExecutorService service = Executors.newCachedThreadPool();
//2.实例化一个裁判计数器
final CountDownLatch cdOrder = new CountDownLatch(1);
//3.实例化一个远动员计数器
final CountDownLatch cdAnswer = new CountDownLatch(4);
//4.创建4个子线程用来模拟4个运动员
for (int i = 0; i < 4; i++)
{
Runnable runnable = new Runnable()
{
@Override
public void run()
{
try
{
//5输出选手正在等待裁判发布口令
System.out.println("选手" + Thread.currentThread().getName() + "正在等待裁判发布口令");
//7裁判计数器的等待阻塞
cdOrder.await();
//9.选手接受裁判口令
System.out.println("选手" + Thread.currentThread().getName() + "已接受裁判口令");
Thread.sleep((long) (Math.random() * 1000));
//10.选手达到终点
System.out.println("选手" + Thread.currentThread().getName() + "到达终点");
//11.运动员计数器减为0,然后程序执行到第12步
cdAnswer.countDown();
}
catch (InterruptedException e)
{
System.out.println("中断异常......");
}
}
};
service.execute(runnable);
}
try {
//6.让裁判计数器的计数减去1,目的是为了为了让第7步的裁判计数器阻塞等待实现放行
Thread.sleep((long) (Math.random() * 1000));
System.out.println("裁判"+Thread.currentThread().getName()+"即将发布口令");
cdOrder.countDown();//裁判计数器减去1
//8.虽然计数器会实现线程的阻塞,但是其他的正常的代码还是按照默认的从上向下的顺序执行
//而此时执行到55行的运动员计数器的时候,程序会发现运动员计数器的计数还不是0,于是就重新去执行运动员计数器所监控的线程的任务其实就是第9,10,11步
cdAnswer.await();
//12.主线程输出提示语:所有选手都到达终点和汇总成绩排名
System.out.println("所有选手都到达终点");
System.out.println("裁判"+Thread.currentThread().getName()+"汇总成绩排名");
} catch (InterruptedException e) {
e.printStackTrace();
}
service.shutdown();
}
}
CyclicBarrier
CyclicBarrier:翻译成中文就是”循环栅栏/障碍“,可以使一定数量的线程反复地在栅栏位置处汇集。当线程到达栅栏位置时将调用await方法,这个方法将阻塞直到所有线程都到达栅栏位置。如果所有线程都到达栅栏位置,那么栅栏将打开,此时所有的线程都将被释放,而栅栏将被重置以便下次使用。
例子:
package com.rj.bd.threads.thread20;
/**
* @desc 乘客类
* @time 2019-10-23
*/
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class Tourist extends Thread {
private CyclicBarrier cyclicBarrier;
public Tourist(CyclicBarrier cyclicBarrier){
this.cyclicBarrier=cyclicBarrier;
}
@Override
public void run()
{
try {
System.out.println("乘客:"+getName()+"已经向巴士车走来");
Thread.sleep(2000);
System.out.println("乘客:"+getName()+"已经在巴士上坐下了");
} catch (InterruptedException e)
{
System.out.println("中断异常。。。。");
}
try
{
cyclicBarrier.await();
System.out.println("乘客:"+getName()+":人满发车喽。。。。。。");
}
catch (InterruptedException e)
{
System.out.println("中断异常。。。。");
}
catch (BrokenBarrierException e)
{
System.out.println("打破障碍异常(人没到齐非要走).....");
}
}
}
package com.rj.bd.threads.thread20;
/**
* @desc 测试类:测试CyclicBarrier
* @time 2019-10-23
*/
import java.util.concurrent.CyclicBarrier;
public class Test {
public static void main(String[] args) {
/**
* CyclicBarrier的实例化:
* 参数1:parties: 是参与线程的个数
* 参数2:最后一个到达线程要做的任务
*/
CyclicBarrier cyclicBarrier=new CyclicBarrier(5,new Runnable()
{
@Override
public void run()
{
System.out.println("\n最后一个上车的关闭车门。。。。\n");
}
});
for (int i = 0; i < 5; i++)
{
Tourist writer = new Tourist(cyclicBarrier);
writer.start();
}
}
}
Semaphore
Semaphore : 是一种基于计数的信号量。它可以设定一个阈值,基于此,多个线程竞争获取许可信号,做自己的申请后归还,超过阈值后,线程申请许可信号将会被阻塞。 Semaphore可以用来构建一些对象池,资源池之类的,比如数据库连接池,我们也可以创建计数为1的Semaphore,将其作为一种类似互斥锁的机制,这也叫二元信号量,表示两种互斥状态
简单来说:
- Semaphore是 synchronized 的加强版,作用是控制线程的并发数量
- Seampore类初始化的时候就可以指定阈值(这个阈值就是线程的并发数量)
- Seampore经常被用来构建例如资源池,连接池这样的东西(为什么呢。因为这些池是需要一个动态平衡的)
- Seampore的阈值为1的时候其功能类似于互斥锁
例子:
package com.rj.bd.threads.thread20;
/**
* @desc 测试类:测试信号量类Semapore
* @time 2019-10-23
*/
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
public class Test03 {
public static void main(String[] args) {
//1.创建一个线程池
ExecutorService executorService = Executors.newCachedThreadPool();
//2.实例化信号量对象,且只允许 10个线程同时访问
final Semaphore semaphore = new Semaphore(10);
for (int i=0;i<30;i++)//3.假设程序中有30个进行IO读写的线程
{
//4.将创建的子线程装入到线程池中并执行
executorService.submit(new Runnable()
{
@Override
public void run()
{
try
{
//5.获取许可
semaphore.acquire();
//6.执行
System.out.println("\n获取许可并执行的线程的名字: " + Thread.currentThread().getName());
Thread.sleep(new Random().nextInt(5000)); // 模拟随机执行时长
//7.释放
semaphore.release();
System.out.println("\n释放线程的名字..." + Thread.currentThread().getName());
} catch (InterruptedException e)
{
e.printStackTrace();
}
}
},i);
}
//8.关闭线程池
executorService.shutdown();
}
}