并发学习
并发工具类 - 分类
第一类:为了并发安全
第二类:管理线程 提高效率
第三类:线程协助
线程池的重要性
什么是池 ? 可以理解为计划经济 比如说我们的资源总量是有限的,就我电脑来说,我电脑有16个线程。所以,我就可以创建16个线程的线程池,我的任务可能很多,就依靠这16个线程来慢慢执行。然后,也不用创建的多,因为创建线程有多开销的。
所以:好处主要是有两个;第一个:可以复用我们的每一个资源
第二个:可以控制我们资源的总量
如果不用线程池,一个任务一个对应一个线程,那么1000个任务就需要创建1000个线程。对于java语言来说,一个线程对应操作系统的一个线程,这样的话,会给操作系统带来太大的开销。
为此:我们希望有固定数量的线程,来执行这1000个线程,这样就避免了反复创建并销毁线程所带来的开销问题。(如果用普通线程的话,执行完毕线程也就销毁了)
总结:线程池的好处
1.加快响应速度
2.合理利用cpu和内存
3.统一管理 (比如说:我不想执行刚刚那个任务了,如果我用线程就需要一个一个停,如果我使用线程池,停一次就好了)
线程池适合应用的场合
1.服务器接收到大量请求时,使用线程池技术是非常合适的,它可以大大减小线程的创建和销毁次数,提高服务器的工作效率。其实tomcat也是使用了线程池技术
2.在开发中,如果需要创建5个以上的线程,那么就可以使用线程池来管理。比如说,我们需要同时请求十个接口,并且对请求回来的结果做进一步处理,那么就很适合用线程池。
创建和停止线程池
线程池构造函数
比如说:corePoolSize = 5 此时,如果来了五个任务,系统就会创建5个线程;如果这5个任务结束了,线程池中会保存这5个任务,不会销毁。
maxPoolSize : 如果corePoolSize 核心线程都在处理,并且任务存储队列也已经满了,这时候它的扩展性就体现出来了。这时候,线程池会在核心线程基础下,进行扩展。最多会扩展到MaxPoolSize。
这里有个疑问?当队列满了maximumPoolSize没满的时候,创建的新线程是执行队列中的任务,还是最新的任务
keepAliveTime 存活时间
ThreadFactory:在线程池上,用来创建线程
线程池中默认创建线程可以找到,通过它可以查看是否守护线程,优先级等
workqueue:工作队列 还有,一个DelayedWorkQueue 这个是延时队列;
第一个是直接转换的,不能进行存储 个人理解就是 队列个数为0的有界队列
线程池应该手动创建还是自动创建
用Executors 自动创建 newFixedThreadPool 线程池测试:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class NewFixedThreadPoolTest {
public static void main(String[] args) {
//创建固定数为4的线程池
ExecutorService executorService = Executors.newFixedThreadPool(4);
//增加1000个数量的任务
for (int i = 0; i < 1000; i++) {
executorService.execute(new Task());
}
}
}
class Task implements Runnable{
@Override
public void run() {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
//打印当前线程名
System.out.println(Thread.currentThread().getName());
}
}
结果如下:可以看到,一直是四个线程在执行
那么为什么会这样呢?进行源码分析:
结果显而易见:这个固定线程数就是传入的nThreads 最大线程数也是这个,最大存活时间为0 因为它最大线程数和固定线程数都一样的,不存在线程池中线程数会大于固定线程数的情况。TimeUnit.MILLSECONDS这个代表的是,最大存活时间的单位为微秒。后面是LinkedBlockingQueue 无界队列
下面演示OOM出错的情况:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class NewFixedThreadPoolTest {
public static void main(String[] args) {
//创建固定数为1的线程池
ExecutorService executorService = Executors.newFixedThreadPool(1);
//希望它造成OOM,故加入的任务多一些
for (int i = 0; i < Integer.MAX_VALUE; i++) {
executorService.execute(new Task());
}
}
}
class Task implements Runnable{
@Override
public void run() {
try {
Thread.sleep(1000000000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//打印当前线程名
System.out.println(Thread.currentThread().getName());
}
}
oom错误如上;
这就是演示了用newFixedThreadPool可能出现的一种情况。所以,用固定数量的线程池还是容易发生错误的。
用Execotors创建SingleThreadExecutor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class SingleThreadExecutorTest {
public static void main(String[] args) {
ExecutorService executorService = Executors.newSingleThreadExecutor();
for (int i = 0; i < 1000; i++) {
executorService.execute(new Task());
}
}
}
Task任务写在上面:结果如下:
所以,只有一个线程,查看源代码:
这种线程池和刚刚的线程池差不多 只是把数量设置为1而已。也会导致同样的问题。
第三种线程池CachedThreadPool
可缓存线程池
特点:无界线程池,具有自动回收多余线程的功能
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class CachedThreadPool {
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < 1000; i++) {
executorService.execute(new Task());
}
}
}
结果如下:生成了1000个线程
根据源码进行分析:
核心线程数为0,最大线程数为Integer.MAX_VALUE这个代表的数是非常大的。存活时间为60s,队列是SynchronousQueue这个代表的是无法存储,(不需要队列进行存储,直接进行创建线程)直接放入线程池,直接交换。
所以,这个队列的特点就可以说清楚了,无界线程池,可以缓存。
这种线程池的问题如下:
而且一直创建的话也会浪费资源,就刚刚的那个情况。
ScheduleAtFixedRate 线程的学习
特点:支持定时及周期性任务执行的线程池
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class ScheduledThreadPoolTest {
public static void main(String[] args) {
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(10);
//延迟5s后开始运行线程
//scheduledExecutorService.schedule(new Task(),5, TimeUnit.SECONDS);
//1s后每隔3s运行一次线程
scheduledExecutorService.scheduleAtFixedRate(new Task(),1,3,TimeUnit.SECONDS);
}
}
经过以上的分析:创建线程池还是推荐手动创建比较好
下面 模拟一个创建线程池的方法:模拟队列的长度;并且设置拒绝策略为直接抛弃,不抛出错误;
package offer;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class Study {
public static void main(String[] args) {
// 设置有界的队列
ArrayBlockingQueue<Runnable> queue = new ArrayBlockingQueue<Runnable>(100);
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(5, 5, 0L, TimeUnit.SECONDS, queue);
// 设置拒绝策略,超过队列以后,不抛出错误,直接抛弃任务
threadPool.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy());
for (int i = 0; i < 200; i++) {
threadPool.submit(new Runnable() {
@Override
public void run() {
System.out.println(1);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
}
}
这样,就是超过了队列,也是不会抛出错误的;
线程池里线程数量设置多少比较合适:
第一个队列是无穷队列,第二个队列是空队列,不需要进行存储,第三个是延迟队列;
停止线程池的正确方法
1 shutdown:这个方法,会让线程池知道我们向让他停止任务,这时候,有新的任务时,它就不会再进行接收了。但是,他会将当前的还在执行的任务执行完。
isShutdown:判断线程池是否shutdown
isTerminated:判断线程池是否完全停止
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ShuntDown {
public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(20);
//准备加入1000个任务
for (int i = 0; i < 20; i++) {
executorService.execute(new ShutDownTask());
/*if(i == 10){
executorService.shutdown();
}*/
}
//已经加入1000个任务了,其他的都存在队列里面
Thread.sleep(1500);
//过了1.5s以后,准备关闭,这时候再有任务也不加了
//判断是否shutdown
System.out.println(executorService.isShutdown());
//返回是否完全停止
System.out.println(executorService.isTerminated());
executorService.shutdown();
//判断是否shutdown
System.out.println(executorService.isShutdown());
System.out.println(executorService.isTerminated());
Thread.sleep(2000);
System.out.println(executorService.isTerminated());
//此时再加入就会报错
executorService.execute(new ShutDownTask());
}
}
class ShutDownTask implements Runnable{
@Override
public void run() {
try {
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
结论如下:
awaitTermination:阻塞队列一段时间,看看队列是否执行完毕,如果执行完毕就返回true 如果时间过来队列还有线程在执行任务就返回false。一般用来做判断的。
shutdownNow:立刻关闭线程池,如果正在执行的任务,它们将会被中断,如果是队列中的任务,那么就直接返回,返回一个列表。
结论如下:
注意:一般来说在队列中的任务要给其他线程池来进行重新执行的。
第一种策略是直接抛出异常,第二种是丢弃,第三种是丢弃老的那个,第四种是随给我提交的我丢弃给谁自己。第四种相对来说会好一点。
相关内容:可以查看以下链接拒绝策略
比如说:主线程提交的任务,他会再提交给主线程来进行运行;
钩子方法,在每个任务执行前可以进行日志和统计
import jdk.nashorn.internal.ir.CallNode;
import java.util.concurrent.*;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
/**
* 这个方法演示每个任务执行前后都可以放钩子函数
*/
public class PauseableThreadPool extends ThreadPoolExecutor {
private boolean isPaused = false;
private final ReentrantLock lock = new ReentrantLock();
private Condition unpaused = lock.newCondition();
private void pause(){
//lock.lock();
isPaused = true;
}
public void resume(){
lock.lock();
try {
isPaused = false;
unpaused.signalAll();
}finally {
lock.unlock();
}
}
@Override
protected void beforeExecute(Thread t, Runnable r) {
super.beforeExecute(t, r);
lock.lock();
while(isPaused){
try {
unpaused.await();
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
public static void main(String[] args) {
PauseableThreadPool pauseableThreadPool = new PauseableThreadPool(10, 20, 10L, TimeUnit.SECONDS, new LinkedBlockingDeque<>());
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("我被执行");
try{
Thread.sleep(10);
}catch (InterruptedException e){
e.printStackTrace();
}
}
};
for (int i = 0; i < 100; i++) {
pauseableThreadPool.execute(runnable);
}
try {
Thread.sleep(100);
pauseableThreadPool.pause();
System.out.println("线程池被暂停");
Thread.sleep(100);
pauseableThreadPool.resume();
System.out.println("线程池重新开始执行");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public PauseableThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
}
public PauseableThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory);
}
public PauseableThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, handler);
}
public PauseableThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler);
}
}
结果如下:
这里还是有一点问题的,到时候回来再看看。
查看线程池源码
Executor:是线程池的最高级父接口,其内部只定义了一个execute方法
ExecutorService:继承Executor接口的接口;它有线程池基本上的方法;故Executors方法创建出线程池时,也是用它来进行接收的。
至于,ThreadPoolEcecutor它和ExecutorService一样就是线程池
比如说创建newCachedThreadPool就是调用他。
线程池实现线程任务复用的原理
点击进入ThreadPoolExecutor线程池中的runWorker执行方法,这个方法上面会描述他的作用
这里写着只要能得到任务就会一直执行。所以,线程池中会一直执行任务。
下面进行分析ThreadPoolExecutor方法中的execute方法
该方法会介绍他的流程
第一:如果线程池中运行的线程数少于线程池的核心数,那么就会增加。
第二:如果线程池中的线程数已经满了,都在跑就会增加到队列中
第三:如果线程池中队列满了,核心线程数也满了。那么就增加看看最大线程数有没有满,如果没满,可以增加返回true就可以增加(创建最大线程数来进行运行任务),如果满了返回false就拒绝任务了。