多线程
一、程序、进程、线程
1.1 程序
使用某种计算机语言编写的,为了完成某些功能的静态代码(app)。
例如:QQ、微信、idea、、、、、、、
1.2 进程
进行中的应用程序,计算机分配资源的最小单位
只有程序在运行状态下,才称之为进程
(1)程序执行的动态过程。
(2)关闭程序,进程结束。
(3)进程生命周期:
开始运行一直到关闭或停止运行。
一个程序可以同时运行多次,每次运行都是一个进程。
1.3 线程
一个程序可以同时运行多个任务功能。
每一个独立运行的任务流程,称为一个线程。
线程的包含在进程之中的,属于计算机运算执行的最小单位
一个进程至少包含一个线程,否则无法运行
二、多线程使用场景
2.1 多线程特点
多个任务流程可以同时执行,互不影响。
2.2 并发和并行
所有的程序都运行在CPU上。
(1)并发
早期CPU是单核的,只能同时运行一个线程。
如果要同时运行多个线程,多个线程在CPU上交替执行。
我们将多个线程在CPU上交替执行的过程,称为并发执行。
并发:是指同时发生,轮流交替执行
(2)并行
我们现在的计算都是多核,可以同时运行多个线程。
每个核心上都可以运行一个线程。
我们将多个核心同时运行多个线程的过程,称为并行执行。
并行:真正意义上的同时执行
(3)并发和并行
我们计算机现在虽然是多核的,但是并行和并发都是存在的。
比如:
我们有16个线程同时执行,但是核心只有4个。
同时并行的是4个线程。
但是每个核心上有多个线程交替并发执行。
2.3 使用场景
当程序需要多个任务流程同时执行时。
2.4 多线程的优点
(1)减少了阻塞,程序的执行效率高。
(2)提高了CPU的利用率。
(3)增强了用户体验。
(4)改善了程序的结构。
三、线程分类
3.1 用户线程
我们程序的功能流程。
主线程:我们程序执行的入口,对应的就是主方法。
我们之前写的程序都是单线程的,只有一个主线程。
一个进程,有且仅有一个主线程。
3.2 守护线程
守护线程,也叫后台线程。
比如:
jvm的垃圾回收线程。
jvm的异常处理线程。
四、多线程实现方式
4.1 继承Thread类
1.继承Thread类
2.重写run方法
3.创建线程对象
4.启动线程调用start方法
开启线程–>在线程中调用run()方法—>run执行---->run执行完毕,销毁线程
package classlib.Day0310;
/**
* @author 35125
*/
public class MyThread extends Thread{
@Override
public void run() {
//run()方法线程体
System.out.println("start---->");
for (int i = 0; i < 100; i++) {
System.out.println("线程"+i);
}
System.out.println("end");
}
public static void main(String[] args) {
//main线程 主线程
//创建线程对象mt1
MyThread mt1 = new MyThread();
//调用start()方法开启线程 同时(交替)执行
mt1.start();
for (int i = 0; i < 20; i++) {
System.out.println("main线程"+i);
}
}
}
4.2 实现Runnable接口
1.实现Runnable接口
2.重写run方法
3.创建线程对象
4.开启新线程(start方法)
Runnable实现类可作为参数构造Thread实例
package classlib.Day0311;
/**
* @author 35125
*/
public class TestRunnable implements Runnable{
@Override
public void run() {
System.out.println("T1 Start");
for (int i = 0; i < 100; i++) {
System.out.println("线程1--->"+i);
}
}
public static void main(String[] args) {
//创建线程
TestRunnable tr = new TestRunnable();
//开启线程
Thread tr2 = new Thread(tr);
tr2.start();
for (int i = 0; i < 50; i++) {
System.out.println("main线程-->"+i);
}
}
}
4.3 两种创建方式的区别
继承Thread类
编写简单,可直接操作线程
适用于单继承
实现Runnable接口
避免单继承局限性
便于共享资源
推荐使用实现Runnable接口方式创建线程
4.4 调用start和run方法的区别
因为线程执行的决定权不在线程,而在CPU,所以线程只能向CPU发出准备就绪信号,等待被执行
所以调用start方法,就是发出信号,等待CPU开启新的线程
调用run方法,不会开启新的线程
调用start开启新线程,调用run不会开启新线程
五、线程生命周期
5.1 生命周期
Java线程具有五种基本状态
(1)新建状态(New):当线程对象对创建后,即进入了新建状态,如:Thread t = new MyThread();
(2)(可运行)就绪状态(Runnable):当调用线程对象的start()方法(t.start();),线程即进入就绪状态。处于就绪状态的线程,只是说明此线程已经做好了准备,随时等待CPU调度执行,并不是说执行了t.start()此线程立即就会执行;
(3)运行状态(Running):当CPU开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态。注:就绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中;
(4)阻塞状态(Blocked):处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才 有机会再次被CPU调用以进入到运行状态。根据阻塞产生的原因不同,阻塞状态又可以分为三种:
1.等待阻塞:运行状态中的线程执行wait()方法,使本线程进入到等待阻塞状态;
2.同步阻塞 – 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态;
3.其他阻塞 – 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
(5)死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
举个通俗一点的例子来解释上面五种状态,比如上厕所:
你平时去商城上厕所,准备去上厕所就是新建状态(new),上厕所要排队,排队就是就绪状态(Runnable),有坑位了,轮到你了,拉屎就是运行状态(Running),你拉完屎发现没有手纸,要等待别人给你送纸过来,这个状态就是阻塞(Blocked),等你上完厕所出来,上厕所这件事情结束了就是死亡状态了。
注意:便秘也是阻塞状态,你便秘太久了,别人等不及了,把你赶走,这个就是挂起,还有一种情况,你便秘了,别人等不及了,跟你说你先出去酝酿一下,5分钟后再过来拉屎,这就是睡眠。
5.2 CPU执行原理
(1)所有可运行的线程进入队列中等待执行。
(2)CPU每次会选择其中一个线程来执行。
(3)CPU会为本次执行该线程分配一个时间片。
(4)如果时间片到期,CPU会将该线程放入队列中,该线程继续等待执行。
(5)如果时间片没有到期,运行时出现阻塞,CPU会将该线程放入阻塞队列。
(6)如果时间片没有到期,线程代码执行完毕,该线程被销毁。
注意:
(1)CPU每次选择哪个线程来执行,我们不知道,随机。
(2)CPU每次分配的时间片大小,我们不知道,随机。
因此,多线程程序每次执行结果都可能不一样!!!!
六、线程方法
6.1 设置和获取名称
Thread tr2 = new Thread(tr);
System.out.println(tr2.getName());
tr2.setName("线程2");
System.out.println(tr2.getName());
tr2.start();
6.2 设置和获取优先级
//线程优先级默认是5 优先级越高 先被CPU执行的概率就越高
//范围是1-10
tr2.setPriority(7);
System.out.println(tr2.getPriority());
6.3 线程阻塞:sleep()方法
当前线程退出CPU,进入阻塞队列,n毫秒后结束阻塞,进入CPU队列中等待执行。
@Override
public void run() {
System.out.println("T1 Start");
for (int i = 0; i < 100; i++) {
if (i == 55){
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("线程1--->"+i);
}
6.4 线程阻塞:join()方法
在一个线程中,调用了另一个线程的join方法。
当前线程阻塞,直到另一个线程全部代码运行完毕,结束阻塞,才能继续执行。
package classlib.Day0311;
/**
* @author 35125
*/
public class MyThread2 implements Runnable{
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println("线程Thread2---->"+i);
}
System.out.println("线程2end");
}
}
package classlib.Day0311;
/**
* @author 35125
*/
public class MyThread3 implements Runnable{
private Thread t2;
public MyThread3(Thread t2) {
this.t2 = t2;
}
@Override
public void run() {
try {
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < 30; i++) {
System.out.println("线程3---->"+i);
}
System.out.println("线程3end");
}
}
package classlib.Day0311;
/**
* 测试join方法
* @author 35125
*/
public class TestMain {
public static void main(String[] args) {
Thread t2 = new Thread(new MyThread2());
//线程3
Thread t3 = new Thread(new MyThread3(t2));
//线程2
t2.start();
t3.start();
}
}
七、多线程JVM内存分配
7.1 程序计数器
一块较小的内存空间,它的作用可以看做是当前线程所执行的字节码的行号指示器。
“线程私有”的内存。
7.2 Java虚拟机栈
用于存储局部变量表、操作栈、动态链接、方法出口等信息。
“线程私有”的内存。
7.3 本地方法栈
本地方法栈(Native Method Stacks)与虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行Java 方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的native 方法服务。
7.4 Java堆
各个线程共享的内存区域。
对于大多数应用来说,Java 堆(Java Heap)是Java 虚拟机所管理的内存中最大的一块。Java 堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。
7.5 方法区
方法区(Method Area)与Java 堆一样,是各个线程共享的内存区域。
它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
7.6 运行时常量池
运行时常量池(Runtime Constant Pool)是方法区的一部分。Class 文件中除了有类的版本、字段、方法、接口等描述等信息外,还有一项信息是常量池(Constant Pool Table),用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。
多线程内存分配:
多个线程共享堆内存和方法区,但是计数器和栈是每个线程私有的。
八、线程同步
8.1 多线程同步时,访问同一对象导致数据不一致的问题
多个线程操作同一个资源对象,会出现数据紊乱。
案例:三个人同时买票,总票数为100张。
package classlib.Day0311;
/**
* @author 35125
*/
public class TestTicket implements Runnable{
private int tickets = 100;
@Override
public void run() {
//买票
while (true){
if (tickets > 0){
System.out.println(Thread.currentThread().getName()+"--->买到第"+tickets--+"号票");
}
}
}
public static void main(String[] args) {
TestTicket tt = new TestTicket();
new Thread(tt,"xiaobai").start();
new Thread(tt,"xiaohuang").start();
new Thread(tt,"laoli").start();
}
}
8.2 线程同步
(1)同步块
将一块代码锁起来,每次只能一个线程执行。
一个线程执行完毕同步块,下一个线程才能执行。
原理:
对象锁:一个对象对应一把锁。
通过该对象锁,锁住某块代码。
哪个线程获取到这个对象锁,哪个线程就可以执行同步块中的代码。其他线程阻塞。
该线程执行完同步块代码,主动释放锁。其他线程竞争这个对象锁。
synchronized(对象名){
//同步的代码块
}
package classlib.Day0311;
/**
* @author 35125
*/
public class TestTicket implements Runnable{
private int tickets = 100;
private Object o = new Object();
@Override
public void run() {
//买票
while (true){
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o){
if (tickets > 0){
System.out.println(Thread.currentThread().getName()+"--->买到"+tickets--+"号票");
}else{
break;
}
}
}
}
public static void main(String[] args) {
TestTicket tt = new TestTicket();
new Thread(tt,"xiaobai").start();
new Thread(tt,"xiaowang").start();
new Thread(tt,"laozhou").start();
}
}
(2)同步方法
通过当前对象this的锁,锁住整个方法。
this表示调用的方法所属的对象。
哪个线程获得this锁,哪个线程就能访问该方法。
[修饰符] synchronized 返回值类型 方法名([参数]){
方法体:同步代码
}
注意:多个线程的this必须指向同一个对象,否则没有同步效果。
package classlib.Day0311;
/**
* @author 35125
*/
public class TestTicket2 implements Runnable{
private int tickets = 100;
@Override
public void run() {
//买票
while(true){
saleTicket();
if (tickets<=0){
break;
}
}
}
//同步方法
//通过当前对象this的锁,锁住整个方法
public synchronized void saleTicket(){
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (tickets>0){
System.out.println(Thread.currentThread().getName()+"买到了"+tickets--+"号票。");
}
}
//主方法
public static void main(String[] args) {
TestTicket2 tt2 =new TestTicket2();
new Thread(tt2,"xiaoming").start();
new Thread(tt2,"xiaozhang").start();
new Thread(tt2,"xiaowangba").start();
}
}
(3)lock方式
jdk1.5提供的一种同步方式。
Lock接口:java.util.concurrent.locks.Lock
void
| lock()
获得锁
void
| unlock()
释放锁
实现类:ReentrantLock
多个线程竞争同一把锁,实现线程同步。
和同步块效果一样。
package classlib.Day0314;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author : [Charles]
* @version : [v1.8]
* @description : [Lock锁]
* @createTime : [2022/3/14 10:49]
*/
public class Tickets3 implements Runnable{
private int ticket = 100;
//创建一把锁
private Lock lk = new ReentrantLock(true);
@Override
public void run() {
while (true){
//获取锁
lk.lock();
try{
if (ticket>0){
System.out.println(Thread.currentThread().getName()+"卖出的票号为:"+ticket);
ticket--;
}
}catch (Exception e){
e.printStackTrace();
} finally {
//释放锁
if (lk != null){
lk.unlock();
}
}
if (ticket<=0){
break;
}
}
}
public static void main(String[] args) {
Tickets3 t11 = new Tickets3();
Thread t1 = new Thread(t11);
Thread t2 = new Thread(t11);
Thread t3 = new Thread(t11);
t1.start();
t2.start();
t3.start();
}
}
(4)lock和同步块比较
lock更直观,直接在代码中可以看见锁。
lock可以解决同步块中出现异常释放锁的问题。
九、线程通信:死锁处理
9.1 线程同步的问题:死锁
package classlib.Day0314;
/**
* @author : [Charles]
* @version : [v1.8]
* @description : [线程同步问题:死锁]
* @createTime : [2022/3/14 11:04]
*/
public class TestThread1 implements Runnable{
private Object a;
private Object b;
public TestThread1(Object a, Object b) {
this.a = a;
this.b = b;
}
@Override
public void run() {
String name = Thread.currentThread().getName();
synchronized (a){
System.out.println(name+"获得了a锁");
synchronized (b){
System.out.println(name+"获得了b锁");
System.out.println(name+"----------------------------------");
System.out.println(name+"释放了b锁");
}
System.out.println(name+"释放了a锁");
}
}
public static void main(String[] args) {
Object a = new Object();
Object b = new Object();
TestThread1 tt1 = new TestThread1(a,b);
TestThread1 tt2 = new TestThread1(a,b);
//创建线程
Thread t1 = new Thread(tt1,"线程1");
Thread t2 = new Thread(tt2,"线程2");
//开启线程
t1.start();
t2.start();
}
}
class MyThread2 implements Runnable{
private Object a;
private Object b;
public MyThread2(Object a, Object b) {
this.a = a;
this.b = b;
}
@Override
public void run() {
String name = Thread.currentThread().getName();
synchronized (b){
System.out.println(name+"获得了b锁");
synchronized (a){
System.out.println(name+"获得了a锁");
System.out.println(name+"++++++++++++++++++++++++++++++++");
System.out.println(name+"释放了a锁");
}
System.out.println(name + "释放了b锁");
}
}
}
线程1:获得了a锁
线程2:获得了b锁
9.2 死锁
两个线程各有一把锁,同时继续执行又需要对方的锁,出现相互阻塞的现象。
9.3 解决方案
通过线程通信的方式,让其中一方先放弃手中的锁,让对方先用完。
对方用完之后,在通知你来用。
对象名.wait() //释放对象锁,然后线程阻塞,直到被唤醒才能继续执行。
对象名.notify() //唤醒一个等待该对象锁的阻塞线程
对象名.notifyAll() //唤醒所有等待该对象锁的阻塞线程
package classlib.Day0314;
/**
* @author : [Charles]
* @version : [v1.8]
* @description : [线程同步问题:死锁]
* @createTime : [2022/3/14 11:04]
*/
public class TestThread1 implements Runnable{
private Object a;
private Object b;
public TestThread1(Object a, Object b) {
this.a = a;
this.b = b;
}
@Override
public void run() {
String name = Thread.currentThread().getName();
synchronized (a){
System.out.println(name+"获得了a锁");
synchronized (b){
System.out.println(name+"获得了b锁");
System.out.println(name+"----------------------------------");
System.out.println(name+"释放了b锁");
//唤醒等待b锁的一个线程
b.notify();
//唤醒等待b锁的所有线程
//b.notifyAll();
}
System.out.println(name+"释放了a锁");
}
}
public static void main(String[] args) {
Object a = new Object();
Object b = new Object();
TestThread1 tt1 = new TestThread1(a,b);
MyThread2 tt2 = new MyThread2(a,b);
//创建线程
Thread t1 = new Thread(tt1,"线程1:");
Thread t2 = new Thread(tt2,"线程2:");
//开启线程
t1.start();
t2.start();
}
}
class MyThread2 implements Runnable{
private Object a;
private Object b;
public MyThread2(Object a, Object b) {
this.a = a;
this.b = b;
}
@Override
public void run() {
String name = Thread.currentThread().getName();
synchronized (b){
System.out.println(name+"获得了b锁");
try {
//释放当前线程b锁 进入阻塞状态
//需要被唤醒 才能结束阻塞 重新进入CPU队列排队
b.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (a){
System.out.println(name+"获得了a锁");
System.out.println(name+"++++++++++++++++++++++++++++++++");
System.out.println(name+"释放了a锁");
}
System.out.println(name + "释放了b锁");
}
}
}
线程1:获得了a锁
线程2:获得了b锁
线程1:获得了b锁
线程1:----------------------------------
线程1:释放了b锁
线程1:释放了a锁
线程2:获得了a锁
线程2:++++++++++++++++++++++++++++++++
线程2:释放了a锁
线程2:释放了b锁
9.4 wait和sleep
都是线程阻塞的方法。
区别:
wait方法是Object方法;sleep方法是Thread方法。
wait方法会释放锁;sleep方法不会释放锁。
wait阻塞,需要其他线程调用notify、notifyAll方法来唤醒;sleep阻塞,到时间自动结束阻塞。
十、线程池
10.1 使用Callable实现多线程
(1)Thread 也实现了Runnable
不管是继承Thread,还是实现Runnable,都要重写run方法。
run方法的缺陷:没有返回值。
不能抛出异常。
(2)Callable接口:call方法有返回值,可以声明抛出异常。
(3)实现Callable接口创建线程
第一,实现Callable
第二,实现call方法(任务代码,和run一样):这个方法有返回值,还可以声明抛出异常。
第三,创建线程对象。
第四,开启线程。
package classlib.Day0314;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
/**
* @author : [Charles]
* @version : [v1.8]
* @description : [多线程:Callable接口]
* @createTime : [2022/3/14 14:38]
*/
public class TestCallable1 implements Callable<String> {
@Override
public String call() {
for (int i = 0; i < 100; i++) {
System.out.println("Callable接口"+i);
}
return "END";
}
public static void main(String[] args) {
//创建任务对象
TestCallable1 t1 = new TestCallable1();
//FutureTask = Runnable + Future
FutureTask<String> task = new FutureTask<>(t1);
//创建线程对象
Thread tt = new Thread(task);
//启动线程
tt.start();
for (int i = 0; i < 100; i++) {
System.out.println("Main"+i);
}
}
}
(4)Future类
用来跟踪和存储Callable执行过程和结果。
V
| get()
等待计算完成,然后检索其结果。
boolean
| isDone()
返回 true
如果任务已完成。
(5)FutureTask
实现了Runnable和Future。
//跟踪线程是否执行完毕
System.out.println(task.isDone());
//get()方法得到call方法的返回值
System.out.println(task.get());
10.2 线程池
线程池是我们创建线程的第四种方式。
线程池是一个线程的集合,包含多个线程。
我们在启动程序时,先创建好一些线程,保存在线程池中。
当我们需要执行任务代码时,从线程池中取出一条线程,然后在这个线程中执行该任务代码。
当任务代码执行完毕时,将线程在放入线程池中。
线程池的优点:
当任务到达时,可以不需要等待线程创建就能立即执行。
降低资源消耗:通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
提高线程的可管理性:线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,监控和调优。
提高响应速度。
10.3 四种线程池
使用java.util.concurrent.Executors 创建线程池。
(1)单一线程池
static ExecutorService
| newSingleThreadExecutor()
创建一个使用从无界队列运行的单个工作线程的执行程序。
使用场景:多个任务顺序执行
package classlib.Day0314;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @author : [Charles]
* @version : [v1.8]
* @description : [单一线程池]
* @createTime : [2022/3/14 15:20]
*/
public class TestPool {
public static void main(String[] args) {
//创建单一线程池 有且仅有一个线程
ExecutorService es = Executors.newSingleThreadExecutor();
//创建任务
Task1 t1 = new Task1();
Task1 t2 = new Task1();
Task2 t3 = new Task2();
Task2 t4 = new Task2();
//执行任务
es.execute(t1);
es.execute(t2);
es.submit(t3);
es.submit(t4);
//关闭线程池
es.shutdown();
}
}
class Task1 implements Runnable{
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName()+"Runnable"+i);
}
}
}
class Task2 implements Callable<String>{
@Override
public String call() throws Exception {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName()+"Callable"+i);
}
return "end";
}
}
(2)固定线程池
static ExecutorService
| newFixedThreadPool(int nThreads)
创建一个线程池,该线程池重用固定数量的从共享无界队列中运行的线程。 |
使用场景:同时要执行的任务数量比较多的情况,控制线程的数量。
package classlib.Day0314;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @author : [Charles]
* @version : [v1.8]
* @description : [固定线程池]
* @createTime : [2022/3/14 15:32]
*/
public class TestFixedPool {
public static void main(String[] args) {
//固定线程池:有且仅有几个线程池
ExecutorService fixed = Executors.newFixedThreadPool(2);
//创建任务
Task1 t1 = new Task1();
//Runnable
Task2 t2 = new Task2();
//Callable
Task1 t3 = new Task1();
//Runnable
Task2 t4 = new Task2();
//Callable
//执行任务
fixed.submit(t1);
fixed.submit(t2);
fixed.submit(t3);
fixed.submit(t4);
//关闭线程池
fixed.shutdown();
}
}
(3)可变线程池
static ExecutorService
| newCachedThreadPool()
创建一个根据需要创建新线程的线程池,但在可用时将重新使用以前构造的线程。 |
初始时,线程数量为0.
当有任务时,创建线程,执行任务。
执行完毕后,放回线程池。
空闲线程空闲时间大于指定时间,销毁该线程。
使用场景:同时执行的任务比较少的时候。
package classlib.Day0314;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @author : [Charles]
* @version : [v1.8]
* @description : [可变线程池]
* @createTime : [2022/3/14 16:11]
*/
public class TestChangePool {
public static void main(String[] args) {
ExecutorService es = Executors.newCachedThreadPool();
Task1 t1 = new Task1();
//Runnable
Task1 t2 = new Task1();
//Runnable
Task2 t3 = new Task2();
//Callable
Task2 t4 = new Task2();
//Callable
es.submit(t1);
es.submit(t2);
es.submit(t3);
es.submit(t4);
es.shutdown();
}
}
(4)定时线程池(任务调度线程池)
static ScheduledExecutorService
| newScheduledThreadPool(int corePoolSize)
创建一个线程池,可以调度命令在给定的延迟之后运行,或定期执行。
使用场景:延迟执行,或者定期执行。
package classlib.Day0314;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* @author : [Charles]
* @version : [v1.8]
* @description : [定时线程池]
* @createTime : [2022/3/14 16:15]
*/
public class TestScheduledPool {
public static void main(String[] args) {
//定时线程池
ScheduledThreadPoolExecutor ste = (ScheduledThreadPoolExecutor) Executors.newScheduledThreadPool(10);
//执行任务
Task1 t1 = new Task1();
//立马执行
//ste.submit(t1);
//定时执行
//ste.schedule(t1,10, TimeUnit.SECONDS);
ste.scheduleAtFixedRate(t1,10,3,TimeUnit.SECONDS);
//关闭线程池
//不能关闭,因为关闭了线程也就销毁了
//ste.shutdown();
}
}
10.4 ExecutorService的submit和execute方法区别
submit() 执行完毕后有返回值。
execute() 执行完毕后没有返回值。
10.5 四种线程池比较
10.6 自定义线程池
以上4种线程池,都是使用下面这个构造器创建的。
我们可以使用这个构造器来自定义线程池。
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
参数说明:
1、corePoolSize(线程池基本大小):当向线程池提交一个任务时,若线程池已创建的线程数小于corePoolSize,即便此时存在空闲线程,也会通过创建一个新线程来执行该任务,直到已创建的线程数大于或等于corePoolSize时,(除了利用提交新任务来创建和启动线程(按需构造),也可以通过 prestartCoreThread() 或 prestartAllCoreThreads() 方法来提前启动线程池中的基本线程。)
2、maximumPoolSize(线程池最大大小):线程池所允许的最大线程个数。当队列满了,且已创建的线程数小于maximumPoolSize,则线程池会创建新的线程来执行任务。另外,对于无界队列,可忽略该参数。
3、keepAliveTime(线程存活保持时间)当线程池中线程数大于核心线程数时,线程的空闲时间如果超过线程存活时间,那么这个线程就会被销毁,直到线程池中的线程数小于等于核心线程数。
4、unit 存活的时间单位
5、workQueue(任务队列):用于传输和保存等待执行任务的阻塞队列。
6、threadFactory(线程工厂):用于创建新线程。threadFactory创建的线程也是采用new Thread()方式,threadFactory创建的线程名都具有统一的风格:pool-m-thread-n(m为线程池的编号,n为线程池内的线程编号)。
7、handler(线程饱和策略):当线程池和队列都满了,再加入线程会执行此策略。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vx0WrfUQ-1647257468161)(C:\Users\35125\AppData\Roaming\Typora\typora-user-images\image-20220314163656504.png)]
package classlib.Day0314;
import java.util.concurrent.*;
/**
* @author : [Charles]
* @version : [v1.8]
* @description : [手写线程池:自定义线程池]
* @createTime : [2022/3/14 16:43]
*/
public class TestCustomPool {
public static void main(String[] args) {
//自定义线程池
//阻塞队列
BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(10);
ThreadPoolExecutor tpe = new ThreadPoolExecutor(2,4,3, TimeUnit.SECONDS,
queue,
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
//使用自定义线程池
//创建任务
Task1 t1 = new Task1();
Task1 t2 = new Task1();
Task2 t3 = new Task2();
Task2 t4 = new Task2();
//执行任务
tpe.execute(t1);
tpe.execute(t2);
tpe.submit(t3);
tpe.submit(t4);
//关闭线程池
tpe.shutdown();
}
}
避免资源耗尽的风险,生产上的推荐解法:
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
//引入依赖包,创建线程池
private ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNameFormat("thread-call-runner-%d").build();
private ExecutorService taskExe = new ThreadPoolExecutor(10,20,200L,TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>(),namedThreadFactory);
```java
*/
public class TestCustomPool {
public static void main(String[] args) {
//自定义线程池
//阻塞队列
BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(10);
ThreadPoolExecutor tpe = new ThreadPoolExecutor(2,4,3, TimeUnit.SECONDS,
queue,
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
//使用自定义线程池
//创建任务
Task1 t1 = new Task1();
Task1 t2 = new Task1();
Task2 t3 = new Task2();
Task2 t4 = new Task2();
//执行任务
tpe.execute(t1);
tpe.execute(t2);
tpe.submit(t3);
tpe.submit(t4);
//关闭线程池
tpe.shutdown();
}
}
避免资源耗尽的风险,生产上的推荐解法:
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
//引入依赖包,创建线程池
private ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNameFormat("thread-call-runner-%d").build();
private ExecutorService taskExe = new ThreadPoolExecutor(10,20,200L,TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>(),namedThreadFactory);