Java多线程编程(五)——线程池
(2)newFixedThreadPool(int nThreads)
(3)手动创建线程池对象-ThreadPoolExecutor
(1)ThreadPoolExecutor.AbortPolicy:
(2)ThreadPoolExecutor.DiscardPolicy:
(3)ThreadPoolExecutor.DiscardOldestPolicy:
(4)ThreadPoolExecutor.CallerRunsPolicy:
线程的状态
概述
在学习操作系统这一本书时,有“五状态模型” 与 “七状态模型”
这里我们所指的是JVM的六种线程状态
JVM与操作系统线程状态的区别
Java线程在Windows平台和Linux平台上的实现方式,是内核线程的实现方式,这样的方式实现的线程,是直接由操作系统内核支持的,内核通过操纵调度器来实现线程调度。也就是说,Java的线程跟操作系统的内核之间存在映射关系,这种映射可以是一对一映射,也可以是一对多或多对多映射。所以说,Java中任一给定时间的线程状态是不反映任何操作系统进程状态的虚拟机状态。
如下图所示:
操作系统中的线程状态
JVM中的线程状态
线程的六种状态(没有运行态)
Java中的线程状态被定义在了java.lang.Thread.State枚举类中,State枚举类的源码如下:
public class Thread {
public enum State {
/* 新建 */
NEW ,
/* 可运行状态 */
RUNNABLE ,
/* 阻塞状态 */
BLOCKED ,
/* 无限等待状态 */
WAITING ,
/* 计时等待 */
TIMED_WAITING ,
/* 终止 */
TERMINATED;
}
// 获取当前线程的状态
public State getState() {
return jdk.internal.misc.VM.toThreadState(threadStatus);
}
}
线程状态 | 具体含义 |
---|---|
NEW | 一个尚未启动的线程的状态。也称之为初始状态、开始状态。线程刚被创建,但是并未启动。还没调用start方法。MyThread t = new MyThread()只有线程象,没有线程特征。 |
RUNNABLE | 当我们调用线程对象的start方法,那么此时线程对象进入了RUNNABLE状态。那么此时才是真正的在JVM进程中创建了一个线程,线程一经启动并不是立即得到执行,线程的运行与否要听令与CPU的调度,那么我们把这个中间状态称之为可执行状态(RUNNABLE)也就是说它具备执行的资格,但是并没有真正的执行起来而是在等待CPU的度。 |
BLOCKED | 当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked状态;当该线程持有锁时,该线程将变成Runnable状态。 |
WAITING | 一个正在等待的线程的状态。也称之为等待状态。造成线程等待的原因有两种,分别是调用Object.wait()、join()方法。处于等待状态的线程,正在等待其他线程去执行一个特定的操作。例如:因为wait()而等待的线程正在等待另一个线程去调用notify()或notifyAll();一个因为join()而等待的线程正在等待另一个线程结束。 |
TIMED_WAITING | 一个在限定时间内等待的线程的状态。也称之为限时等待状态。造成线程限时等待状态的原因有三种,分别是:Thread.sleep(long),Object.wait(long)、join(long)。 |
TERMINATED | 一个完全运行完成的线程的状态。也称之为终止状态、结束状态 |
线程池
概述:
提到池,大家应该能想到的就是水池。水池就是一个容器,在该容器中存储了很多的水。那么什么是线程池呢?线程池也是可以看做成一个池子,在该池子中存储很多个线程。
系统创建一个线程的成本是比较高的,因为它涉及到与操作系统交互,当程序中需要创建大量生存期很短暂的线程时,频繁的创建和销毁线程对系统的资源消耗有可能大于业务处理是对系统资源的消耗,这样就有点"舍本逐末"了。针对这一种情况,为了提高性能,我们就可以采用线程池。线程池在启动的时,会创建大量空闲线程,当我们向线程池提交任务的时,线程池就会启动一个线程来执行该任务。等待任务执行完毕以后,线程并不会死亡,而是再次返回到线程池中称为空闲状态。等待下一次任务的执行。
线程池的设计思路 :
准备一个任务容器
一次性启动多个(2个)消费者线程
刚开始任务容器是空的,所以线程都在wait
直到一个外部线程向这个任务容器中扔了一个"任务",就会有一个消费者线程被唤醒
这个消费者线程取出"任务",并且执行这个任务,执行完毕后,继续等待下一次任务的到来
Java线程池的用法
Java提供了两个自动创建线程池的静态方法
(1)static ExecutorService newCachedThreadPool() 创建一个默认的线程池
(2)static newFixedThreadPool(int nThreads) 创建一个指定最多线程数量的线程池
(1)newCachedThreadPool()
使用案例如下:
Executors:可以帮助我们创建线程池对象
ExecutorService:可以帮助我们控制线程池
public class MyThreadPoolDemo {
public static void main(String[] args) throws InterruptedException {
ExecutorService es = Executors.newCachedThreadPool();
es.submit(()->{
System.out.println(Thread.currentThread().getName() + "执行");
});
//Thread.sleep(500);
es.submit(()->{
System.out.println(Thread.currentThread().getName() + "执行");
});
es.shutdown();
}
}
运行结果如下:
注意:
上述代码第7行处有一个注释,若取消注释会怎么样呢?
我们知道该方法创建线程是由线程池控制的,当线程池中没有空闲的线程时,而此时又有一个submit方法提交任务申请时,线程池才会创建线程。
取消第7行的注释,即执行Thread.sleep(500); 此时第一个任务已经执行完成,线程被归还到线程池中,所以当第二个任务到来时,不需要创建新的线程。
(2)newFixedThreadPool(int nThreads)
使用Executors中所提供的静态方法来创建线程池
创建一个指定最多线程数量的线程池
public class MyThreadPoolDemo2 {
public static void main(String[] args) {
//参数不是初始值而是最大值
ExecutorService es = Executors.newFixedThreadPool(10);
es.submit(()->{
System.out.println(Thread.currentThread().getName() + "在执行了");
});
es.submit(()->{
System.out.println(Thread.currentThread().getName() + "在执行了");
});
es.shutdown();
}
}
(3)手动创建线程池对象-ThreadPoolExecutor
我们可以先看看前面两种Java自动创建线程池方法即newCachedThreadPool,newFixedThreadPool(int nThreads) 的源码
newCachedThreadPool
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, 2147483647, 60L, TimeUnit.SECONDS, new SynchronousQueue());
}
newFixedThreadPool(int nThreads)
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue());
}
显然,里面都有涉及到ThreadPoolExecutor这个类,我们可以用API查看一下这个构造方法,这里列举最复杂的一个构造方法:
显然,ThreadPoolExecutor构造方法中有7个参数!
参数一:核心线程数量
参数二:线程池中最大线程数量(核心数 + 临时数)
参数三:空闲线程(临时线程)最大存活时间
参数四:时间单位
参数五:任务队列(阻塞队列)
参数六:创建线程工厂(创建方式)
参数七:要执行任务过多时的解决方案(默认:拒绝服务)
Coding:
import java.util.concurrent.*;
public class Demo {
public static void main(String[] args) {
//核心线程数2个,最大线程数5个,时间2s,时间单位-秒,
阻塞队列10,默认创建,拒绝策略
ThreadPoolExecutor pool = new ThreadPoolExecutor(2, 5,
2, TimeUnit.SECONDS, new ArrayBlockingQueue<>(10),
Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
pool.submit(new MyRunnable());
pool.submit(new MyRunnable());
pool.shutdown();
}
}
public class MyRunnable implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "执行了");
}
}
运行结果:
pool-1-thread-1执行了
pool-1-thread-2执行了
参数详解
第一、二个参数——核心线程数量、最大线程数
核心线程数量是固定的,就算是长时间没有任务,它也不会被JVM释放
最大线程数 = 核心线程数 + 临时线程数
临时线程是根据需要(任务队列的多少)申请的,若长时间没有使用,会被释放,资源归还给线程池。
第三、四个参数——空闲线程最大存活时间、时间单位
这个顾名思义,很好理解,就是指临时线程的“手”上没有任务的时候还可以活多久。
这是一个枚举类TimeUnit
使用案例:使用 秒-TimeUnit.SECONDS
第五个参数——任务队列
让任务在队列中等着,等有线程空闲了,再从这个队列中获取线程。
第六个参数——创建线程工厂
这个比较复杂,但是初学阶段不用深究,我们可以在IDEA中,选中参数-Executors.defaultThreadFactory(),ctrl+B
public static ThreadFactory defaultThreadFactory() {
return new Executors.DefaultThreadFactory();
}
再查看 DefaultThreadFactory(),ctrl + B
从newThread方法中可以看出,这里也是用之前学的Thread t = new Thread(...),来创建线程,只不过这里设置了一些参数,守护线程,优先级等等......
第七个参数--要执行任务过多时的解决方案
(1)什么时候拒绝???
当线程池的最大容量为5,任务队列容量为3时,若有10个任务同时到来,就会发生拒绝。
故 当提交任务数 > 池子中最大线程数量 + 队列容量 就会发生拒绝!
(2)怎么拒绝???
Java提供了4种拒绝方式:
ThreadPoolExecutor.AbortPolicy: 丢弃任务并抛出RejectedExecutionException异常。是默认的策略。
ThreadPoolExecutor.DiscardPolicy: 直接丢弃任务,但是不抛出异常 这是不推荐的做法。
ThreadPoolExecutor.DiscardOldestPolicy: 抛弃队列中等待最久的任务 然后把当前任务加入队列中。
ThreadPoolExecutor.CallerRunsPolicy: 调用任务的run()方法绕过线程池直接执行。
执行任务过多时4种拒绝策略
(1)ThreadPoolExecutor.AbortPolicy:
import java.util.concurrent.*;
//ThreadPoolExecutor.AbortPolicy
public class ThreadPoolExecutorDemo01 {
public static void main(String[] args) {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
1,
3,
20,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(1),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);
// 提交5个任务,而该线程池最多可以处理4个任务,
// 当我们使用AbortPolicy这个任务处理策略的时候,就会抛出异常
//5个任务
for (int i = 0; i < 5; i++) {
threadPoolExecutor.submit(() -> {
System.out.println(Thread.currentThread().getName()
+ "---->执行任务" );
});
}
threadPoolExecutor.shutdown();
}
}
上述案例最多可以同时处理4个任务,但是却同时分配了5个任务给他,所以运行抛出RejectedExecutionException异常,并且丢弃最后一个任务。
(2)ThreadPoolExecutor.DiscardPolicy:
import java.util.concurrent.*;
public class ThreadPoolExecutorDemo02 {
public static void main(String[] args) {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
1,
3,
20,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(1),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.DiscardPolicy()
);
for (int i = 0; i < 5; i++) {
threadPoolExecutor.submit(() -> {
System.out.println(Thread.currentThread().getName()
+ "-->执行任务" );
});
}
threadPoolExecutor.shutdown();
}
}
同第一个类似,同样会丢弃多余的任务,但是不会抛出异常。这样子是十分危险的,所以不推荐使用。
(3)ThreadPoolExecutor.DiscardOldestPolicy:
import java.util.concurrent.*;
public class ThreadPoolExecutorDemo03 {
public static void main(String[] args) {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
1,
3,
20,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(1),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.DiscardOldestPolicy()
);
for (int x = 0; x < 15; x++) {
final int y = x ;
threadPoolExecutor.submit(() -> {
System.out.println(Thread.currentThread().getName() + "---->> 执行了任务" + y);
});
}
threadPoolExecutor.shutdown();
}
}
显然他会丢弃之前的任务,执行最后来的任务。
(4)ThreadPoolExecutor.CallerRunsPolicy:
public class ThreadPoolExecutorDemo04 {
public static void main(String[] args) {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
1,
3,
20,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(1),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.CallerRunsPolicy()
);
for (int i = 0; i < 10; i++) {
int x = i;
threadPoolExecutor.submit(() -> {
System.out.println(Thread.currentThread().getName()
+ "-->执行任务" + x);
});
}
threadPoolExecutor.shutdown();
}
}
这里看到每一步都执行了,当任务过多时,调用任务的run()方法绕过线程池直接执行(请main主线程帮忙一起执行)。
阅读下章可见
Java多线程编程(六)——原子性https://blog.csdn.net/weixin_43715214/article/details/122438253