1. 并发工具类
1.1 CountDownLatch
CountDownLatch类位于java.util.concurrent包下,利用它可以实现类似计数器的功能。比如有一个任务A,它要等待其他4个任务执行完毕之后才能执行,此时就可以利用CountDownLatch来实现这种功能了。
countDown()实现计数器-1
await()等待拦截方法,等待计数器为0时再放行,否则则一直阻塞
getCount()获取当前计数器中计数数量
编写代码测试
//等待子线程全部执行完毕后在执行主线程
public static void main(String[] args) {
System.out.println("主线程开始运行");
//第一个 子线程
new Thread(()->{
System.out.println("子线程:"+Thread.currentThread().getName()+"开始运行");
}).start();
//第二个 子线程
new Thread(()->{
System.out.println("子线程:"+Thread.currentThread().getName()+"开始运行");
}).start();
System.out.println("子线程执行完毕,主线程继续执行");
}
控制台效果
从下图可看出问题主线程先抢占了资源然后是第一个子线程,但是当第一个子线程运行完后紧接着又是主线程运行最后这才是
第二个子线程运行,并没有使所有子线程走完后再走主线程

使用CountDownLatch解决方案
编写代码测试
//等待子线程全部执行完毕后在执行主线程
//监视几个线程就写几个 当前有两个线程所以写2
private static CountDownLatch countDownLatch=new CountDownLatch(2);
public static void main(String[] args) throws InterruptedException {
System.out.println("主线程开始运行");
//第一个 子线程
new Thread(()->{
System.out.println("子线程:"+Thread.currentThread().getName()+"开始运行");
//线程数量-1,通知该线程运行完毕
countDownLatch.countDown();
}).start();
//第二个 子线程
new Thread(()->{
System.out.println("子线程:"+Thread.currentThread().getName()+"开始运行");
//线程数量-1,通知该线程运行完毕
countDownLatch.countDown();
}).start();
//等待,等待计数器中线程数为0时才能继续向下执行
countDownLatch.await();
System.out.println("子线程执行完毕,主线程继续执行");
}
控制台效果
从下图可看出是所以有子线程走完后再执行的主线程

1.2 CyclicBarrier
CyclicBarrier初始化时规定一个数目,然后计算调用了CyclicBarrier.await()进入等待的线程数。当线程数达到了这个数目时,
所有进入等待状态的线程被唤醒并继续。
CyclicBarrier就象它名字的意思一样,可看成是个障碍, 所有的线程必须到齐后才能一起通过这个障碍。
CyclicBarrier初始时还可带一个Runnable的参数, 此Runnable任务在CyclicBarrier的数目达到后,
所有其它线程被唤醒前被执行。
编写测试代码
//创建10个线程
for (int i=1;i<=10;i++){
new Thread(()->{
try {
Thread.sleep(100);
System.out.println(Thread.currentThread().getName()+"准备就绪");
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("开始比赛");
}).start();
}
}
控制台效果

使用CyclicBarrier解决方案
编写测试代码
//设置等待线程数量,当线程数量到达指定数量时,统一向下运行
private static CyclicBarrier cyclicBarrier=new CyclicBarrier(10);
public static void main(String[] args) {
//创建10个线程
for (int i = 1; i <=10 ; i++) {
new Thread(()->{
try {
Thread.sleep(100);
System.out.println(Thread.currentThread().getName()+"准备就绪");
//等待
cyclicBarrier.await();
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
System.out.println("开始比赛~");
}).start();
}
}
控制台效果

1.3 Semaphore
Semaphore是一种基于计数的信号量。它可以设定一个阈值,基于此,多个线程竞争获取许可信号,做自己的申请后归还,
超过阈值后,线程申请许可信号将会被阻塞。Semaphore可以用来构建一些对象池,资源池之类的,比如数据库连接池,我们也
可以创建计数为1的Semaphore,将其作为一种类似互斥锁的机制,这也叫二元信号量,表示两种互斥状态。
编写测试代码
一共有三个茅坑,每个人上完之后让下一个人上
for (int i = 1; i <= 10; i++) {
new Thread(() -> {
System.out.println("终于有茅坑了,可以上厕所了");
}).start();
}
控制台效果
如图是不可以的

使用Semaphore解决方案
编写测试代码
private static Semaphore semaphore=new Semaphore(3);
public static void main(String[] args) {
for (int i = 1; i <= 10; i++) {
new Thread(() -> {
if (semaphore.availablePermits()>0){
System.out.println(Thread.currentThread().getName()+"终于有茅坑了,可以上厕所了");
}else{
System.out.println(Thread.currentThread().getName()+"没有茅坑了");
}
try {
//申请资源
semaphore.acquire();
System.out.println(Thread.currentThread().getName()+"前面的人终于走了~");
//模拟上厕所时间
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName()+"上完了~");
//释放资源
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
;
}).start();
}
}
控制台 效果

1.4 Exchanger
可以执行线程的资源交换,线程数量必须为偶数,因为是两两相互交换资源,如果不是偶数默认情况下导致阻塞,
可以设置交换资源超时时间
编写测试代码
private static String str1="资源一";
private static String str2="资源二";
//构建资源交换
private static Exchanger<String> stringExchanger=new Exchanger<>();
public static void main(String[] args) {
//第一个线程
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"初始占用资源:"+str1);
//资源交换,将资源交给其他线程和获取到其他线程交换过来的资源
try {
String newStr=stringExchanger.exchange(str1);
System.out.println(Thread.currentThread().getName()+"交换资源:"+newStr);
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
//第二个线程
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"初始占用资源:"+str2);
//资源交换,将资源交给其他线程和获取到其他线程交换过来的资源
try {
String newStr=stringExchanger.exchange(str2);
System.out.println(Thread.currentThread().getName()+"交换资源:"+newStr);
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
//第三个线程
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"初始占用资源:"+str2);
//资源交换,将资源交给其他线程和获取到其他线程交换过来的资源
try {
String newStr=stringExchanger.exchange(str2,1000, TimeUnit.MILLISECONDS);
System.out.println(Thread.currentThread().getName()+"交换资源:"+newStr);
} catch (InterruptedException | TimeoutException e) {
e.printStackTrace();
}
}).start();
}
控制台效果

2. 线程池
类似于一个池子,可以存放/管理线程
2.1 使用线程池的好处
第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
第二:提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
第三:提高线程的可管理性
2.2 线程池的作用
线程池是为突然大量爆发的线程设计的,通过有限的几个固定线程为大量的操作服务,减少了创建和销毁线程所需的时
间,从而提高效率。
如果一个线程的时间非常长,就没必要用线程池了(不是不能作长时间操作,而是不宜。),况且我们还不能控制线程池
中线程的开始、挂起、和中止。
2.3 线程池的分类及使用
线程池顶级类ThreadPoolExecutor最终实现Executor接口,在JUC包下,通过Executors类可以创建不同类型的线程池,
分类如下
1.newScheduledThreadPool 定时任务线程池,可以设置任务时间
//构建 一个线程池
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 1; i <=10 ; i++) {
//创建线程池
executorService.execute(()->{
System.out.println("创建线程池"+Thread.currentThread().getName());
});
}

2.newFixedThreadPool 定长线程池
//构建线程池对象
ExecutorService executorService = Executors.newFixedThreadPool(24);
for (int i = 0; i < 10; i++) {
//创建线程
executorService.execute(()->{
System.out.println("创建线程:"+Thread.currentThread().getName());
});
}
//线程池停止
executorService.shutdown();

3.newSingleThreadExecutor 利用的是单线程,单线程处理任务,一般不用
//创建一个线程池
ExecutorService executorService = Executors.newSingleThreadExecutor();
for (int i = 0; i < 10; i++) {
executorService.execute(()->{
System.out.println("创建线程:"+Thread.currentThread().getName());
});
}

4.newCachedThreadPool 带缓存的线程池
//创建一个线程池
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(3);
for (int i = 0; i < 10; i++) {
scheduledExecutorService.schedule(()->{
System.out.println("创建线程:"+Thread.currentThread().getName());
},1000, TimeUnit.MILLISECONDS);
}

3.所有的线程池分类底层调用的都是ThreadPoolExecutor()构造方法,该构造方法中每一个参数含义
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue)
3.1 corePoolSize代表核心线程池大小,当有任务时,会创建对应线程处理对应任务,当线程到达一定数量后,则会缓存到
队列当中,不会再次创建新的线程
3.2 maximumPoolSize: 线程池最大线程数,它表示在线程池中最多能创建多少个线程
3.3 keepAliveTime: 表示线程没有任务执行时最多保持多久时间会终止。
3.4 unit: 参数keepAliveTime的时间单位,有7种取值,在TimeUnit类中有7种静态属性
4.如何确定线程池中创建线程数量
考虑CPU密集和IO密集,如果不考虑IO情况下,一般是处理器数量+1,如果考虑IO,则处理器*2
