并发工具类与线程池
1.并发工具类
1.CountDownLatch:可以实现线程计数,阻塞后续线程
CountDownLatch类位于java.util.concurrent包下,利用它可以实现类似计数器的功能。比如有一个任务A,它要等待其他4个任务执行完毕之后才能执行,此时就可以利用CountDownLatch来实现这种功能了。
countDown()实现计数器-1
await()等待拦截方法,等待计数器为0时再放行,否则则一直阻塞
getCount()获取当前计数器中计数数量
CountDownLatch
利用它可以实现类似计数器的功能。比如有一个任务A,它要等待其他4个任务执行完毕之后才能执行,此时就可以利用CountDownLatch来实现这种功能了。
package com.yjc.juc;
import java.util.concurrent.CountDownLatch;
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
System.out.println("主线程启动---->等待子线程执行完毕");
//代表等待两个线程执行完主线程才继续执行
CountDownLatch countDownLatch=new CountDownLatch(2);
new Thread(() ->
{
System.out.println("第一个子线程" + Thread.currentThread().getName() + "正在执行");
countDownLatch.countDown();
System.out.println("第一个子线程" + Thread.currentThread().getName() + "执行完毕");
}).start();
new Thread(() ->
{
System.out.println("第二个子线程" + Thread.currentThread().getName() + "正在执行");
countDownLatch.countDown();
System.out.println("第二个子线程" + Thread.currentThread().getName() + "执行完毕");
}).start();
//使主线程进入等待状态
countDownLatch.await();
System.out.println("子线程执行完毕,主线程开始执行");
}
}
执行结果
CyclicBarrier
CyclicBarrier初始化时规定一个数目,然后计算调用了CyclicBarrier.await()进入等待的线程数。当线程数达到了这个数目时,所有进入等待状态的线程被唤醒并继续。
CyclicBarrier就象它名字的意思一样,可看成是个障碍, 所有的线程必须到齐后才能一起通过这个障碍。
CyclicBarrier初始时还可带一个Runnable的参数, 此Runnable任务在CyclicBarrier的数目达到后,所有其它线程被唤醒前被执行。
package com.yjc.juc;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierDemo {
static CyclicBarrier cyclicBarrier=new CyclicBarrier(7, ()-> {
System.out.println("开始!");
});
public static void main(String[] args){
for (int i = 1; i <=7 ; i++) {
final int count=i;
new Thread(()->{
try {
System.out.println(Thread.currentThread().getName()+":开始第"+count+"次");
cyclicBarrier.await();
System.out.println(Thread.currentThread().getName()+"开始进行");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
}
}
}
Semaphore
Semaphore是一种基于计数的信号量。它可以设定一个阈值,基于此,多个线程竞争获取许可信号,做自己的申请后归还,超过阈值后,线程申请许可信号将会被阻塞。Semaphore可以用来构建一些对象池,资源池之类的,比如数据库连接池,我们也可以创建计数为1的Semaphore,将其作为一种类似互斥锁的机制,这也叫二元信号量,表示两种互斥状态。
package com.yjc.juc;
import java.util.concurrent.Semaphore;
public class SemaphoreDemo {
public static void main(String[] args) {
//代表现在一共只有三个资源
Semaphore semaphore=new Semaphore(3);
//创建十条线程进行资源抢夺
for ( int i = 0; i <10 ; i++) {
final int count =i;
new Thread(()->{
try {
semaphore.acquire();//请求资源
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("第"+(count+1)+"条线程抢到了资源");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("第"+(count+1)+"条线程释放资源");
//释放资源
semaphore.release();
}).start();
}
}
}
执行结果
Exchanger
用于两个工作线程之间交换数据的封装工具类,简单说就是一个线程在完成一定的事务后想与另一个线程交换数据,则第一个先拿出数据的线程会一直等待第二个线程,直到第二个线程拿着数据到来时才能彼此交换对应数据
package com.yjc.juc;
import java.util.concurrent.Exchanger;
public class ExchangerDemo {
public static void main(String[] args) {
Exchanger exchanger = new Exchanger();
new Thread(() -> {
for (int i = 1; i < 10; i++) {
try {
Thread.sleep(800);
int data=i;
System.out.println(i+"\t"+Thread.currentThread().getName()+"交换前:"+data);
data = (int)exchanger.exchange(data);
System.out.println(i+"\t"+Thread.currentThread().getName()+"交换后:"+data);
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
new Thread(() -> {
int count=0;
while (true) {
++count;
try {
Thread.sleep(800);
int data = 0;
System.out.println(count+"\t"+Thread.currentThread().getName() + "交换前:" + data);
data = (int) exchanger.exchange(data);
System.out.println(count+"\t"+Thread.currentThread().getName() + "交换后:" + data);
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
}
线程交换的线程数需要是二的倍数,要不然会出现线程阻塞的状态,也可以通过设置响应时间,一旦在规定时间内没有完成数据的交互,那么就抛出异常
线程池
Java中的线程池是运用场景最多的并发框架,几乎所有需要异步或并发执行任务的程序
都可以使用线程池。在开发过程中,合理地使用线程池能够带来3个好处。
第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
第二:提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
第三:提高线程的可管理性。线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,
还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控。但是,要做到合理利用线程池,必须对其实现原理了如指掌。
ThreadPoolExecutor
Executor框架的最顶层实现是ThreadPoolExecutor类,Executors工厂类中提供的newScheduledThreadPool、newFixedThreadPool、newCachedThreadPool方法其实也只是ThreadPoolExecutor的构造函数参数不同而已。通过传入不同的参数,就可以构造出适用于不同应用场景下的线程池,那么它的底层原理是怎样实现的呢,这篇就来介绍下ThreadPoolExecutor线程池的运行过程。
- corePoolSize: 核心池的大小。 当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中
- maximumPoolSize: 线程池最大线程数,它表示在线程池中最多能创建多少个线程;
- keepAliveTime: 表示线程没有任务执行时最多保持多久时间会终止。
- unit: 参数keepAliveTime的时间单位,有7种取值
newCachedThreadPool
创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。示例代码如下:
package com.yjc.juc;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class CachedThreadPoolDemo {
public static void main(String[] args) {
//创建一个可缓存线程如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程
//线程池为无限大,当执行第二个任务时第一个任务已经完成,会复用执行第一个任务的线程,而不用每次新建线程
ExecutorService executorService= Executors.newCachedThreadPool();
for (int i = 0; i <100 ; i++) {
final int count=i;
executorService.execute(()->{
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"--->i:"+count);
});
}
}
}
newFixedThreadPool
创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待
package com.yjc.juc;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class FixedThreadPoolDemo {
public static void main(String[] args) {
//创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待
ExecutorService executorService= Executors.newFixedThreadPool(5);
for (int i = 0; i <100 ; i++) {
final int count=i;
executorService.execute(()->{
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"--->i:"+count);
});
}
}
}
newScheduledThreadPool
package com.yjc.juc;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class ScheduledThreadPoolDemo {
public static void main(String[] args) {
//创建一个定长线程池,支持定时及周期性任务执行
ScheduledExecutorService executorService= Executors.newScheduledThreadPool(5);
for (int i = 0; i <100 ; i++) {
final int count=i;
executorService.schedule(()->{
System.out.println(Thread.currentThread().getName()+"--->i:"+count);
//延迟三秒开始执行
},3, TimeUnit.SECONDS);
}
}
}
延迟三秒才开始执行
newSingleThreadExecutor
创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。示例代码如下:
package com.yjc.juc;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class SingleThreadExecutorDemo {
public static void main(String[] args) {
//创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行
ExecutorService executorService= Executors.newSingleThreadExecutor();
for (int i = 0; i <100 ; i++) {
final int count=i;
executorService.execute(()->{
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"--->i:"+count);
});
}
}}
线程池原理剖析
提交一个任务到线程池中,线程池的处理流程如下:
1、判断线程池里的核心线程是否都在执行任务,如果不是(核心线程空闲或者还有核心线程没有被创建)则创建一个新的工作线程来执行任务。如果核心线程都在执行任务,则进入下个流程。
2、线程池判断工作队列是否已满,如果工作队列没有满,则将新提交的任务存储在这个工作队列里。如果工作队列满了,则进入下个流程。
3、判断线程池里的线程是否都处于工作状态,如果没有,则创建一个新的工作线程来执行任务。如果已经满了,则交给饱和策略来处理这个任务。
合理配置线程池
CPU密集型时,任务可以少配置线程数,大概和机器的cpu核数相当,这样可以使得每个线程都在执行任务
IO密集型时,大部分线程都阻塞,故需要多配置线程数,2*cpu核数