JAVA基础-多线程线程池
文章目录
- 1. 多线程
- 1.1什么是多线程
- (1)并发和并行
- (2)进程和线程
- 1.2多线程的实现方式
- 1.2.1 方式一:继承Thread类
- 1.2.2 方式二:实现Runnable接口
- 1.2.3方式三: 实现Callable接口
- 1.3 常见的成员方法
- 1.3.1 设置和获取线程名称
- 1.3.2 线程休眠
- 1.3.3 线程优先级
- 1.3.4 守护线程
- 1.3.5 礼让线程和插队线程
- 1.4 线程的生命周期
- 2. 线程同步
- 2.1卖票
- 2.2 同步代码块
- 2.3 同步方法
- 2.4 Lock锁
- 2.5 死锁
- 2.6.生产者消费者(等待唤醒机制)
- 2.6.1 生产者和消费者机制
- 2.6.2 阻塞队列实现等待唤醒机制
- 4 线程的状态
- 5. 线程池
1. 多线程
1.1什么是多线程
(1)并发和并行
- 并行:在同一时刻,有多个指令在多个CPU上同时执行。
- 并发:在同一时刻,有多个指令在单个CPU上交替执行。
(2)进程和线程
-
进程:是正在运行的程序
独立性:进程是一个能独立运行的基本单位,同时也是系统分配资源和调度的独立单位
动态性:进程的实质是程序的一次执行过程,进程是动态产生,动态消亡的
并发性:任何进程都可以同其他进程一起并发执行 -
线程:是进程中的单个顺序控制流,是一条执行路径
单线程:一个进程如果只有一条执行路径,则称为单线程程序
多线程:一个进程如果有多条执行路径,则称为多线程程序

1.2多线程的实现方式
1.2.1 方式一:继承Thread类
-
方法名 说明 void run() 在线程开启后,此方法将被调用执行 void start() 使此线程开始执行,Java虚拟机会调用run方法() -
实现步骤
- 定义一个类MyThread继承Thread类
- 在MyThread类中重写run()方法
- 创建MyThread类的对象
- 启动线程
-
代码演示
//1.定义MyThread类继承Thread public class MyThread extends Thread{@Override//2.重写run方法public void run() {//书写线程要执行代码for (int i = 0; i < 100; i++) {System.out.println(getName() + "HelloWorld");}} }public class ThreadDemo {public static void main(String[] args) {//3.创建MyThread对象MyThread t1 = new MyThread();MyThread t2 = new MyThread();t1.setName("线程1");t2.setName("线程2");//4.启动线程t1.start();t2.start();} } -
两个小问题
-
为什么要重写run()方法?
因为run()是用来封装被线程执行的代码
-
run()方法和start()方法的区别?
run():封装线程执行的代码,直接调用,相当于普通方法的调用
start():启动线程;然后由JVM调用此线程的run()方法
-
1.2.2 方式二:实现Runnable接口
-
Thread构造方法
方法名 说明 Thread(Runnable target) 分配一个新的Thread对象 Thread(Runnable target, String name) 分配一个新的Thread对象 -
实现步骤
- 定义一个类MyRunnable实现Runnable接口
- 在MyRunnable类中重写run()方法
- 创建MyRunnable类的对象
- 创建Thread类的对象,把MyRunnable对象作为构造方法的参数
- 启动线程
-
代码演示
public class MyRunnable implements Runnable {@Overridepublic void run() {for(int i=0; i<100; i++) {System.out.println(Thread.currentThread().getName()+":"+i);}} } public class MyRunnableDemo {public static void main(String[] args) {//创建MyRunnable类的对象MyRunnable my = new MyRunnable();//创建Thread类的对象,把MyRunnable对象作为构造方法的参数//Thread(Runnable target) // Thread t1 = new Thread(my); // Thread t2 = new Thread(my);//Thread(Runnable target, String name)Thread t1 = new Thread(my,"坦克");Thread t2 = new Thread(my,"飞机");//启动线程t1.start();t2.start();} }
1.2.3方式三: 实现Callable接口
-
方法介绍
方法名 说明 V call() 计算结果,如果无法计算结果,则抛出一个异常 FutureTask(Callable callable) 创建一个 FutureTask,一旦运行就执行给定的 Callable V get() 如有必要,等待计算完成,然后获取其结果 -
实现步骤
- 定义一个类MyCallable实现Callable接口
- 在MyCallable类中重写call()方法(是有返回值的,表示多线程的运行结果)
- 创建MyCallable类的对象(表示多线程要执行的任务)
- 创建Future的实现类FutureTask对象,把MyCallable对象作为构造方法的参数(作用是管理多线程运行的结果)
- 创建Thread类的对象,把FutureTask对象作为构造方法的参数
- 启动线程
- 再调用get方法,就可以获取线程结束之后的结果。
-
代码演示
public class MyCallable implements Callable<Integer> {@Overridepublic Integer call() throws Exception {//求1~100之间的和int sum = 0;for (int i = 1; i <= 100; i++) {sum = sum + i;}return sum;} }public class ThreadDemo {public static void main(String[] args) throws ExecutionException, InterruptedException {//创建MyCallable的对象(表示多线程要执行的任务)MyCallable mc = new MyCallable();//创建FutureTask的对象(作用管理多线程运行的结果)FutureTask<Integer> ft = new FutureTask<>(mc);//创建线程的对象Thread t1 = new Thread(ft);//启动线程t1.start();//获取多线程运行的结果Integer result = ft.get();System.out.println(result);} } -
三种实现方式的对比
- 实现Runnable、Callable接口
- 好处: 扩展性强,实现该接口的同时还可以继承其他的类
- 缺点: 编程相对复杂,不能直接使用Thread类中的方法
- 继承Thread类
- 好处: 编程比较简单,可以直接使用Thread类中的方法
- 缺点: 可以扩展性较差,不能再继承其他的类
- 实现Runnable、Callable接口
1.3 常见的成员方法
1.3.1 设置和获取线程名称
-
方法介绍
方法名 说明 void setName(String name) 将此线程的名称更改为等于参数name String getName() 返回此线程的名称 Thread currentThread() 返回对当前正在执行的线程对象的引用 -
String getName() 返回此线程的名称
-
void setName(String name) 设置线程的名字(构造方法也可以设置名字)
- 如果我们没有给线程设置名字,线程也是有默认的名字的
格式:Thread-X(X序号,从0开始的) - 如果我们要给线程设置名字,可以用set方法进行设置,也可以构造方法设置
- 如果我们没有给线程设置名字,线程也是有默认的名字的
-
static Thread currentThread() 获取当前线程的对象
- 当JVM虚拟机启动之后,会自动的启动多条线程
- 其中有一条线程就叫做main线程,他的作用就是去调用main方法,并执行里面的代码
- 在以前,我们写的所有的代码,其实都是运行在main线程当中
-
代码演示
public class MyThread extends Thread {public MyThread() {}public MyThread(String name) {super(name);}@Overridepublic void run() {for (int i = 0; i < 100; i++) {System.out.println(getName()+":"+i);}} } public class MyThreadDemo {public static void main(String[] args) {MyThread my1 = new MyThread();MyThread my2 = new MyThread();//void setName(String name):将此线程的名称更改为等于参数 namemy1.setName("高铁");my2.setName("飞机");//Thread(String name)MyThread my1 = new MyThread("高铁");MyThread my2 = new MyThread("飞机");my1.start();my2.start();//static Thread currentThread() 返回对当前正在执行的线程对象的引用System.out.println(Thread.currentThread().getName());//main} }
1.3.2 线程休眠
-
相关方法
方法名 说明 static void sleep(long millis) 使当前正在执行的线程停留(暂停执行)指定的毫秒数 -
static void sleep(long time) 让线程休眠指定的时间,单位为毫秒
- 哪条线程执行到这个方法,那么哪条线程就会在这里停留对应的时间
- 方法的参数:就表示睡眠的时间,单位毫秒 1 秒= 1000毫秒
- 当时间到了之后,线程会自动的醒来,继续执行下面的其他代码
-
代码演示
public class MyThread extends Thread{public MyThread() {}public MyThread(String name) {super(name);}@Overridepublic void run() {for (int i = 0; i < 100; i++) {try {//每打印一次要休眠一秒Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(getName() + "@" + i);}} }public class ThreadDemo {public static void main(String[] args) throws InterruptedException {//1.创建线程的对象MyThread t1 = new MyThread("飞机");MyThread t2 = new ("坦克");//2.开启线程t1.start();t2.start();} }
1.3.3 线程优先级
-
线程调度
-
两种调度方式
- 分时调度模型:所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片
- 抢占式调度模型:优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的 CPU 时间片相对多一些
-
Java使用的是抢占式调度模型
-
随机性
假如计算机只有一个 CPU,那么 CPU 在某一个时刻只能执行一条指令,线程只有得到CPU时间片,也就是使用权,才可以执行指令。所以说多线程程序的执行是有随机性,因为谁抢到CPU的使用权是不一定的
-
-
优先级相关方法
方法名 说明 final int getPriority() 返回此线程的优先级 final void setPriority(int newPriority) 更改此线程的优先级线程默认优先级是5;线程优先级的范围是:1-10 数值越大优先级越大,抢占cpu的概率越大
-
代码演示
public class MyCallable implements Callable<String> {@Overridepublic String call() throws Exception {for (int i = 0; i < 100; i++) {System.out.println(Thread.currentThread().getName() + "---" + i);}return "线程执行完毕了";} } public class Demo {public static void main(String[] args) {//优先级: 1 - 10 默认值:5MyCallable mc = new MyCallable();FutureTask<String> ft = new FutureTask<>(mc);Thread t1 = new Thread(ft);t1.setName("飞机");t1.setPriority(10);//System.out.println(t1.getPriority());//5t1.start();MyCallable mc2 = new MyCallable();FutureTask<String> ft2 = new FutureTask<>(mc2);Thread t2 = new Thread(ft2);t2.setName("坦克");t2.setPriority(1);//System.out.println(t2.getPriority());//5t2.start();} }
1.3.4 守护线程
-
相关方法
方法名 说明 void setDaemon(boolean on) 将此线程标记为守护线程,当运行的线程都是守护线程时,Java虚拟机将退出 -
代码演示
public class MyThread1 extends Thread {@Overridepublic void run() {for (int i = 0; i < 10; i++) {System.out.println(getName() + "---" + i);}} } public class MyThread2 extends Thread {@Overridepublic void run() {for (int i = 0; i < 100; i++) {System.out.println(getName() + "---" + i);}} } public class Demo {public static void main(String[] args) {MyThread1 t1 = new MyThread1();MyThread2 t2 = new MyThread2();t1.setName("女神");t2.setName("备胎");//把第二个线程设置为守护线程//当普通线程执行完之后,那么守护线程也没有继续运行下去的必要了.t2.setDaemon(true);t1.start();t2.start();} }
1.3.5 礼让线程和插队线程
| 方法 | 说明 |
|---|---|
| public static void yield() | 出让线程/礼让线程 |
| public final void join() | 插入线程/插队线程 |
- 礼让线程
尽可能让两个线程执行的均匀一点,但不是绝对的
public class MyThread extends Thread{@Overridepublic void run() {//"飞机" "坦克"for (int i = 1; i <= 100; i++) {System.out.println(getName() + "@" + i);//表示出让当前CPU的执行权Thread.yield();}}
}public class ThreadDemo {public static void main(String[] args) {/*public static void yield() 出让线程/礼让线程*/MyThread t1 = new MyThread();MyThread t2 = new MyThread();t1.setName("飞机");t2.setName("坦克");t1.start();t2.start();}
}
- 插入线程
把土豆线程插入到main线程之前
package a08threadmethod5;public class ThreadDemo {public static void main(String[] args) throws InterruptedException {MyThread t = new MyThread();t.setName("土豆");t.start();//表示把t这个线程,插入到当前线程之前。//t:土豆//当前线程: main线程t.join();//执行在main线程当中的for (int i = 0; i < 10; i++) {System.out.println("main线程" + i);}}
}
1.4 线程的生命周期

调用start()之后变成就绪状态,抢到cpu执行权才能到运行状态,当run()里面的程序执行完毕就进入死亡状态
当遇到sleep()或者其他阻塞方法会进入阻塞状态,阻塞状态结束之后会变成就绪状态
2. 线程同步
2.1卖票
-
案例需求
某电影院目前正在上映国产大片,共有100张票,而它有3个窗口卖票,请设计一个程序模拟该电影院卖票
-
代码实现
public class MyThread extends Thread {//表示这个类所有的对象,都共享ticket数据static int ticket = 0;//0 ~ 99@Overridepublic void run() {while (true) {//同步代码块if (ticket < 100) {try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}ticket++;System.out.println(getName() + "正在卖第" + ticket + "张票!!!");} else {break;}}}}public class ThreadDemo {public static void main(String[] args) {//创建线程对象MyThread t1 = new MyThread();MyThread t2 = new MyThread();MyThread t3 = new MyThread();//起名字t1.setName("窗口1");t2.setName("窗口2");t3.setName("窗口3");//开启线程t1.start();t2.start();t3.start();} }
卖票案例引发的安全问题
-
卖票出现了问题
- 相同的票出现了多次
- 出现了负数的票
-
问题产生原因
线程执行的随机性导致的,可能在卖票过程中丢失cpu的执行权,导致出现问题
2.2 同步代码块
-
安全问题出现的条件
- 是多线程环境
- 有共享数据
- 有多条语句操作共享数据
-
如何解决多线程安全问题呢?
- 基本思想:让程序没有安全问题的环境
-
怎么实现呢?
- 把多条语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行即可
- Java提供了同步代码块的方式来解决
-
同步代码块格式:
synchronized(任意对象) { 多条语句操作共享数据的代码 }synchronized(任意对象):就相当于给代码加锁了,任意对象就可以看成是一把锁
-
同步的好处和弊端
- 好处:解决了多线程的数据安全问题
- 弊端:当线程很多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率
-
代码演示
public class MyThread extends Thread {//表示这个类所有的对象,都共享ticket数据static int ticket = 0;//0 ~ 99@Overridepublic void run() {while (true) {//锁对象一定是唯一的,Mythread.class是唯一的字节码文件对象synchronized (MyThread.class) {//同步代码块if (ticket < 100) {try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}ticket++;System.out.println(getName() + "正在卖第" + ticket + "张票!!!");} else {break;}}}} }public class ThreadDemo {public static void main(String[] args) {//创建线程对象MyThread t1 = new MyThread();MyThread t2 = new MyThread();MyThread t3 = new MyThread();//起名字t1.setName("窗口1");t2.setName("窗口2");t3.setName("窗口3");//开启线程t1.start();t2.start();t3.start();} }
2.3 同步方法
同步代码块就是把一段代码给锁起来,解决多线程操作共享数据带来的安全问题
如果想要把一个方法里面的代码全都锁起来,可以直接把synchronized加在方法上
-
同步方法的格式
同步方法:就是把synchronized关键字加到方法上
修饰符 synchronized 返回值类型 方法名(方法参数) { 方法体; }同步方法的锁对象是什么呢?
this
-
静态同步方法
同步静态方法:就是把synchronized关键字加到静态方法上
修饰符 static synchronized 返回值类型 方法名(方法参数) { 方法体; }同步静态方法的锁对象是什么呢?
类名.class
-
代码演示
public class MyRunnable implements Runnable {int ticket = 0;@Overridepublic void run() {//1.循环while (true) {//2.同步代码块(同步方法)if (method()) break;}}//thisprivate synchronized boolean method() {//3.判断共享数据是否到了末尾,如果到了末尾if (ticket == 100) {return true;} else {//4.判断共享数据是否到了末尾,如果没有到末尾try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}ticket++;System.out.println(Thread.currentThread().getName() + "在卖第" + ticket + "张票!!!");}return false;} }public class ThreadDemo {public static void main(String[] args) {MyRunnable mr = new MyRunnable();Thread t1 = new Thread(mr);Thread t2 = new Thread(mr);Thread t3 = new Thread(mr);t1.setName("窗口1");t2.setName("窗口2");t3.setName("窗口3");t1.start();t2.start();t3.start();} }
2.4 Lock锁
虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock
Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化
-
ReentrantLock构造方法
方法名 说明 ReentrantLock() 创建一个ReentrantLock的实例 -
加锁解锁方法
方法名 说明 void lock() 获得锁 void unlock() 释放锁 -
代码演示
public class Ticket implements Runnable {//票的数量private int ticket = 100;private Object obj = new Object();private ReentrantLock lock = new ReentrantLock();@Overridepublic void run() {while (true) {//synchronized (obj){//多个线程必须使用同一把锁.try {lock.lock();if (ticket <= 0) {//卖完了break;} else {Thread.sleep(100);ticket--;System.out.println(Thread.currentThread().getName() + "在卖票,还剩下" + ticket + "张票");}} catch (InterruptedException e) {e.printStackTrace();} finally {lock.unlock();}// }}} }public class Demo {public static void main(String[] args) {Ticket ticket = new Ticket();Thread t1 = new Thread(ticket);Thread t2 = new Thread(ticket);Thread t3 = new Thread(ticket);t1.setName("窗口一");t2.setName("窗口二");t3.setName("窗口三");t1.start();t2.start();t3.start();} }
2.5 死锁
-
概述
线程死锁是指由于两个或者多个线程互相持有对方所需要的资源,导致这些线程处于等待状态,无法前往执行
-
什么情况下会产生死锁
- 资源有限
- 同步嵌套
-
代码演示
package a12deadlock;public class MyThread extends Thread {static Object objA = new Object();static Object objB = new Object();@Overridepublic void run() {//1.循环while (true) {if ("线程A".equals(getName())) {synchronized (objA) {System.out.println("线程A拿到了A锁,准备拿B锁");//Asynchronized (objB) {System.out.println("线程A拿到了B锁,顺利执行完一轮");}}} else if ("线程B".equals(getName())) {if ("线程B".equals(getName())) {synchronized (objB) {System.out.println("线程B拿到了B锁,准备拿A锁");//Bsynchronized (objA) {System.out.println("线程B拿到了A锁,顺利执行完一轮");}}}}}} }package a12deadlock;public class ThreadDemo {public static void main(String[] args) {/*需求:死锁*/MyThread t1 = new MyThread();MyThread t2 = new MyThread();t1.setName("线程A");t2.setName("线程B");t1.start();t2.start();} }
2.6.生产者消费者(等待唤醒机制)
2.6.1 生产者和消费者机制
-
概述
生产者消费者模式是一个十分经典的多线程协作的模式
所谓生产者消费者问题,实际上主要是包含了两类线程:
一类是生产者线程用于生产数据
一类是消费者线程用于消费数据
为了解耦生产者和消费者的关系,通常会采用共享的数据区域,就像是一个仓库
生产者生产数据之后直接放置在共享数据区中,并不需要关心消费者的行为
消费者只需要从共享数据区中去获取数据,并不需要关心生产者的行为
核心:利用这个共享的数据区域来控制线程的执行
-
生产者消费者和机制分析
第一种情况,消费者等待:

第二种情况,加上生产者等待的情况:

由此得到完整的案例:

- Object类的等待和唤醒方法
| 方法名 | 说明 |
|---|---|
| void wait() | 导致当前线程等待,直到另一个线程调用该对象的 notify()方法或 notifyAll()方法 |
| void notify() | 唤醒正在等待对象监视器的单个线程 |
| void notifyAll() | 唤醒正在等待对象监视器的所有线程 |
-
代码实现
package demo;public class Desk {//0表示没有面条public static int foodFlag=0;//是程序是否到达末尾的标志,吃货只能吃十碗,吃到第十碗就结束public static int count=10;//唯一的锁对象public static Object lock=new Object();}package demo;public class Foodie extends Thread{//消费者//1 循环//2 锁//3 判断是否到末尾//4 没有到末尾,执行核心代码@Overridepublic void run() {while(true){synchronized (Desk.lock){//都末尾if(Desk.count==0){break;//没到末尾,执行核心代码}else {//先写让消费者等待的情况//如果桌上没有面条,那么消费者就要等if(Desk.foodFlag==0){try {Desk.lock.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}else {//消费者不需要等待,那就是桌子上有面条,可以吃,吃完之后唤醒生产者继续做面条Desk.count--;System.out.println("吃货正在吃,还能再吃"+Desk.count+"碗");Desk.foodFlag=0;Desk.lock.notifyAll();}}}}} }package demo;public class Cook extends Thread{//生产者@Overridepublic void run() {while(true){synchronized (Desk.lock){if(Desk.count==0){break;}else {if(Desk.foodFlag==1){try {Desk.lock.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}else {System.out.println("厨师正在做一碗面条");Desk.foodFlag=1;Desk.lock.notifyAll();}}}}} }package demo;public class Test {public static void main(String[] args) {Foodie f=new Foodie();Cook c=new Cook();c.setName("厨师");f.setName("吃货");f.start();c.start();} }
## 3.4 阻塞队列- 什么是阻塞队列+ 阻塞队列继承结构+ 常见BlockingQueue:ArrayBlockingQueue: 底层是数组,有界LinkedBlockingQueue: 底层是链表,无界.但不是真正的无界,最大为int的最大值+ BlockingQueue的核心方法:**put(anObject): 将参数放入队列,如果放不进去会阻塞****take(): 取出第一个数据,取不到会阻塞**+ 代码示例```javapublic class Demo02 {public static void main(String[] args) throws Exception {// 创建阻塞队列的对象,容量为 1ArrayBlockingQueue<String> arrayBlockingQueue = new ArrayBlockingQueue<>(1);// 存储元素arrayBlockingQueue.put("汉堡包");// 取元素System.out.println(arrayBlockingQueue.take());System.out.println(arrayBlockingQueue.take()); // 取不到会阻塞System.out.println("程序结束了");}}
2.6.2 阻塞队列实现等待唤醒机制
-
代码实现
package a14waitandnotify;import java.util.concurrent.ArrayBlockingQueue;public class Foodie extends Thread{ArrayBlockingQueue<String> queue;public Foodie(ArrayBlockingQueue<String> queue) {this.queue = queue;}@Overridepublic void run() {while(true){//不断从阻塞队列中获取面条try {String food = queue.take();System.out.println(food);} catch (InterruptedException e) {e.printStackTrace();}}} }package a14waitandnotify;import java.util.concurrent.ArrayBlockingQueue;public class Cook extends Thread{ArrayBlockingQueue<String> queue;public Cook(ArrayBlockingQueue<String> queue) {this.queue = queue;}@Overridepublic void run() {while(true){//不断的把面条放到阻塞队列当中try {queue.put("面条");System.out.println("厨师放了一碗面条");} catch (InterruptedException e) {e.printStackTrace();}}} }package a14waitandnotify;import java.util.concurrent.ArrayBlockingQueue;public class ThreadDemo {public static void main(String[] args) {/*** 需求:利用阻塞队列完成生产者和消费者(等待唤醒机制)的代码* 细节:* 生产者和消费者必须使用同一个阻塞队列** *///1.创建阻塞队列的对象ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(1);//2.创建线程的对象,并把阻塞队列传递过去Cook c = new Cook(queue);Foodie f = new Foodie(queue);//3.开启线程c.start();f.start();} }
4 线程的状态
状态被定义在了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);}}
通过源码我们可以看到Java中的线程存在6种状态,每种线程状态的含义如下
| 线程状态 | 具体含义 |
|---|---|
| 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 | 一个完全运行完成的线程的状态。也称之为终止状态、结束状态 |
线程的状态只定义了六种状态,没有定义运行状态
因为在运行状态的时候,线程已经交给java系统,不管了


5. 线程池
5.1 基本原理
概述 :
提到池,大家应该能想到的就是水池。水池就是一个容器,在该容器中存储了很多的水。那么什么是线程池呢?线程池也是可以看做成一个池子,在该池子中存储很多个线程。
线程池存在的意义:
系统创建一个线程的成本是比较高的,因为它涉及到与操作系统交互,当程序中需要创建大量生存期很短暂的线程时,频繁的创建和销毁线程对系统的资源消耗有可能大于业务处理是对系
统资源的消耗,这样就有点"舍本逐末"了。针对这一种情况,为了提高性能,我们就可以采用线程池。线程池在启动的时,会创建大量空闲线程,当我们向线程池提交任务的时,线程池就
会启动一个线程来执行该任务。等待任务执行完毕以后,线程并不会死亡,而是再次返回到线程池中称为空闲状态。等待下一次任务的执行。
线程池的核心思路
① 创建一个池子,池子中是空的
② 提交任务时,池子会创建新的线程对象,任务执行完毕,线程归还给池子下回再次提交任务时,不需要创建新的线程,直接复用已有的线程即可
③ 但是如果提交任务时,池子中没有空闲线程,也无法创建新的线程,任务就会排队等待
5.2 Executors创建线程池
- 概述 :
JDK对线程池也进行了相关的实现,在真实企业开发中我们也很少去自定义线程池,而是使用JDK中自带的线程池。
我们可以使用Executors中所提供的静态方法来创建线程池
| 方法 | 说明 |
|---|---|
| static ExecutorService newCachedThreadPool() | 创建一个默认的线程池 |
| static newFixedThreadPool(int nThreads) | 创建一个指定最多线程数量的线程池 |
- 线程池代码实现步骤:
1,创建线程池
2,提交任务
3,所有的任务全部执行完毕,关闭线程池
- 代码实现 :
public class MyRunnable implements Runnable{@Overridepublic void run() {for (int i = 1; i <= 100; i++) {System.out.println(Thread.currentThread().getName() + "---" + i);}}
}public class MyThreadPoolDemo {public static void main(String[] args) throws InterruptedException {/*public static ExecutorService newCachedThreadPool() 创建一个没有上限的线程池public static ExecutorService newFixedThreadPool (int nThreads) 创建有上限的线程池*///1.获取线程池对象(创建有上限的线程池)ExecutorService pool1 = Executors.newFixedThreadPool(3);//创建无上限的线程池//ExecutorService pool1 = Executors.newCachedThreadPool();//2.提交任务pool1.submit(new MyRunnable());pool1.submit(new MyRunnable());pool1.submit(new MyRunnable());pool1.submit(new MyRunnable());pool1.submit(new MyRunnable());pool1.submit(new MyRunnable());//3.销毁线程池pool1.shutdown();}
}
5.3 ThreadPoolExecutor-自定义线程池

- 创建线程池对象 :
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(核心线程数量,最大线程数量,空闲线程最大存活时间,任务队列,创建线程工厂,任务的拒绝策略);
- 对参数的理解
(1) 核心线程数量3,临时线程的数量3,
当我提交5个任务,那么,有三个任务会被核心线程处理,剩下的两个任务会排队等待

(2)核心线程数量3,临时线程数量3,并且队伍长度设置为3
当提交8个任务的时候,前三个任务会被核心线程处理(线程1-3),剩下的线程会在队伍里面等待;但是,三个线程在队伍里面等待已经到达最大长度,其他线程不能再等待。这时候会使用临时线程,执行剩下的两个任务(线程3和线程4)
细节1:临时线程到底什么时候才会使用?
只有当核心线程都在被占用并且等待队伍也排满的时候才会使用临时线程
细节2:任务在执行的时候一定是按照提交的顺序吗?
不是的,如下图,任务4-6还在队伍中等待,但是任务7-8已经在临时线程中执行

(3)提交10个任务,这个时候任务数量>核心线程数量+队伍最大长度+临时线程数量,该怎么办?

当核心线程都在工作,队伍排满,并且临时线程也全都被占用,那么剩下的任务会触发任务的拒绝策略

- 任务拒绝策略
RejectedExecutionHandler是jdk提供的一个任务拒绝策略接口,它下面存在4个子类。
| 任务拒绝策略 | 说明 |
|---|---|
| ThreadPoolExecutor.AbortPolicy | 默认策略:丢弃任务并抛出RejectedExecutionException异常。 |
| ThreadPoolExecutor.DiscardPolicy: | 丢弃任务,但是不抛出异常 这是不推荐的做法。 |
| ThreadPoolExecutor.DiscardOldestPolicy | 抛弃队列中等待最久的任务 然后把当前任务加入队列中。 |
| ThreadPoolExecutor.CallerRunsPolicy: | 调用任务的run()方法绕过线程池直接执行。 |
如果在上面提交10个任务的情况中使用第三种任务拒绝策略(DiscardOldestPolicy),那么将队伍当中的任务4抛弃,并将任务10加入到队列
- 自定义线程池的代码示例:
package mythreadpool.a02threadpool2;import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;public class MyThreadPoolDemo1 {public static void main(String[] args){/*ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(核心线程数量,最大线程数量,空闲线程最大存活时间,任务队列,创建线程工厂,任务的拒绝策略);参数一:核心线程数量 不能小于0参数二:最大线程数 不能小于0,最大数量 >= 核心线程数量参数三:空闲线程最大存活时间 不能小于0参数四:时间单位 用TimeUnit指定参数五:任务队列 不能为null参数六:创建线程工厂 不能为null参数七:任务的拒绝策略 不能为null*/ThreadPoolExecutor pool = new ThreadPoolExecutor(3, //核心线程数量,能小于06, //最大线程数,不能小于0,最大数量 >= 核心线程数量60,//空闲线程最大存活时间TimeUnit.SECONDS,//时间单位(秒)new ArrayBlockingQueue<>(3),//任务队列Executors.defaultThreadFactory(),//创建线程工厂new ThreadPoolExecutor.AbortPolicy()//任务的拒绝策略);}
}
- 线程池多大合适?
什么是最大并行数?,什么是cpu的四核八线程?
相当于cpu有4个大脑,因为拥有超线程技术,将4个大脑虚拟成八个,针对四核八线程的电脑的,最大并行数是8
cpu密集型运算:项目当中计算比较多,但是读取文件和读取数据库的操作比较少
I/O密集型运算:项目中读取文件或者数据库操作比较多


相关文章:
JAVA基础-多线程线程池
文章目录 1. 多线程1.1什么是多线程(1)并发和并行(2)进程和线程 1.2多线程的实现方式1.2.1 方式一:继承Thread类1.2.2 方式二:实现Runnable接口1.2.3方式三: 实现Callable接口 1.3 常见的成员方法1.3.1 设置…...
HarmonyOS 沉浸式状态实现的多种方式
1. HarmonyOS 沉浸式状态实现的多种方式 HarmonyOS 沉浸式状态实现的多种方式 1.1. 方法一 1.1.1. 实现讲解 (1)首先设置setWindowLayoutFullScreen(true)(设置全屏布局)。 布局将从屏幕最顶部开始到最底部结束,…...
Python3.11.9下载和安装
Python3.11.9下载和安装 1、下载 下载地址:https://www.python.org/downloads/windows/ 选择版本下载,例如:Python 3.11.9 - April 2, 2024 2、安装 双击exe安装 3、配置环境变量 pathD:\Program Files\python3.11.9...
简简单单的UDP
前言 上一篇了解了TCP的三次握手过程,目的、以及如何保证可靠性、序列号与ACK的作用,最后离开的时候四次挥手的内容,这还只是TCP内容中的冰山一角,是不是觉得TCP这个协议非常复杂,这一篇我们来了解下传输层另外一个协…...
减少 try...catch,定义全局统一异常处理器!
前言 软件开发springboot项目过程中,不可避免的需要处理各种异常,spring mvc 架构中各层会出现大量的try {...} catch {...} finally {...}代码块,不仅有大量的冗余代码,而且还影响代码的可读性。这样就需要定义个全局统一异常处理器&#x…...
多点支撑:滚珠导轨的均匀分布优势!
滚珠导轨的滚珠稳定性可以有效保持滚珠导轨的稳定运行,减少滚珠脱落的风险,确保设备的长期稳定性和可靠性。事实上,滚珠导轨的滚珠稳定性主要依赖于以下几个方面: 1、精密的制造工艺:滚珠导轨的导轨和滑块通常采用高精…...
电气火灾探测器在商场火灾隐患监测和火灾预防中的应用
徐悦 安科瑞电气股份有限公司 近年来,全国火灾事故频发,尤其是在大型商场等公共场所,火灾造成了巨大的人员伤亡和财产损失。以南京金盛百货中央门店火灾为例,该起事故暴露了商场在电气安全、消防管理方面的重大隐患,…...
速盾:如何有效防止服务器遭受攻击?
服务器攻击是网络安全中常见的问题,但我们可以采取一系列的措施来有效防止服务器的遭受攻击。以下是一些常见的防御措施: 更新和维护服务器软件:及时更新操作系统、应用程序以及安全补丁,以确保最新版本的软件没有已知的漏洞。同时…...
【今日更新】使用Python辅助处理WebGIS
Linux发行版本: Debian GNU/Linux 12 (bookworm)操作系统内核: Linux-6.1.0-18-amd64-x86_64-with-glibc2.36Python版本: 3.11.2 1.使用Python处理MapServer配置文件Mapfile 创建、分析、修改和格式化的python库 MapServer Mapfiles。 Python 2和3 兼容 纯Python-无MapServer依…...
Linux 消息队列
在Linux中,线程间消息队列可以通过使用System V消息队列或POSIX消息队列来实现。 使用System V消息队列: System V消息队列是一种基于IPC(Inter-process Communication,进程间通信)的通信机制,可以用于进程…...
十大经典排序算法-冒泡算法详解介绍
1、十大经典排序算法 排序算法是《数据结构与算法》中最基本的算法之一。 排序算法可以分为内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部的排序记录,在排序过程中需要…...
delphi 编译多语言工程 error RC2104 : undefined keyword or key name:
Delphi 10.3中建立多语言工程,编译时出现错误:error RC2104 : undefined keyword or key name: 出现错误的的文件是.rc文件,出现错误的位置是 System_JSONConsts_SInvalidJavascriptQuote, L"Invalid JavaScript string quote character…...
[python] 如何debug python脚本中C++后端的core dump
文章目录 Debug过程Reference Debug过程 另外:对于core dump: gdb版本是>7,gdb从版本7开始支持对Python的debug。确保你的系统中安装了 GDB 调试器和对应版本的 Python 调试信息包(例如 python-dbg 或 python-debuginfo)。 #…...
Ecmascript(ES)标准
Ecmascript(ES)标准 ECMAScript(通常简称为 ES)是一种标准化的脚本语言,由 Ecma International 通过 ECMA-262 标准定义。ECMAScript 是 JavaScript 的规范版本,几乎所有的现代浏览器和许多服务器端环境&a…...
易泊车牌识别相机:4S 店的智能之选
在当今数字化时代,科技的进步不断为各个行业带来更高效、便捷的解决方案。对于 4S 店来说,易泊车牌识别相机的出现,无疑为其运营管理带来了全新的变革。 一、易泊车牌识别相机的强大功能 易泊车牌识别相机以其卓越的性能和精准的识别能力&…...
Webpack 深度解析与实战指南
文章目录 前言一、安装于基本配置安装Webpack 和 Webpack CLI创建基本配置文件 二、加载器常见的加载器配置加载器 三、插件(Plugins)常用的插件配置插件 四、性能优化缓存代码分割Tree Shaking压缩 五、开发服务器安装服务器配置服务器启动服务器生产环…...
【RabbitMQ】06-消费者的可靠性
1. 消费者确认机制 没有ack,mq就会一直保留消息。 spring:rabbitmq:listener:simple:acknowledge-mode: auto # 自动ack2. 失败重试机制 当消费者出现异常后,消息会不断requeue(重入队)到队列,再重新发送给消费者。…...
【K8S系列】如何监控集群CPU使用率并设置告警的分析与详细解决方案
监控 Kubernetes 集群的 CPU 使用率并设置告警是确保集群健康和性能的关键。以下是几种常见的方案,每种方案的具体步骤都进行了详细说明。 方案 1: 使用 Prometheus 和 Grafana 1. 安装 Prometheus 和 Grafana 1.1 使用 Helm 安装 Prometheus 添加 Helm 仓库: hel…...
解线性方程组(二)
实验类型:●验证性实验 ○综合性实验 ○设计性实验 实验目的:进一步熟练掌握用Jacobi迭代法和Gauss-Seidel法解线性方程组的算法,提高编程能力和解算线性方程组问题的实践技能。 实验内容: 1)取初值性x(0)(0,0,0,0)T, 精度要求ε…...
HarmonyOS Next 实战卡片开发 02
HarmonyOS Next 实战卡片开发 02 卡片开发中,还有一个难点是显示图片。其中分为显示本地图片和显示网络图片 显示本地图片 卡片可以显示本地图片,如存放在应用临时目录下的图片。路径比如 /data/app/el2/100/base/你的项目boundleName/temp/123.png 以…...
Vue记事本应用实现教程
文章目录 1. 项目介绍2. 开发环境准备3. 设计应用界面4. 创建Vue实例和数据模型5. 实现记事本功能5.1 添加新记事项5.2 删除记事项5.3 清空所有记事 6. 添加样式7. 功能扩展:显示创建时间8. 功能扩展:记事项搜索9. 完整代码10. Vue知识点解析10.1 数据绑…...
智能在线客服平台:数字化时代企业连接用户的 AI 中枢
随着互联网技术的飞速发展,消费者期望能够随时随地与企业进行交流。在线客服平台作为连接企业与客户的重要桥梁,不仅优化了客户体验,还提升了企业的服务效率和市场竞争力。本文将探讨在线客服平台的重要性、技术进展、实际应用,并…...
NFT模式:数字资产确权与链游经济系统构建
NFT模式:数字资产确权与链游经济系统构建 ——从技术架构到可持续生态的范式革命 一、确权技术革新:构建可信数字资产基石 1. 区块链底层架构的进化 跨链互操作协议:基于LayerZero协议实现以太坊、Solana等公链资产互通,通过零知…...
UR 协作机器人「三剑客」:精密轻量担当(UR7e)、全能协作主力(UR12e)、重型任务专家(UR15)
UR协作机器人正以其卓越性能在现代制造业自动化中扮演重要角色。UR7e、UR12e和UR15通过创新技术和精准设计满足了不同行业的多样化需求。其中,UR15以其速度、精度及人工智能准备能力成为自动化领域的重要突破。UR7e和UR12e则在负载规格和市场定位上不断优化…...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...
以光量子为例,详解量子获取方式
光量子技术获取量子比特可在室温下进行。该方式有望通过与名为硅光子学(silicon photonics)的光波导(optical waveguide)芯片制造技术和光纤等光通信技术相结合来实现量子计算机。量子力学中,光既是波又是粒子。光子本…...
Selenium常用函数介绍
目录 一,元素定位 1.1 cssSeector 1.2 xpath 二,操作测试对象 三,窗口 3.1 案例 3.2 窗口切换 3.3 窗口大小 3.4 屏幕截图 3.5 关闭窗口 四,弹窗 五,等待 六,导航 七,文件上传 …...
嵌入式常见 CPU 架构
架构类型架构厂商芯片厂商典型芯片特点与应用场景PICRISC (8/16 位)MicrochipMicrochipPIC16F877A、PIC18F4550简化指令集,单周期执行;低功耗、CIP 独立外设;用于家电、小电机控制、安防面板等嵌入式场景8051CISC (8 位)Intel(原始…...
规则与人性的天平——由高考迟到事件引发的思考
当那位身着校服的考生在考场关闭1分钟后狂奔而至,他涨红的脸上写满绝望。铁门内秒针划过的弧度,成为改变人生的残酷抛物线。家长声嘶力竭的哀求与考务人员机械的"这是规定",构成当代中国教育最尖锐的隐喻。 一、刚性规则的必要性 …...
【汇编逆向系列】六、函数调用包含多个参数之多个整型-参数压栈顺序,rcx,rdx,r8,r9寄存器
从本章节开始,进入到函数有多个参数的情况,前面几个章节中介绍了整型和浮点型使用了不同的寄存器在进行函数传参,ECX是整型的第一个参数的寄存器,那么多个参数的情况下函数如何传参,下面展开介绍参数为整型时候的几种情…...
