文章目录
- 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密集型运算:项目中读取文件或者数据库操作比较多