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 阻塞队列- 什么是阻塞队列![](img/阻塞队列.png)+ 阻塞队列继承结构![06_阻塞队列继承结构](.\img\06_阻塞队列继承结构.png)+ 常见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 以…...
FastDDS服务发现之PDP的收发
目录 PDP发送PDP接收EDP更新 EntityID 通过FastDDS服务发现之PDP和EDP的创建这一节内容,可以了解服务发现的概念,机制和PDP/EDP中各类对象的创建,本文详细介绍Simple PDP发送数据,接收数据和处理报文的流程。 PDP发送 通过在RTP…...
【计网不挂科】计算机网络期末考试——【选择题&填空题&判断题&简述题】试卷(2)
前言 大家好吖,欢迎来到 YY 滴计算机网络 系列 ,热烈欢迎! 本章主要内容面向接触过C的老铁 本博客主要内容,收纳了一部门基本的计算机网络题目,供yy应对期中考试复习。大家可以参考 本章是去答案版本。带答案的版本在下…...
关于有机聚合物铝电容的使用(2)
在使用时需要特别注意的几个应用场景: 在有较长供电电缆或PCB电源布线较长的场合。 这个场景应当仍与有机聚合物铝电容的耐压有关。 假设在相同的冲击电流下,较长的供电电缆和PCB布线,那么电缆和PCB布线上产生的冲击电压就会越高。故而&…...
Linux -- 进程初印象
目录 预备知识 切入点 PCB 看见进程 pid getpid 函数 预备知识 Linux -- 冯诺依曼体系结构(硬件)-CSDN博客https://blog.csdn.net/2301_76973016/article/details/143598784?spm1001.2014.3001.5501 Linux -- 操作系统(软件…...
【超级简单】Facebook脸书视频下载一键保存手机
Facebook作为目前服务全球30亿用户,尤其是出海和跨境用户没有办法忽视的平台,提供了一个在线平台,使用户分享照片、视频、状态更新和链接等内容,然而,令人遗憾的是,用户没有办法直接将照片和视频保存到本地…...
昇思大模型平台打卡体验活动:项目2基于MindSpore通过GPT实现情感分类
昇思大模型平台打卡体验活动:项目2基于MindSpore通过GPT实现情感分类 1. 载入与处理数据集 在情感分类任务中,我们使用了IMDB数据集,首先需要对数据进行加载和处理。由于原数据集没有验证集,我们将训练集重新划分为训练集和验证…...
【JAVA】会员等级互通匹配数据库表设计
1、使用数据库:mysql数据库 设计四张表: 会员互通合作商配置表 会员互通合作商会员等级配置表 会员互通合作日志表 会员互通合作等级映射表 CREATE TABLE user_level_partner ( id bigint NOT NULL AUTO_INCREMENT, partner_novarchar(100) DE…...
论文阅读:基于语义分割的非结构化田间道路场景识别
论文地址:DOI: 10.11975/j.issn.1002-6819.2021.22.017 概要 环境信息感知是智能农业装备系统自主导航作业的关键技术之一。农业田间道路复杂多变,快速准确地识别可通行区域,辨析障碍物类别,可为农业装备系统高效安全地进行路径规…...
linux部分问题以及解决方式
目录 1.ubuntu桌面不显示了,只有命令行1.1启动gdm3服务1.2安装lightdm桌面管理包 1.ubuntu桌面不显示了,只有命令行 有如下两种解决方式。 1.1启动gdm3服务 这种方法只能临时生效,每次重启都要手动启动 sudo service gdm3 restart 1.2安装…...
qt QTreeWidget详解
1、概述 QTreeWidget 是 Qt 框架中的一个类,用于以树形结构展示数据。它基于 QTreeView 并提供了更高级别的接口,使得添加、删除和管理树形结构中的项变得更加简单。QTreeWidget 支持多级嵌套,每个项(QTreeWidgetItem)…...
盐城网站建设包括哪些/搜索百度网址网页
展开全部修改TTF字体方法如下:所需工具:Font creator首先运行Font Creator程序,打开一个已有的32313133353236313431303231363533e78988e69d8331333337626263字体文件,选择你要修复的字体,如图选择“格式-命…...
真人性做爰免费网站/中国十大流量网站
资源 ⭐ ⭐ ⭐ 欢迎点个小小的Star支持!⭐ ⭐ ⭐ 开源不易,希望大家多多支持~ 更多CV和NLP中的transformer模型(BERT、ERNIE、ViT、DeiT、Swin Transformer等)、深度学习资料,请参考:awesome-DeepLearning 更多的预训练语言模型…...
网站建站域名解析最后做/北京网络营销招聘
选择目标编译固件平台 cd ~/source 编辑前检查 make defconfig make prereq 设置固件默认大小及自定义新硬件 想生成自定义名称固件、机器型号需要修改8处地方 主板CPU是MT7620N 硬件是和ZBT WR8305RT的硬件一样 板子上的真实型号是JGX-X5 固件容量: 16M 十六进制&a…...
wordpress移动端顶部导航/如何提升网站搜索排名
-- 锐捷"智能工厂方案"助力柳钢集团MES项目成功落地 在《中国制造2025》的战略指引下,我国的钢铁企业正在钢铁制造信息化、数字化与制造技术融合发展的进程中,迎来新的变革与发展。 在2016年工信部公布的两化融合管理体系贯标试点企业名单中&a…...
自己做局域网站/推广软文发布平台
从今天开始进入C的学习历程,希望在整理笔记,梳理知识,加强记忆的同时也能够帮助到大家。在以后的岁月里希望与大家共同学习,不断进步,积极思考,好好睡觉😁 目录 1.C的由来 2.C关键字 3.命名空间…...
wordpress 通配符替换/网址导航
disabled可以让input不提交,但displaynone不行; 且disabled必须逐个写在input标签上,写在input外面的div上是不起作用的。...