关于线程和线程池的学习,我们可以从以下几个方面入手:
第一,什么是线程,线程和进程的区别是什么
第二,线程中的基本概念,线程的生命周期
第三,单线程和多线程
第四,为什么要使用线程池
第五,线程池的原理解析
第六,常见的几种线程池的特点以及各自的应用场景
线程,程序执行流的最小执行单位,是行程中的实际运作单位,经常容易和进程这个概念混淆。那么,线程和进程究竟有什么区别呢?首先,进程是一个动态的过程,是一个活动的实体。简单来说,一个应用程序的运行就可以被看做是一个进程,而线程,是运行中的实际的任务执行者。可以说,进程中包含了多个可以同时运行的线程。
线程的生命周期,线程的生命周期可以利用以下的图解来更好的理解:
第一步,是用new Thread()的方法新建一个线程,在线程创建完成之后,线程就进入了就绪(Runnable)状态,此时创建出来的线程进入抢占CPU资源的状态,当线程抢到了CPU的执行权之后,线程就进入了运行状态(Running),当该线程的任务执行完成之后或者是非常态的调用的stop()方法之后,线程就进入了死亡状态。而我们在图解中可以看出,线程还具有一个则色的过程,这是怎么回事呢?当面对以下几种情况的时候,容易造成线程阻塞,第一种,当线程主动调用了sleep()方法时,线程会进入则阻塞状态,除此之外,当线程中主动调用了阻塞时的IO方法时,这个方法有一个返回参数,当参数返回之前,线程也会进入阻塞状态,还有一种情况,当线程进入正在等待某个通知时,会进入阻塞状态。那么,为什么会有阻塞状态出现呢?我们都知道,CPU的资源是十分宝贵的,所以,当线程正在进行某种不确定时长的任务时,Java就会收回CPU的执行权,从而合理应用CPU的资源。我们根据图可以看出,线程在阻塞过程结束之后,会重新进入就绪状态,重新抢夺CPU资源。这时候,我们可能会产生一个疑问,如何跳出阻塞过程呢?又以上几种可能造成线程阻塞的情况来看,都是存在一个时间限制的,当sleep()方法的睡眠时长过去后,线程就自动跳出了阻塞状态,第二种则是在返回了一个参数之后,在获取到了等待的通知时,就自动跳出了线程的阻塞过程
单线程,顾名思义即是只有一条线程在执行任务,这种情况在我们日常的工作学习中很少遇到,所以我们只是简单做一下了解
多线程,创建多条线程同时执行任务,这种方式在我们的日常生活中比较常见。但是,在多线程的使用过程中,还有许多需要我们了解的概念。比如,在理解上并行和并发的区别,以及在实际应用的过程中多线程的安全问题,对此,我们需要进行详细的了解。
并行和并发:在我们看来,都是可以同时执行多种任务,那么,到底他们二者有什么区别呢?
并发,从宏观方面来说,并发就是同时进行多种时间,实际上,这几种时间,并不是同时进行的,而是交替进行的,而由于CPU的运算速度非常的快,会造成我们的一种错觉,就是在同一时间内进行了多种事情
而并行,则是真正意义上的同时进行多种事情。这种只可以在多核CPU的基础下完成。
还有就是多线程的安全问题?为什么会造成多线程的安全问题呢?我们可以想象一下,如果多个线程同时执行一个任务,name意味着他们共享同一种资源,由于线程CPU的资源不一定可以被谁抢占到,这是,第一条线程先抢占到CPU资源,他刚刚进行了第一次操作,而此时第二条线程抢占到了CPU的资源,name,共享资源还来不及发生变化,就同时有两条数据使用了同一条资源,具体请参考多线程买票问题。这个问题我们应该如何解决那?
有造成问题的原因我们可以看出,这个问题主要的矛盾在于,CPU的使用权抢占和资源的共享发生了冲突,解决时,我们只需要让一条线程战歌了CPU的资源时,阻止第二条线程同时抢占CPU的执行权,在代码中,我们只需要在方法中使用同步代码块即可。在这里,同步代码块不多进行赘述,可以自行了解。
1. 创建/销毁线程伴随着系统开销,过于频繁的创建/销毁线程,会很大程度上影响处理效率,例如:
记创建线程消耗时间 T1,执行任务消耗时间 T2,销毁线程消耗时间 T3
如果 T1+T3>T2,那么是不是说开启一个线程来执行这个任务太不划算了!
正好,线程池缓存线程,可用已有的闲置线程来执行新任务,避免了 T1+T3 带来的系统开销
2. 线程并发数量过多,抢占系统资源从而导致阻塞
我们知道线程能共享系统资源,如果同时执行的线程过多,就有可能导致系统资源不足而产生阻塞的情况
运用线程池能有效的控制线程最大并发数,避免以上的问题
3. 对线程进行一些简单的管理
比如:延时执行、定时循环执行的策略等
运用线程池都能进行很好的实现
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
这是其中最重要的一个构造方法,这个方法决定了创建出来的线程池的各种属性,下面依靠一张图来更好的理解线程池和这几个参数:
corePoolSize:线程池核心线程数量
maximumPoolSize:线程池最大线程数量
keepAliverTime:当活跃线程数大于核心线程数时,空闲的多余线程最大存活时间
unit:存活时间的单位
workQueue:存放任务的队列
handler:超出线程范围和队列容量的任务的处理程序
threadFactory - 执行程序创建新线程时使用的工厂。
核心线程:
线程池新建线程的时候,如果当前线程总数小于 corePoolSize,则新建的是核心线程,如果超过 corePoolSize,则新建的是非核心线程
核心线程默认情况下会一直存活在线程池中,即使这个核心线程啥也不干(闲置状态)。
如果指定 ThreadPoolExecutor 的 allowCoreThreadTimeOut 这个属性为 true,那么核心线程如果不干活(闲置状态)的话,超过一定时间(时长下面参数决定),就会被销毁掉
很好理解吧,正常情况下你不干活我也养你,因为我总有用到你的时候,但有时候特殊情况(比如我自己都养不起了),那你不干活我就要把你干掉了
TimeUnit unit
keepAliveTime 的单位,TimeUnit 是一个枚举类型,其包括:
NANOSECONDS :1微毫秒 = 1微秒 / 1000
MICROSECONDS :1微秒 = 1毫秒 / 1000
MILLISECONDS :1毫秒 = 1秒 /1000
SECONDS :秒
MINUTES :分
HOURS :小时
DAYS : 天
BlockingQueue workQueue
该线程池中的任务队列:维护着等待执行的Runnable对象
当所有的核心线程都在干活时,新添加的任务会被添加到这个队列中等待处理,如果队列满了,则新建非核心线程执行任务
常用的 workQueue 类型:
SynchronousQueue:这个队列接收到任务的时候,会直接提交给线程处理,而不保留它,如果所有线程都在工作怎么办?那就新建一个线程来处理这个任务!所以为了保证不出现<线程数达到了 maximumPoolSize 而不能新建线程>的错误,使用这个类型队列的时候,maximumPoolSize 一般指定成 Integer.MAX_VALUE,即无限大
LinkedBlockingQueue:这个队列接收到任务的时候,如果当前线程数小于核心线程数,则新建线程(核心线程)处理任务;如果当前线程数等于核心线程数,则进入队列等待。由于这个队列没有最大值限制,即所有超过核心线程数的任务都将被添加到队列中,这也就导致了 maximumPoolSize 的设定失效,因为总线程数永远不会超过 corePoolSize
ArrayBlockingQueue:可以限定队列的长度,接收到任务的时候,如果没有达到 corePoolSize 的值,则新建线程(核心线程)执行任务,如果达到了,则入队等候,如果队列已满,则新建线程(非核心线程)执行任务,又如果总线程数到了 maximumPoolSize,并且队列也满了,则发生错误
DelayQueue:队列内元素必须实现 Delayed 接口,这就意味着你传进去的任务必须先实现 Delayed 接口。这个队列接收到任务时,首先先入队,只有达到了指定的延时时间,才会执行任务
那说了这么多,你可能有疑惑,我知道new一个ThreadPoolExecutor,大概知道各个参数是干嘛的,可是我new完了,怎么向线程池提交一个要执行的任务啊?
通过ThreadPoolExecutor.execute(Runnable command)
方法即可向线程池内添加一个任务
上面介绍参数的时候其实已经说到了ThreadPoolExecutor执行的策略,这里给总结一下,当一个任务被添加进线程池时:
线程池的执行流程又是怎样的呢?
有图我们可以看出,任务进来时,首先执行判断,判断核心线程是否处于空闲状态,如果不是,核心线程就先就执行任务,如果核心线程已满,则判断任务队列是否有地方存放该任务,若果有,就将任务保存在任务队列中,等待执行,如果满了,在判断最大可容纳的线程数,如果没有超出这个数量,就开创非核心线程执行任务,如果超出了,就调用handler实现拒绝策略。
handler的拒绝策略:
有四种:第一种AbortPolicy:不执行新任务,直接抛出异常,提示线程池已满
第二种DisCardPolicy:不执行新任务,也不抛出异常
第三种DisCardOldSetPolicy:将消息队列中的第一个任务替换为当前新进来的任务执行
第四种CallerRunsPolicy:直接调用execute来执行当前任务
CachedThreadPool:(推荐使用)可缓存的线程池,该线程池中没有核心线程,非核心线程的数量为Integer.max_value,就是无限大,当有需要时创建线程来执行任务,没有需要时回收线程,适用于耗时少,任务量大的情况。
SecudleThreadPool:周期性执行任务的线程池,按照某种特定的计划执行线程中的任务,有核心线程,但也有非核心线程,非核心线程的大小也为无限大。适用于执行周期性的任务。
SingleThreadPool:只有一条线程来执行任务,适用于有顺序的任务的应用场景。
FixedThreadPool:定长的线程池,有核心线程,核心线程的即为最大的线程数量,没有非核心线程
1,SingleThreadPool 使用案例
package com.test.threadpool;
public class RunThread extends Thread {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"正在执行.....");
}
}
package com.test.threadpool;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class singleThreadExecutorTest {
public static void main(String[] args){
ExecutorService pool = Executors.newSingleThreadExecutor();
Thread t1=new RunThread();
Thread t2=new RunThread();
Thread t3=new RunThread();
Thread t4=new RunThread();
Thread t5=new RunThread();
t1.setName("t1");
t2.setName("t2");
t3.setName("t3");
t4.setName("t4");
t5.setName("t5");
pool.execute(t1);
pool.execute(t2);
pool.execute(t3);
pool.execute(t4);
pool.execute(t5);
pool.shutdown();
}
}
2,newFixedThreadPool 使用案例
package com.test.threadpool;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class singleThreadExecutorTest {
public static void main(String[] args){
ExecutorService pool = Executors.newFixedThreadPool(2);
Thread t1=new RunThread();
Thread t2=new RunThread();
Thread t3=new RunThread();
Thread t4=new RunThread();
Thread t5=new RunThread();
t1.setName("t1");
t2.setName("t2");
t3.setName("t3");
t4.setName("t4");
t5.setName("t5");
pool.execute(t1);
pool.execute(t2);
pool.execute(t3);
pool.execute(t4);
pool.execute(t5);
pool.shutdown();
}
}
3,newCachedThreadPool实例
package com.test.threadpool;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class singleThreadExecutorTest {
public static void main(String[] args){
ExecutorService pool = Executors.newCachedThreadPool();
Thread t1=new RunThread();
Thread t2=new RunThread();
Thread t3=new RunThread();
Thread t4=new RunThread();
Thread t5=new RunThread();
t1.setName("t1");
t2.setName("t2");
t3.setName("t3");
t4.setName("t4");
t5.setName("t5");
pool.execute(t1);
pool.execute(t2);
pool.execute(t3);
pool.execute(t4);
pool.execute(t5);
pool.shutdown();
}
}
4,ScheduledThreadPoolExecutor实例
public class scheduledThreadExecutorTest{
public static void main(String[] args) {
// TODO Auto-generated method stub
ScheduledThreadPoolExecutor exec =new ScheduledThreadPoolExecutor(1);
exec.scheduleAtFixedRate(new Runnable(){//每隔一段时间就触发异常
@Override
public void run() {
// TODO Auto-generated method stub
//throw new RuntimeException();
System.out.println("===================");
}}, 1000, 5000, TimeUnit.MILLISECONDS);
exec.scheduleAtFixedRate(new Runnable(){//每隔一段时间打印系统时间,证明两者是互不影响的
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println(System.nanoTime());
}}, 1000, 2000, TimeUnit.MILLISECONDS);
}
}
结果:
=================== 23119318857491 23121319071841 23123319007891 =================== 23125318176937 23127318190359 =================== 23129318176148 23131318344312 23133318465896 =================== 23135319645812
参考:
https://www.jianshu.com/p/210eab345423