day12-多线程

多线程

1.为什么要学习多线程

生活:流水线打工

public static void main(String[] args) {
// 代码…    for (int i = 0; i < 10; i++) {
System.out.println(i);
}   
// 代码...
}

多线程:多(个) 线程

1.1 进程和线程

线程:是进程中的单个顺序控制流,是一条执行路径。

单线程:一个进程如果只有一条执行路径,则称为单线程程序

多线程:一个进程如果有多条执行路径,则称为多线程程序

在这里插入图片描述

进程:是正在运行的软件
独立性:进程是一个能独立运行的基本单位,同时也是系统分配资源和调度的独立单位。
动态性:进程的实质是程序的一次执行过程,进程是动态产生,动态消亡的。
并发性:任何进程都可以同其他进程一起并发执行

在这里插入图片描述

1.2 并发和并行

那到底是什么是并发

并发:在同一时刻,有多个指令在单个CPU交替执行。

在这里插入图片描述

**解释:**相信大家看到这张图就可以明白什么是并发了。三个菜,但是现在厨房里面只有一个厨师,那么这名厨师就需要先炒会第一个菜,再炒会第二个菜,再炒会第三个菜,再炒会第二个菜。由于,这个厨师在三个菜之间切换的速度比较快,那么在外人客户眼中看来,就是也是同时执行的。但是在某一个瞬间,他只在做其中一件事情。

并行:在同一时刻,有多个指令在多个CPU同时执行。

在这里插入图片描述

**解释:**现在饭馆需要炒三个菜:分别是西红柿炒番茄,青椒肉丝,海参炒饭。现在有三个厨师同时炒菜,那么这就是并行,重点:事情同时进行。

1.3 简单了解多线程

是指从软件或者硬件上实现多个线程并发执行的技术。(并发)

具有多线程能力的计算机因有硬件支持而能够在同一时间执行多个线程,提升性能。(并行)

1.4 小结

并发和并行

并行:在同一时刻,有多个指令在多个CPU上同时执行。

并发:在同一时刻,有多个指令在单个CPU上交替执行。

进程和线程

进程:就是操作系统中正在运行的一个应用程序。

线程:就是应用程序中做的事情。比如:360软件中的杀毒,扫描木马,清理垃圾。

而我们今天要学习的,就是多个线程的并发情况。
你觉得他有什么好处呢?如果没有多线程技术,那么我们电脑中的程序同时只能运行一个。
比如记事本跟打字软件就不能一起执行,打开记事本的时候就不能打字。玩游戏的时候就不能喷人。
而有了多线程,就能实现刚刚的需求。CPU在记事本软件和打字软件之间做高速的切换,那么在我们眼中这两款软件就是同时执行的了。

补充了解:

CPU线程数指的是CPU能够同时处理的线程数量。它的作用是决定CPU的计算能力和运行效率。CPU的线程数越多,就能并行地执行更多的指令,从而提高计算速度和系统响应速度。因此,在需要大量计算和数据处理的场景下,CPU线程数多的计算机具有更优越的性能表现。
每个 CPU 内核可以有两个线程。因此,具有两个内核的处理器将具有四个线程。具有 16 个内核的处理器将有32个线程。具有 24 个内核的处理器将有 48 个线程。
线程对计算机的功能很重要,因为它们决定了计算机在任何给定时间可以执行的任务数。

1.5 了解java中的多线程和cpu的线程区别

(1) 线程是CPU级别的,一个cpu核心支持一个线程

(2)Java多线程并不是由于cpu线程数为多个才称为多线程,当Java线程数大于cpu线程数,操作系统使用时间片机制,采用线程调度算法,频繁的进行线程切换。

(3) 线程是操作系统最小的调度单位,进程是资源(比如:内存)分配的最小单位

(4)Java中的所有线程在JVM进程中,CPU调度的是进程中的线程

2.多线程的实现方式

2.1 多线程的实现方案

继承Thread类的方式进行实现
实现Runnable接口的方式进行实现
利用Callable和Future接口方式实现

2.2 方式1:继承Thread类

步骤:
定义一个类MyThread继承Thread类
在MyThread类中重写run()方法
创建MyThread类的对象
启动线程

代码展示:

/**目标:多线程的创建方式一:继承Thread类实现。*/
public class ThreadDemo1 {public static void main(String[] args) {// 3、new一个新线程对象Thread t = new MyThread();// 4、调用start方法启动线程(执行的还是run方法)t.start();for (int i = 0; i < 5; i++) {System.out.println("主线程执行输出:" + i);}}
}/**1、定义一个线程类继承Thread类*/
class MyThread extends Thread{/**2、重写run方法,里面是定义线程以后要干啥*/@Overridepublic void run() {for (int i = 0; i < 5; i++) {System.out.println("子线程执行输出:" + i);}}
}

两个小问题:

1.为什么要重写run()方法?

因为run()是用来封装被线程执行的代码

2.run()方法和start()方法的区别?

**run():**封装线程执行的代码,直接调用,相当于普通方法的调用,并没有开启线程。

**start():**启动线程;然后由JVM调用此线程的run()方法

创建两个线程,那么Java中就有main线程和自己创建出来的两条线程,共计三条线程。

2.3 方式2:实现Runnable接口

步骤:
1.定义一个类MyRunnable实现Runnable接口

2.在MyRunnable类中重写run()方法

3.创建MyRunnable类的对象

4.创建Thread类的对象,把MyRunnable对象作为构造方法的参数

5.启动线程

代码展示:

/**目标:学会线程的创建方式二,理解它的优缺点。*/
public class ThreadDemo2 {public static void main(String[] args) {// 3、创建一个任务对象Runnable target = new MyRunnable();// 4、把任务对象交给Thread处理Thread t = new Thread(target);// Thread t = new Thread(target, "1号");// 5、启动线程t.start();for (int i = 0; i < 10; i++) {System.out.println("主线程执行输出:" + i);}}
}
/**1、定义一个线程任务类 实现Runnable接口*/
class MyRunnable  implements Runnable {/**2、重写run方法,定义线程的执行任务的*/@Overridepublic void run() {for (int i = 0; i < 10; i++) {System.out.println("子线程执行输出:" + i);}}
}

简化写法

public static void main(String[] args) {//这种模式先 会进行线程的抢占new Thread(() -> {for (int i = 0; i < 5; i++) {System.out.println("子线程执行" + i);}}).start();for (int i = 0; i < 5; i++) {System.out.println("主线程执行" + i);}}

2.4 方式3:Callable和Future

在这里插入图片描述

步骤:

1.定义一个类MyCallable实现Callable接口

2.在MyCallable类中重写call()方法

3.创建MyCallable类的对象

4.创建Future的实现类FutureTask对象,把MyCallable对象作为构造方法的参数

5.创建Thread类的对象,把FutureTask对象作为构造方法的参数

6.启动线程

7.再调用get方法,就可以获取线程结束之后的结果。

代码演示:

MyCallable

/**1、定义一个任务类 实现Callable接口  应该申明线程任务执行完毕后的结果的数据类型*/
class MyCallable implements Callable<String>{private int n;public MyCallable(int n) {this.n = n;}/**2、重写call方法(任务方法)*/@Overridepublic String call() throws Exception {int sum = 0;for (int i = 1; i <= n ; i++) {sum += i;}return "子线程执行的结果是:" + sum;}
}

Demo

/**目标:学会线程的创建方式三:实现Callable接口,结合FutureTask完成。FutureTask对象的作用1: 是Runnable的对象(实现了Runnable接口),可以交给Thread了FutureTask对象的作用2: 可以在线程执行完毕之后通过调用其get方法得到线程执行完成的结果*/
public class ThreadDemo3 {public static void main(String[] args) {// 3、创建Callable任务对象Callable<String> call = new MyCallable(100);// 4、把Callable任务对象 交给 FutureTask 对象FutureTask<String> f1 = new FutureTask<>(call);// 5、交给线程处理Thread t1 = new Thread(f1);t1.setName("一号线程");// 6、启动线程t1.start();System.out.println("线程名字为:" + t1.getName()+","+"线程编号为:"+t1.getId());Callable<String> call2 = new MyCallable(200);FutureTask<String> f2 = new FutureTask<>(call2);Thread t2 = new Thread(f2);t2.setName("二号线程");t2.start();System.out.println("线程名字为:" + t2.getName() + "," + "线程编号为:" + t2.getId());try {// 如果f1任务没有执行完毕,这里的代码会等待,直到线程1跑完才提取结果。//调用get方法,就可以获取线程结束之后的结果String rs1 = f1.get();System.out.println("第一个结果:" + rs1);} catch (Exception e) {e.printStackTrace();}try {// 如果f2任务没有执行完毕,这里的代码会等待,直到线程2跑完才提取结果。String rs2 = f2.get();System.out.println("第二个结果:" + rs2);} catch (Exception e) {e.printStackTrace();}}
}

2.5 三种方式的对比

优点缺点
实现Runnable、Callable接口扩展性强,实现该接口的同时还可以继承其他的类。编程相对复杂,不能直接使用Thread类中的方法
继承Thread类编程比较简单,可以直接使用Thread类中的方法可以扩展性较差,不能再继承其他的类

3.线程类的常见方法

3.1 方法

方法描述
String getName()返回此线程的名称
void setName(String name)将此线程的名称更改
public static Thread currentThread()返回对当前正在执行的线程对象的引用
public static void sleep(long time)让线程休眠指定的时间,单位为毫秒。
public final void setPriority(int newPriority)设置线程的优先级
public final int getPriority()获取线程的优先级

线程优先级有10个等级,分别用整数1-10表示。其中1位最低优先级,10为最高优先级,5为默认值。

线程优先级高,只是权重高,获得CPU调度的概率高,并不是一定排前面

代码展示

MyThread

public class MyThread extends Thread{public MyThread() {}public MyThread(String name) {super(name);}@Overridepublic void run() {for (int i = 0; i < 5; i++) {System.out.println(Thread.currentThread().getName()+"输出:" + i);}}
}

ThreadDemo

public static void main(String[] args) {//main方法是由主线程负责调用的Thread t1 = new MyThread("一号");// t1.setName("一号");System.out.println("一号线程的优先级为:" + t1.getPriority());t1.start();System.out.println(t1.getName());Thread t2 = new MyThread("二号");System.out.println("二号线程的优先级为:" + t2.getPriority());// t1.setName("二号");t2.start();System.out.println(t2.getName());Thread t3 = new MyThread("三号");System.out.println("三号线程的优先级为:" + t3.getPriority());t3.start();System.out.println(t2.getName());// 哪个线程执行它,它就得到哪个线程对象(当前线程对象)// 主线程是main 默认下称优先级为5Thread thread = Thread.currentThread();//当前线程 为 mainSystem.out.println("当前线程的优先级为:" + Thread.currentThread().getPriority());System.out.println(thread.getName());thread.setName("最牛线程");for (int i = 0; i < 5; i++) {System.out.println(thread.getName()+"输出:" + i);}
}
public class ThreadDemo02 {// main方法是由主线程负责调度的public static void main(String[] args) throws Exception {for (int i = 1; i <= 5; i++) {System.out.println("输出:" + i);if(i == 3){// 让当前线程进入休眠状态// 段子:项目经理让我加上这行代码,如果用户愿意交钱,我就注释掉。Thread.sleep(3000);}}}
}

3.2 线程生命周期问题

一个宫女的故事:宫女出生了------新建
宫女参加选秀成为才女-------------就绪
宫女获得皇帝侍奉权---------------运行
宫女侍奉完成回寝宫等待-----------就绪
宫女侍奉中被打断------------------阻塞----就绪
宫女完成侍奉任务----------------死亡

在这里插入图片描述

就绪: 有资格抢cput执行权,但是没有执行代码的权利。(在这个过程中不停地抢cpu)

运行:在运行状态 有资格并且可以执行代码,但是有可能被其他线程抢走cpu的执行权 回退到就绪状态。

**阻塞:**遇到sleep进入阻塞状态。

问题:sleep方法会让线程睡眠,睡眠时间到了,立马就会执行下面的代码吗?

4.线程的安全问题

4.1 案例:卖票

需求:某电影院目前正在上映国产大片,共有100张票,而它有3个窗口卖票,请设计一个程序模拟该电影院卖票。
思路
1.定义一个类Ticket实现Runnable接口,里面定义一个成员变量:

private int ticketCount = 100;

2.在Ticket类中重写run()方法实现卖票,代码步骤如下:
1.判断票数大于0,就卖票,并告知是哪个窗口卖的
2.票数要减1
3.卖光之后,线程停止

3.定义一个测试类TicketDemo,里面有main方法,代码步骤如下:
A:创建Ticket类的对象
B:创建三个Thread类的对象,把Ticket对象作为构造方法的参数,并给出对应的窗口名称
C:启动线程

代码演示

public class Ticket implements Runnable {//定义初始票数private int ticketCount = 100;@Overridepublic void run() {while (true) {if (ticketCount > 0) {ticketCount--;System.out.println(Thread.currentThread().getName() + "抢到了票!!" + "还有" + ticketCount + "张票");}else {System.out.println("一张票也没有了!!!");break;}}}
}
public class TicketDemo {public static void main(String[] args) {Ticket tk1 = new Ticket();Thread t1 = new Thread(tk1);Thread t2 = new Thread(tk1);Thread t3 = new Thread(tk1);t1.setName("窗口1");t2.setName("窗口2");t3.setName("窗口3");t1.start();t2.start();t3.start();}
}

4.2卖票案例的思考

刚才讲解了电影院卖票程序,好像没有什么问题。但是在实际生活中,售票时出票也是需要时间的,所以,在出售一张票的时候,需要一点时间的延迟,接下来我们去修改卖票程序中卖票的动作:
每次出票时间100毫秒,用sleep()方法实现【正常开发逻辑进入if判断之前和if判断里面都会有逻辑代码】

卖票出现了问题
相同的票出现了多次
问题原因:
线程执行的随机性导致的

代码演示:

public class Ticket implements Runnable {//定义初始票数private int ticketCount = 100;@Overridepublic void run() {while (true) {try {Thread.sleep(10);if (ticketCount > 0) {ticketCount--;System.out.println(Thread.currentThread().getName() + "抢到了票!!" + "还有" + ticketCount + "张票");}else {System.out.println("一张票也没有了!!!");break;}} catch (InterruptedException e) {e.printStackTrace();}}}
}

4.3 卖票案例数据安全问题的解决

为什么出现问题?(这也是我们判断多线程程序是否会有数据安全问题的标准)
多线程操作共享数据
如何解决多线程安全问题呢?
基本思想:让程序没有安全问题的环境

怎么实现呢?
1.把多条语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行即可
2.Java提供了同步代码块的方式来解决

4.4 同步代码块

在上述案例中我们用到了synchronized,那synchronized到底是什么呢?

锁多条语句操作共享数据,可以使用同步代码块实现

格式:

 synchronized(任意对象) { 多条语句操作共享数据的代码 }	

默认情况:是打开的,只要有一个线程进去执行代码了,锁就会关闭

当线程执行完出来了,锁才会自动打开

synchronized(this)锁的是调用该代码块的当前对象(不能保证线程安全)
synchronized(xxx.class)是对当前类上锁,不管是不是同一线程,只要是该类的对象,就会被上锁同步的

同步的好处和弊端

**好处:**解决了多线程的数据安全问题

**弊端:**当线程很多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率

代码展示:

public class Ticket implements Runnable {//定义初始票数private int ticketCount = 100;@Overridepublic void run() {while (true) {try {Thread.sleep(10);synchronized (Ticket.class){if (ticketCount > 0) {ticketCount--;System.out.println(Thread.currentThread().getName() + "抢到了票!!" + "还有" + ticketCount + "张票");}else {System.out.println("一张票也没有了!!!");break;}}} catch (InterruptedException e) {e.printStackTrace();}}}
}

4.5 同步方法

同步方法:就是把synchronized关键字加到方法上

格式: [把休眠时间放到锁之前]

修饰符 synchronized 返回值类型 方法名(方法参数) {    }

同步代码块和同步方法的区别:

同步代码块可以锁住指定代码,同步方法是锁住方法中所有代码

同步代码块可以指定锁对象,同步方法不能指定锁对象

同步方法的锁对象是什么呢?

this

同步静态方法:就是把synchronized关键字加到静态方法上
格式:

  修饰符 static synchronized 返回值类型 方法名(方法参数) {    }

同步静态方法的锁对象是什么呢?
类名.class

快捷方式 选中代码 ctrl+alt+m

用同步方法实现代码演示:

public class Ticket implements Runnable {//定义初始票数private int ticketCount = 100;@Overridepublic void run() {while (true) {try {Thread.sleep(10);if (extracted()) break;} catch (InterruptedException e) {e.printStackTrace();}}}private synchronized boolean extracted() {if (ticketCount > 0) {ticketCount--;System.out.println(Thread.currentThread().getName() + "抢到了票!!" + "还有" + ticketCount + "张票");} else {System.out.println("一张票也没有了!!!");return true;}return false;}
}

4.6 Lock锁

虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,
为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock

Lock实现提供比使用synchronized方法和语句可以获得更广泛的锁定操作
Lock中提供了获得锁和释放锁的方法

void lock():获得锁
void unlock():释放锁【放到finally里面释放锁】

Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化
ReentrantLock的构造方法

ReentrantLock():创建一个ReentrantLock的实例

代码演示:

public class Ticket implements Runnable {//定义初始票数private int ticketCount = 100;//定义锁private final static ReentrantLock lock = new ReentrantLock();@Overridepublic void run() {while (true) {//4.解决单窗口卖票问题 win11处理机制的问题try {Thread.sleep(10);//在休眠后锁住lock.lock();if (ticketCount > 0) {ticketCount--;System.out.println(Thread.currentThread().getName() + "抢到了票!!" + "还有" + ticketCount + "张票");} else {System.out.println("一张票也没有了!!!");break;}} catch (InterruptedException e) {e.printStackTrace();} finally {//释放锁lock.unlock();}}}
}

4.7 按照获取的锁分类

获取对象锁

synchronized(this|object) {}

修饰非静态方法

获取类锁

synchronized(类.class) {}

修饰静态方法

对象锁与类锁的区别
对象锁:每个实例都会有一个monitor对象,即Java对象的锁,类的对象可以有多个,所以每个对象有其独立的

对象锁,互不干扰

类锁:每个类只有一个Class对象,所以每个类只有一个类锁;类锁是加载类上的,而类信息是存在JVM方法区的,并且整个JVM只有一份,方法区又是所有线程共享的,所以类锁是所有线程共享的

5.死锁

线程死锁是指由于两个或者多个线程互相持有对方所需要的资源,导致这些线程处于等待状态,无法前往执行。

在这里插入图片描述

代码演示:

public static void main(String[] args) {Object o1 = new Object();Object o2 = new Object();new Thread(() -> {while (true){synchronized (o1){synchronized (o2){System.out.println("一锁二");}}}}).start();new Thread(() -> {while (true){synchronized (o2){synchronized (o1){System.out.println("二锁一");}}}}).start();
}

6.生产者消费者

生产者消费者模式是一个十分经典的多线程协作的模式,弄懂生产者消费者问题能够让我们对多线程编程的理解更加深刻。就是一个典型的等待唤醒机制

生产者 - 消费者模型( Producer-consumer problem) 是一个非常经典的多线程并发协作的模型,在分布式系统里非常常见

6.1步骤

* 1.循环
* 2.同步代码块
* 3.判断共享数据是否到了末尾(没有到末尾)
* 4.判断共享数据是否到了末尾(到了末尾)

消费者步骤:

1.判断桌子上是否有汉堡包。

2.判断桌子上是否有汉堡

3.如果没有就等待。

4.如果有就开吃

5.吃完 总数-1

6.吃完之后 唤醒老八继续做汉

7.修改桌子的状态

生产者步骤:

1.判断桌子上是否有汉堡包

2.如果有就等待

3.如果没有 则制作

4.修改桌子上食物的状态

5.唤醒消费者开吃

6.2 等待和唤醒的方法

为了体现生产和消费过程中的等待和唤醒,Java就提供了几个方法供我们使用,这几个方法在Object类中
Object类的等待和唤醒方法:

方法名说明
void wait()导致当前线程等待,直到另一个线程调用该对象的 notify()方法或notifyAll()方法
void notify()唤醒正在等待对象监视器的单个线程
void notifyAll()唤醒正在等待对象监视器的所有线程

代码实现:

Desk

/*** 作用:控制生产者和消费者的执行*/
public class Desk {//定义一个标记//0 没有 1 有public static int flag = 0;//定义汉堡总数public static int hamburger = 10;//创建锁对象用于锁住一个普通类对象public  static Object lock = new Object();//使用类锁 线程必须一个一个的执行 //类锁会锁住一个类的所有创造出来的所有实例对象,无论这些线程持有的是否是同一个对象}

People

/*** 1.循环* 2.同步代码块* 3.判断共享数据是否到了末尾(没有到末尾)* 4.判断共享数据是否到了末尾(到了末尾)*/
public class People implements Runnable {@Overridepublic void run() {while (true) {synchronized (Desk.lock) {//判断桌子上是否有汉堡if (Desk.hamburger == 0) {break;} else {if (Desk.flag == 0) {//如果没有 就等待try {Desk.lock.wait();//让当前线程和锁进行绑定} catch (InterruptedException e) {e.printStackTrace();}} else {//吃完 总数-1Desk.hamburger--;//如果有 就开吃System.out.println("我们正在吃老八做的小汉堡,还能吃" + Desk.hamburger + "个");//吃完之后 唤醒老八继续做汉堡Desk.lock.notifyAll();//修改桌子的状态Desk.flag = 0;}}}}}
}

OldEight

/*** 1.循环* 2.同步代码块* 3.判断共享数据是否到了末尾(没有到末尾)* 4.判断共享数据是否到了末尾(到了末尾)*/public class OldEight implements Runnable {@Overridepublic void run() {while (true) {synchronized (Desk.lock) {if (Desk.hamburger == 0){break;}else {//判断桌子上是否有汉堡if (Desk.flag == 1) {//如果有 则等待try {Desk.lock.wait();} catch (InterruptedException e) {e.printStackTrace();}}else {//如果没有 则制作System.out.println("老八正在加急制作小汉堡");//修改桌子上食物的状态Desk.flag = 1;//唤醒消费者开吃Desk.lock.notifyAll();}}}}}
}

Test

public class Test {public static void main(String[] args) {People people = new People();OldEight oldEight = new OldEight();Thread thread = new Thread(people);thread.setName("小敏");thread.start();Thread thread1 = new Thread(oldEight);thread.setName("老八");thread1.start();}
}

7.线程的状态

在这里插入图片描述

在这里插入图片描述

小结

虚拟机中线程的六种状态:

新建状态( NEW )---------------创建线程对象

就绪状态( RUNNABLE )--------start方法

阻塞状态( BLOCKED )-----------无法获得锁对象

等待状态( WAITING )-----------wait方法

计时等待( TIMED_WAITING )—sleep方法

结束状态( TERMINATED )------全部代码运行完毕

运行不属于线程中的状态 这是 当线程抢夺到cpu的执行权的时候 交给操作系统进行管理。java就不管啦!

8.线程池

8.1 线程池主要核心原理

1.创建一个池子,池子中是空的

2.有任务需要执行时,创建线程对象 。

任务执行完毕,线程对象归还给池子 。

下次再次提交任务,不需要创建新的线程,直接复用已有线程。

3.所有的任务全部执行完毕,关闭线程池。

8.2 线程池创建过程

1.创建Executors中的静态方法

方法名称说明
public static ExecutorService newcachedThreadPool()创建一个没有上限的线程池 (也是有最大上限int类型最大值 通常到不了电脑就崩溃了)
public static ExecutorService newFixedThreadPool(int nThreads)创建有上限的线程池

2.提交任务 submit方法

3.销毁线程池(通常不需要)shutdown方法

方法名称说明
void execute(Runnable command)执行任务/命令,没有返回值,一般用来执行 Runnable 任务
Future submit(Callable task)执行任务,返回未来任务对象获取线程结果,一般拿来执行 Callable 任务
void shutdown()等任务执行完毕后关闭线程池

代码展示:

MyRunnable

public class MyRunnable implements Runnable{@Overridepublic void run() {for (int i = 0; i < 10; i++) {System.out.println(Thread.currentThread().getName()+"--------"+i);}}
}

MyCallable

public class CallableTest implements Callable<String> {@Overridepublic String call() throws Exception {for (int i = 0; i < 10; i++) {System.out.println(Thread.currentThread().getName()+"------"+i);}return null;}
}

ThreadPoolDemo

public class ThreadPoolDemo {public static void main(String[] args) {//创建线程池ExecutorService pool = Executors.newCachedThreadPool();//执行任务//每次提交一个任务 开始一个线程  1-1pool.submit(new MyRunnable());pool.submit(new MyRunnable());pool.submit(new MyRunnable());pool.submit(new MyRunnable());// ExecutorService pool = Executors.newCachedThreadPool();// pool.submit(new CallableTest());// pool.submit(new CallableTest());// pool.submit(new CallableTest());//关闭线程池pool.shutdown();}
}

如果指定了线程池的数量 则可以用指定线程池数量的方法

 public static void main(String[] args) throws InterruptedException {//创建线程池
//        ExecutorService pool = Executors.newCachedThreadPool();ExecutorService pool = Executors.newFixedThreadPool(3);//执行任务//每次提交一个任务 开始一个线程  1-1pool.submit(new MyRunnable());pool.submit(new MyRunnable());pool.submit(new MyRunnable());pool.submit(new MyRunnable());pool.submit(new MyRunnable());pool.submit(new MyRunnable());//最多只能由三个线程。//关闭线程池pool.shutdown();}

8.3 自定义线程池

8.3.1案例引入创建自定义线程池

创建一个核心线程数2,最大线程数5,临时线程空闲2秒,队列10,默认创建线程,拒绝策略抛出异常的线程池。
ThreadPoolExecutor pool = new ThreadPoolExecutor(2,//核心线程数5,//最大线程数2,//临时线程空闲时间TimeUnit.SECONDS,//时间单位new ArrayBlockingQueue<>(10),//队列Executors.defaultThreadFactory(),//创建线程方式new ThreadPoolExecutor.AbortPolicy());// 线程拒绝策略// 获取线程pool.submit(new MyRunnable());// 关闭线程池pool.shutdown();

8.3.2 线程池细节

在这里插入图片描述

排队问题?
招募临时线程问题?
处理上线问题?

在这里插入图片描述

8.3.3 故事中的核心元素

在这里插入图片描述

8.3.4 线程池的创建

ThreadPoolExecutor构造器的参数说明

public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize, long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory, RejectedExecutionHandler handler) 

ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(核心线程数量,最大线程数量,空闲线程最大存活时间,任务队列,创建线程工厂,任务的拒绝策略);

参数一:指定线程池的线程数量(核心线程): corePoolSize -------------------不能小于0
参数二:指定线程池可支持的最大线程数: maximumPoolSize-------------------最大数量 >= 核心线程数量
参数三:指定临时线程的最大存活时间: keepAliveTime-------------------不能小于0
参数四:指定存活时间的单位(秒、分、时、天): unit-------------------时间单位
参数五:指定任务队列: workQueue -------------------不能为null
参数六:指定用哪个线程工厂创建线程: threadFactory -------------------不能为null
参数七:指定线程忙,任务满的时候,新任务来了怎么办: handler -------------------不能为null

代码展示:

public class ThreadPoolDemo3 {public static void main(String[] args) {ThreadPoolExecutor pool = new ThreadPoolExecutor(2,5,3,TimeUnit.SECONDS,new ArrayBlockingQueue<>(3),Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());//正常2个线程处理pool.submit(new CallableTest());pool.submit(new CallableTest());//队列中最多放3个pool.submit(new CallableTest());pool.submit(new CallableTest());pool.submit(new CallableTest());//最大线程是5个 最多再开启3个临时线程pool.submit(new CallableTest());pool.submit(new CallableTest());pool.submit(new CallableTest());//再多一个就报错
//        pool.submit(new CallableTest());pool.shutdown();}
}

8.3.5 任务拒绝策略

策略详解
ThreadPoolExecutor.AbortPolicy丢弃任务并抛出RejectedExecutionException异常。是默认的策略
ThreadPoolExecutor.DiscardPolicy丢弃任务,但是不抛出异常 这是不推荐的做法
ThreadPoolExecutor.DiscardOldestPolicy抛弃队列中等待最久的任务 然后把当前任务加入队列中
ThreadPoolExecutor.CallerRunsPolicy由主线程负责调用任务的run()方法从而绕过线程池直接执行

8.3.6 注意问题

Executors使用可能存在的陷阱大型并发系统环境中使用Executors如果不注意可能会出现系统风险。

在这里插入图片描述

8.3.7 面试题

临时线程什么时候创建啊?

新任务提交时发现核心线程都在忙,任务队列也满了,并且还可以创建临时线程,此时才会创建临时线程。

什么时候会开始拒绝任务?

核心线程和临时线程都在忙,任务队列也满了,新的任务过来的时候才会开始任务拒绝。常见: 游戏/购物 排队。

8.3.8 高级面试题

高阶面试题冲刺2W月薪!!!

  • 自定义线程池什么时候创建临时线程?当任务数>core+queue
  • 队列什么时候生效呢?当任务数>core
  • 自定义线程池什么时候拒绝策略生效? 任务数>max+queue
  • 什么时候创建核心线程?懒汉式 饿汉式
  • 案例:
  1. 创建一个:基本线程为2,最大线程数为5,等待时间为5s,任务队列为10,创建工厂默认,拒绝策略默认-的线程池。

    1. for循环测试创建2个线程
    2. for循环测试创建5个线程
    3. for循环测试创建12个线程
    4. for循环测试创建13个线程
    5. for循环测试创建17个线程
public static void main(String[] args) {ThreadPoolExecutor pool = new ThreadPoolExecutor(2, 5, 5,TimeUnit.SECONDS, new ArrayBlockingQueue<>(10),Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());for (int i = 0; i < 15; i++) {pool.submit(new MyRunnable());}pool.shutdown();
}

常。是默认的策略 |
| ThreadPoolExecutor.DiscardPolicy | 丢弃任务,但是不抛出异常 这是不推荐的做法 |
| ThreadPoolExecutor.DiscardOldestPolicy | 抛弃队列中等待最久的任务 然后把当前任务加入队列中 |
| ThreadPoolExecutor.CallerRunsPolicy | 由主线程负责调用任务的run()方法从而绕过线程池直接执行 |

8.3.6 注意问题

Executors使用可能存在的陷阱大型并发系统环境中使用Executors如果不注意可能会出现系统风险。

[外链图片转存中…(img-WyOvg8CK-1715501236869)]

8.3.7 面试题

临时线程什么时候创建啊?

新任务提交时发现核心线程都在忙,任务队列也满了,并且还可以创建临时线程,此时才会创建临时线程。

什么时候会开始拒绝任务?

核心线程和临时线程都在忙,任务队列也满了,新的任务过来的时候才会开始任务拒绝。常见: 游戏/购物 排队。

8.3.8 高级面试题

高阶面试题冲刺2W月薪!!!

  • 自定义线程池什么时候创建临时线程?当任务数>core+queue
  • 队列什么时候生效呢?当任务数>core
  • 自定义线程池什么时候拒绝策略生效? 任务数>max+queue
  • 什么时候创建核心线程?懒汉式 饿汉式
  • 案例:
  1. 创建一个:基本线程为2,最大线程数为5,等待时间为5s,任务队列为10,创建工厂默认,拒绝策略默认-的线程池。

    1. for循环测试创建2个线程
    2. for循环测试创建5个线程
    3. for循环测试创建12个线程
    4. for循环测试创建13个线程
    5. for循环测试创建17个线程
public static void main(String[] args) {ThreadPoolExecutor pool = new ThreadPoolExecutor(2, 5, 5,TimeUnit.SECONDS, new ArrayBlockingQueue<>(10),Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());for (int i = 0; i < 15; i++) {pool.submit(new MyRunnable());}pool.shutdown();
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/692144.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【回溯 字典树(前缀树)】212. 单词搜索 II

本文涉及知识点 回溯 字典树&#xff08;前缀树&#xff09; LeetCode212. 单词搜索 II 给定一个 m x n 二维字符网格 board 和一个单词&#xff08;字符串&#xff09;列表 words&#xff0c; 返回所有二维网格上的单词 。 单词必须按照字母顺序&#xff0c;通过 相邻的单元…

【全开源】排队叫号系统基于FastAdmin+GatewayWorker(源码搭建/上线/运营/售后/维护更新)

一款基于FastAdminGatewayWorker开发的多项目多场景排队叫号系统&#xff0c;支持大屏幕投屏&#xff0c;语音播报叫号&#xff0c;可用于餐厅排队取餐、美甲店排队取号、排队领取、排队就诊、排队办理业务等诸多场景&#xff0c;助你轻松应对各种排队取号叫号场景。 功能简介…

springboot中mybatisplus注意事项

使用代码生成工具CodeGenerator 需要修改的内容 dsc.setUsername(“root”); mysql账号dsc.setPassword(“root”); mysql密码strategy.setInclude(“crm_edu”); 表名pc.setModuleName(“eduservice”); //模块名 package com.test.demo;import com.baomidou.mybatisplus.a…

Array.map解析

map方法会创建一个新数组。该方法会循环数组中的每个值&#xff0c;如果仅仅是想循环数组不需要返回值使用数组的forEach方法就可以。原数组中的每个元素都调用一次提供的函数后的返回值组成。Array.map 它接收一个函数 这个函数可以接收三个参数 数组的每个值item 这个值的索引…

【WebGPU】WebGPU 中的反应扩散计算着色器

在本教程中&#xff0c;我们将使用 WebGPU 技术中的计算着色器实现图像效果。更多精彩内容尽在数字孪生平台。 程序结构 主要构建两个 WebGPU 管道&#xff1a; 运行反应扩散算法多次迭代的计算管道&#xff08;js/rd-compute.js 和 js/shader/rd-compute-shader.js&#xff…

李飞飞首次创业!

B站&#xff1a;啥都会一点的研究生公众号&#xff1a;啥都会一点的研究生 最近AI又有啥进展&#xff1f;一起看看吧~ 中国独角兽企业已达369家&#xff0c;六成以上与AI、芯片等硬科技赛道有关 2024中关村论坛“全球独角兽企业大会”上发布全新《中国独角兽企业发展报告&am…

数据结构-二叉树-红黑树

一、红黑树的概念 红黑树是一种二叉搜索树&#xff0c;但在每个节点上增加一个存储位表示节点的颜色&#xff0c;可以是Red或者BLACK&#xff0c;通过对任何一条从根到叶子的路径上各个节点着色方式的限制&#xff0c;红黑树确保没有一条路径会比其他路径长出两倍&#xff0c;…

相同的树——java

给你两棵二叉树的根节点 p 和 q &#xff0c;编写一个函数来检验这两棵树是否相同。 如果两个树在结构上相同&#xff0c;并且节点具有相同的值&#xff0c;则认为它们是相同的。 示例 1&#xff1a; 输入&#xff1a;p [1,2,3], q [1,2,3] 输出&#xff1a;true示例 2&…

网安面经之文件包含漏洞

一、文件包含漏洞 1、文件包含漏洞原理&#xff1f;危害&#xff1f;修复&#xff1f; 原理&#xff1a;开发⼈员⼀般希望代码更灵活&#xff0c;所以将被包含的⽂件设置为变量&#xff0c;⽤来进⾏动态调⽤&#xff0c;但是由于⽂件包含函数加载的参数没有经过过滤或者严格的…

HTML表单创建学习

文章目录 1、创建HTML框架2.body标签CSS3.表单创建3.1、添加fieldset与label标签3.2、为label标签添加css样式3.3、添加input标签3.4、添加提交按钮3.5、在input标签中添加required3.6、添加minlength属性3.7、pattern属性3.8、设置表单单选按钮无法同时选中3.9、添加链接3.10、…

三、RocketMQ应用

RocketMQ应用 一、测试环境工程准备二、消息响应1.消息发送的状态-SendStatus2.producer消息Id-msgId3.broker消息Id-offsetMsgId 三、普通消息1.消息发送分类1.1 同步发送消息1.2 异步发送消息1.3 单向发送消息 2.代码举例2.1 同步消息发送生产者2.2 异步消息发送生产者2.3 单…

AI应用案例:新闻文本分类

随着科学技术的不断发展&#xff0c;互联网技术得以快速的发展和普及&#xff0c;并已在各行各业得到了广泛的应用&#xff0c;从中致使了网络上的信息呈现出爆炸式的增长状态&#xff0c;达到了“足不出户&#xff0c;万事皆知”的境况&#xff0c;充分体现了互联网新闻给生活…