Thread多线程(创建,方法,安全,通信,线程池,并发,并行,线程的生命周期)【全详解】
目录
1.多线程概述
2.多线程的创建
3.Thread的常用方法
4.线程安全
5.线程同步
6.线程通信
7.线程池
8.其它细节知识:并发、并行
9.其它细节知识:线程的生命周期
1.多线程概述
线程是什么?
线程(Thread)是一个程序内部的一条执行流程。
程序中如果只有一条执行流程,那这个程序就是单线程的程序。
多线程是什么?
多线程是指从软硬件上实现的多条执行流程的技术(多条线程由CPU负责调度执行)。
2.多线程的创建
方式一:继承Thread类
实现步骤:
定义一个子类MyThread继承线程类java.lang.Thread,重写run()方法
创建MyThread类的对象
调用线程对象的start()方法启动线程(启动后还是执行run方法的)
代码实现
package com.itheima.day11.teacher.thread01;
/*start() 开启新线程的方法 --- 线程开启功能run() 线程执行的代码 --- 线程任务继承方法MyThread 既是一个线程对象(负责线程开启) 又是一个线程任务(写需要执行线程任务)耦合性高!!线程对象 -- 线程任务 分离!!*/
public class MyThread extends Thread{@Overridepublic void run() {/*每个新的线程 都循环输出 十次*/for (int i = 0; i < 10; i++) {// 代码执行过程中 可以获取到当前正在运行的线程对象Thread thread = Thread.currentThread();System.out.println("新的线程"+thread.getName()+"正在执行"+i);}}
}-----------------------
package com.itheima.day11.teacher.thread01;public class Test {public static void main(String[] args) {System.out.println("当前是一个程序的入口"+Thread.currentThread().getName());System.out.println("程序入口也是一个线程 这个线程叫主线程..");//主线程执行过程中 再产生新的线程。/*线程的创建方式一1: 创建一个类 继承 Thread线程类2: 手动重写run方法。----就是新的线程对象要执行的内容.3: 创建 子类对象(线程对象)。4: 调用start方法 开启这个新的线程。当开启完了 程序中出现两个线程了 一个是 主线程 一个新建的线程。每个线程将来都是独立的空间。*/MyThread t1 = new MyThread();t1.start();// 开启新的线程for (int i = 0; i < 10; i++) {System.out.println("线程"+Thread.currentThread().getName()+"正在执行"+i);}}
}--------------------
package com.itheima.day11.teacher.thread01;public class Test2 {/*线程的创建方式一1: 创建一个类 继承 Thread线程类2: 手动重写run方法。----就是新的线程对象要执行的内容.3: 创建 子类对象(线程对象)。4: 调用start方法 开启这个新的线程。当开启完了 程序中出现两个线程了 一个是 主线程 一个新建的线程。每个线程将来都是独立的空间。多个线程执行每次执行效果 不尽相同 因为 CPU的高速切换 没有规律Thread 代表线程对象的类Thread.currentThread() 获取 执行当前代码的线程。普通方法getName() 获取线程的名字 名字默认 Thread-0 Thread-1 ....可以设置名字setName("...")*/public static void main(String[] args) {//程序入口也是一个线程 这个线程叫主线程..System.out.println("当前是一个程序的入口"+Thread.currentThread().getName());//主线程执行过程中 再产生新的线程。MyThread t1 = new MyThread();t1.setName("小迪迪");t1.start();// 开启新的线程// 这还是main中for (int i = 0; i < 10; i++) {System.out.println("线程"+Thread.currentThread().getName()+"正在执行"+i);}}
}
优缺点:
优点:编码简单
缺点:存在单继承的局限性,线程类继承Thread后,不能继承其他类,不便于扩展
方式二:实现Runnable接口
实现步骤:
定义一个线程任务类MyRunnable实现Runnable接口,重写run()方法
创建MyRunnable任务对象
把MyRunnable任务对象交给Thread处理。
调用线程对象的start()方法启动线程
package com.itheima.day11.teacher.thread02;
/*线程任务类 里面只有线程任务 run方法*/
public class MyRunnable implements Runnable{@Overridepublic void run() {for (int i = 0; i < 10; i++) {System.out.println("当前在"+Thread.currentThread().getName()+"正在输出:"+i);}}
}-------------------------
package com.itheima.day11.teacher.thread02;public class RunnableTest {/*创建线程方式二1: 定义一个实现Runnable接口的 线程任务类,重写run方法。2: 创建一个线程任务对象。3: 创建线程对象 并在构造中传递线程任务对象。4: 开启新的线程 线程对象.start()*/public static void main(String[] args) {// 创建线程任务对象MyRunnable mr = new MyRunnable();// 创建线程对象的同时 将线程任务传递过去 --- 线程和任务绑定Thread t = new Thread(mr);//调用start方法t.start();//主线程操作for (int i = 0; i <10 ; i++) {System.out.println("当前在"+Thread.currentThread().getName()+"正在输出:"+i);}}
}--------------------
package com.itheima.day11.teacher.thread02;public class RunnableTest2 {/*创建线程方式二1: 定义一个实现Runnable接口的 线程任务类,重写run方法。2: 创建一个线程任务对象。3: 创建线程对象 并在构造中传递线程任务对象。4: 开启新的线程 线程对象.start()*/public static void main(String[] args) {// 创建线程任务对象MyRunnable mr = new MyRunnable();//上面的操作 创建了一个外部类 实现了接口 并创建该外部类的对象 外部类对象---Runnabel接口的实现类对象// 创建线程对象的同时 将线程任务传递过去 --- 线程和任务绑定Thread t1 = new Thread(mr);//mr 是 Runnable接口的实现类对象//调用start方法t1.start();//
// Thread t2 = new Thread(匿名内部类形式) 匿名内部类本质 子类对象Thread t2 = new Thread(new Runnable() {@Overridepublic void run() {for (int i = 0; i < 10; i++) {System.out.println("当前在"+Thread.currentThread().getName()+"正在输出:"+i);}}});t2.start();// 能不能用lambda 可以// lambda 作用简化匿名内部类 使用前提 参数是一个函数式接口 函数式接口 有且只有一个抽象方法的接口new Thread(()->{for (int i = 0; i < 10; i++) {System.out.println("当前在"+Thread.currentThread().getName()+"正在输出:"+i);}}).start();}
}
优缺点:
优点:任务类只是实现接口,可以继续继承其他类、实现其他接口,扩展性强。
缺点:需要多一个Runnable对象。
方式三:实现Callable接口
实现步骤:
1.创建任务对象:
1.定义一个类实现Callable接口,重写call方法,封装要做的事情和要返回的数据。
2.把Callable类型的对象封装成FutureTask对象(线程任务对象)。
2.把线程任务对象封装成Thread对象。
3.调用Thread对象的start方法启动线程。
4.线程执行完毕后、通过FutureTask对象的的get方法去获取线程任务执行的结果。
package com.itheima.day11.teacher.thread03;import java.util.concurrent.Callable;/*Callable接口有个泛型 表示 返回的结果类型。返回值 call*/
public class MyCallable implements Callable<Integer> {// 构造 方法 都可以访问 成员变量private Integer number;public MyCallable(Integer number){// 当能够调用call方法的时候 构造方法执行完了// 方法中没有参数 构造中可不可以设计参数// 把构造中传递的number 赋值给成员变量this.number = number;}//任务 求一个数的 绝对值@Overridepublic Integer call() throws Exception {System.out.println("当前的线程:"+Thread.currentThread().getName());return Math.abs(number);//直接使用}}-----------------------
package com.itheima.day11.teacher.thread03;import java.util.concurrent.Callable;/*Callable接口有个泛型 表示 返回的结果类型。返回值 call*/
public class MyCallable1 implements Callable<String> {//任务 求一个数的 绝对值@Overridepublic String call() throws Exception {System.out.println("当前的线程:"+Thread.currentThread().getName());return "下载任务执行成功";//直接使用}
}-----------------------
package com.itheima.day11.teacher.thread03;import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;public class CallableTest {/*创建线程方式三1:创建子类实现Callable接口 完成call方法重写。2:创建线程任务(自定义callable)对象.3:创建一个 线程任务管理对象 FutureTask封装 callable实现对象。4:创建线程对象 绑定线程任务管理对象5:调用start方法*/public static void main(String[] args) throws ExecutionException, InterruptedException {//创建线程任务对象MyCallable my = new MyCallable(-10);//带参构造//传参给 Thread 创建线程对象
// Thread thread = new Thread(my);// Callable 是一个带有返回值的 任务--需要先交给任务管理对象 因为执行之后该线程是有返回值的 得有对象去处理返回值FutureTask<Integer> task = new FutureTask<>(my);// 在把 task对象 绑定到线程对象中Thread thread = new Thread(task);// 调用start方法thread.start();// 返回值在 Task身上System.out.println("任务执行结果:"+task.get());MyCallable1 callable1 = new MyCallable1();FutureTask<String> stringFutureTask = new FutureTask<String>(callable1);Thread thread1 = new Thread(stringFutureTask);thread1.start();String s = stringFutureTask.get();System.out.println(s);//这样写没意义,不能传参数,只能自己定义吗
/* FutureTask<Integer> integerFutureTask = new FutureTask<>(new Callable<Integer>() {int a=10;int b=0;@Overridepublic Integer call() throws Exception {return a+b;}});new Thread(integerFutureTask).start(); //不执行线程是拿不到返回会值结果的
// new Thread(new FutureTask<Integer>(()-> 10+0 )).start(); 可以这样简写,但是拿不到结果值没意义System.out.println(integerFutureTask.get());*/}
}
3.Thread的常用方法


package com.itheima.day12.teacher.thread01;public class MyThread extends Thread{public MyThread(String name){super(name);//把名字传递给父类的构造}//线程任务方法 将来哪个线程对象执行 在它的代码中就可以得到哪个线程对象@Overridepublic void run() {// 干吕布Thread thread = Thread.currentThread();//获取当前线程对象for (int i = 1; i <=5 ; i++) {//每人跟吕布过招 5次System.out.println(thread.getName()+"大呼:吕布小儿,莫跑~~吃我"+i+"招!!");
// System.out.println(super.getName()+"大呼:吕布小儿,莫跑~~吃我"+i+"招!!");}}
}----------------------
package com.itheima.day12.teacher.thread01;public class ThreadDemo {public static void main(String[] args) throws InterruptedException {//三英战吕布System.out.println("接下来请欣赏 三英战吕布!");// 每秒钟 输出 5 4 3 2 1for (int i = 5; i >=1 ; i--) {System.out.println(i);//休眠一秒Thread.sleep(1000);}MyThread bb = new MyThread("刘备");System.out.println(bb.getName());// bb.setName("刘备");MyThread yy = new MyThread("关羽");System.out.println(yy.getName());MyThread ff = new MyThread("张飞");System.out.println(ff.getName());bb.start();yy.start();ff.start();}
}---------------------
package com.itheima.day12.teacher.thread01;public class ThreadDemo02 {public static void main(String[] args) throws InterruptedException {System.out.println("宇迪和弓箭手 一起打吕布");//创建宇迪线程MyThread my = new MyThread("宇迪");my.start();//宇迪线程执行my.join();//先执行 当前 my线程对象 再执行其他的线程//同时总部 排除弓箭手也对 宇迪进行打击Thread.currentThread().setName("弓箭手");for (int i = 1; i <= 5; i++) {System.out.println(Thread.currentThread().getName()+"在发射第"+i+"只箭~");}}
}
4.线程安全
什么是线程安全问题?
多个线程,同时操作同一个共享资源的时候,可能会出现业务安全问题。
线程安全问题出现的原因?
1.存在多个线程在同时执行
2.多个线程同时访问一个共享资源
3.存在修改该共享资源的情况
用程序模拟线程安全问题
/**
出现的问题两人同时取钱,卡里10万,结果都取成功,卡里-10万
*/package com.itheima.day12.teacher.asynchronize;
// 先定义账户类
public class Account {private String cardId;private double money;//余额public Account() {}public Account(String cardId, double money) {this.cardId = cardId;this.money = money;}/*设计一个取钱的方法参数 取得钱 金额返回值 不需要*/public void drawMoney(double money){// 局部位置money 代表取的钱// 成员位置money 代表余额// this.money -= money;// 知道是谁来取的----哪个线程执行到这里了 谁就是哪个线程String name = Thread.currentThread().getName();// 判断余额是否充足if(this.money >= money){//可以取System.out.println(name+"来取钱:"+money+" 成功!");this.money -= money;System.out.println(name+"取钱之后的余额:"+this.money);}else {System.out.println(name+"来取钱:余额不足,请充值后再去...");}}/*** 获取* @return cardId*/public String getCardId() {return cardId;}/*** 设置* @param cardId*/public void setCardId(String cardId) {this.cardId = cardId;}/*** 获取* @return money*/public double getMoney() {return money;}/*** 设置* @param money*/public void setMoney(double money) {this.money = money;}@Overridepublic String toString() {return "Account{cardId = " + cardId + ", money = " + money + "}";}
}---------------
package com.itheima.day12.teacher.asynchronize;
/*取钱线程类*/
public class DrawMoney extends Thread{// 定义一个账户的成员变量 --初始化交给了构造 由外界传递进来private Account account;// 构造第一个参数 是 取钱的账户对象 第二参数 表示线程的名字public DrawMoney(Account account,String name){super(name);this.account = account;}@Overridepublic void run() {//取钱就是在账户里面 减少余额// 调用账户对象 的 取钱方法// 小明线程 小红线程 操作的 账户是同一个// 在测试类创建 一个账户 传递进来account.drawMoney(100000); // 通过账户对象 取钱 (取的钱数)}
}------------------
package com.itheima.day12.teacher.asynchronize;public class ThreadTest {public static void main(String[] args) {//创建一个账户对象 小明和小红 共享账户 卡号 余额Account account = new Account("ICBC-114",100000);//创建两个线程对象 分别代表小红 和 小明// 线程对象中传递 共享的账户 以及 线程名字new DrawMoney(account,"小明").start();new DrawMoney(account,"小红").start();}
}
5.线程同步
1.认识线程同步: 线程同步是解决线程安全问题的方案
2.线程同步的思想:
1.让多个线程实现先后依次访问共享资源,这样就解决了安全问题。
2.加锁:每次只允许一个线程加锁,加锁后才能进入访问,访问完毕后自动解锁,然后其他线程才能再加锁进来。
3.线程同步的解决方案
方式一:同步代码块
作用:把访问共享资源的核心代码给上锁,以此保证线程安全。
原理:每次只允许一个线程加锁后进入,执行完毕后自动解锁,其他线程才可以进来执行。
写法:
synchronized(同步锁) { 访问共享资源的核心代码 }
同步锁的注意事项:对于当前同时执行的线程来说,同步锁必须是同一把(同一个对象),否则会出bug。
锁对象的使用规范:
建议使用共享资源作为锁对象
对于实例方法建议使用this作为锁对象
对于静态方法建议使用字节码(类名.class)对象作为锁对象
package com.itheima.day12.teacher.synchronize01;
// 先定义账户类
public class Account {private String cardId;private double money;//余额public Account() {}public Account(String cardId, double money) {this.cardId = cardId;this.money = money;}/*设计一个取钱的方法参数 取得钱 金额返回值 不需要*/public void drawMoney(double money){// 使用同步代码块方式加锁 保证两个线程只要一个线程在修改共享资源// 锁对象怎么选择 --- 只要保证 两个线程对象将来公用一把锁synchronized (this){ //() 里面的对象具备唯一性 "lock" 字符串一旦创建不能能改//直接写字符串 可以 但是不规范 开发规范当前共享资源 一般普通方法写 this 静态方法写 类名.class// 局部位置money 代表取的钱// 成员位置money 代表余额// this.money -= money;// 知道是谁来取的----哪个线程执行到这里了 谁就是哪个线程String name = Thread.currentThread().getName();// 判断余额是否充足if(this.money >= money){//可以取System.out.println(name+"来取钱:"+money+" 成功!");this.money -= money;System.out.println(name+"取钱之后的余额:"+this.money);}else {System.out.println(name+"来取钱:余额不足,请充值后再去...");}}}/*** 获取* @return cardId*/public String getCardId() {return cardId;}/*** 设置* @param cardId*/public void setCardId(String cardId) {this.cardId = cardId;}/*** 获取* @return money*/public double getMoney() {return money;}/*** 设置* @param money*/public void setMoney(double money) {this.money = money;}public String toString() {return "Account{cardId = " + cardId + ", money = " + money + "}";}
}-----------------------
package com.itheima.day12.teacher.synchronize01;/*取钱线程类*/
public class DrawMoney extends Thread{// 定义一个账户的成员变量 --初始化交给了构造 由外界传递进来private Account account;// 构造第一个参数 是 取钱的账户对象 第二参数 表示线程的名字public DrawMoney(Account account, String name){super(name);this.account = account;}@Overridepublic void run() {//取钱就是在账户里面 减少余额// 调用账户对象 的 取钱方法// 小明线程 小红线程 操作的 账户是同一个// 在测试类创建 一个账户 传递进来account.drawMoney(100000); // 通过账户对象 取钱 (取的钱数)}
}---------------------
package com.itheima.day12.teacher.synchronize01;public class ThreadTest {public static void main(String[] args) {//创建一个账户对象 小明和小红 共享账户 卡号 余额Account account = new Account("ICBC-114",100000);//创建两个线程对象 分别代表小红 和 小明// 线程对象中传递 共享的账户 以及 线程名字new DrawMoney(account,"小明").start();new DrawMoney(account,"小红").start();}
}
方式二:同步方法
作用:把访问共享资源的核心方法给上锁,以此保证线程安全。
原理:每次只能一个线程进入,执行完毕以后自动解锁,其他线程才可以进来执行。
写法:
修饰符 synchronized 返回值类型 方法名称(形参列表) { 操作共享资源的代码 }
同步方法底层原理:
1.同步方法其实底层也是有隐式锁对象的,只是锁的范围是整个方法代码。
2.如果方法是实例方法:同步方法默认用this作为的锁对象。
3.如果方法是静态方法:同步方法默认用类名.class作为的锁对象。
是同步代码块好还是同步方法好一点?
范围上:同步代码块锁的范围更小,同步方法锁的范围更大。
可读性:同步方法更好。
package com.itheima.day12.teacher.synchronize02;
// 先定义账户类
public class Account {private String cardId;private double money;//余额public Account() {}public Account(String cardId, double money) {this.cardId = cardId;this.money = money;}/*设计一个取钱的方法参数 取得钱 金额返回值 不需要同步方法就是将锁 固定到方法上了 整个方法上锁了方法声明位置 加入 synchronized 关键字*/public synchronized void drawMoney(double money){// 局部位置money 代表取的钱// 成员位置money 代表余额// this.money -= money;// 知道是谁来取的----哪个线程执行到这里了 谁就是哪个线程String name = Thread.currentThread().getName();// 判断余额是否充足if(this.money >= money){//可以取System.out.println(name+"来取钱:"+money+" 成功!");this.money -= money;System.out.println(name+"取钱之后的余额:"+this.money);}else {System.out.println(name+"来取钱:余额不足,请充值后再去...");}}/*** 获取* @return cardId*/public String getCardId() {return cardId;}/*** 设置* @param cardId*/public void setCardId(String cardId) {this.cardId = cardId;}/*** 获取* @return money*/public double getMoney() {return money;}/*** 设置* @param money*/public void setMoney(double money) {this.money = money;}public String toString() {return "Account{cardId = " + cardId + ", money = " + money + "}";}
}-----------------
package com.itheima.day12.teacher.synchronize02;/*取钱线程类*/
public class DrawMoney extends Thread{// 定义一个账户的成员变量 --初始化交给了构造 由外界传递进来private Account account;// 构造第一个参数 是 取钱的账户对象 第二参数 表示线程的名字public DrawMoney(Account account, String name){super(name);this.account = account;}@Overridepublic void run() {//取钱就是在账户里面 减少余额// 调用账户对象 的 取钱方法// 小明线程 小红线程 操作的 账户是同一个// 在测试类创建 一个账户 传递进来account.drawMoney(100000); // 通过账户对象 取钱 (取的钱数)}
}-----------------
package com.itheima.day12.teacher.synchronize02;public class ThreadTest {public static void main(String[] args) {//创建一个账户对象 小明和小红 共享账户 卡号 余额Account account = new Account("ICBC-114",100000);//创建两个线程对象 分别代表小红 和 小明// 线程对象中传递 共享的账户 以及 线程名字new DrawMoney(account,"小明").start();new DrawMoney(account,"小红").start();}
}
方式三:Lock锁
Lock锁是JDK5开始提供的一个新的锁定操作,通过它可以创建出锁对象进行加锁和解锁,更灵活、更方便、更强大。
Lock是接口,不能直接实例化,可以采用它的实现类ReentrantLock来构建Lock锁对象。
package com.itheima.day12.teacher.lock;import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;// 先定义账户类
public class Account {private String cardId;private double money;//余额public Account() {}public Account(String cardId, double money) {this.cardId = cardId;this.money = money;}/*设计一个取钱的方法参数 取得钱 金额返回值 不需要Lock 都是加锁原理底层 都是每次只允许一个线程加锁 加锁之后才能访问,手动解锁 其他线程可以再加锁进来。灵活的加锁和释放锁两个方法lock() 加锁 unlock() 释放锁Lock接口 用它的实现类*///成员位置创建一个 Lock锁对象 锁对象不能被改 所以 加上final修饰private final Lock lock = new ReentrantLock();public void drawMoney(double money){// 局部位置money 代表取的钱// 成员位置money 代表余额// this.money -= money;// 知道是谁来取的----哪个线程执行到这里了 谁就是哪个线程String name = Thread.currentThread().getName();// 判断余额是否充足lock.lock();//加锁if(this.money >= money){//可以取System.out.println(name+"来取钱:"+money+" 成功!");this.money -= money;System.out.println(name+"取钱之后的余额:"+this.money);}else {System.out.println(name+"来取钱:余额不足,请充值后再去...");}lock.unlock();//释放锁}/*** 获取* @return cardId*/public String getCardId() {return cardId;}/*** 设置* @param cardId*/public void setCardId(String cardId) {this.cardId = cardId;}/*** 获取* @return money*/public double getMoney() {return money;}/*** 设置* @param money*/public void setMoney(double money) {this.money = money;}public String toString() {return "Account{cardId = " + cardId + ", money = " + money + "}";}
}---------------------
package com.itheima.day12.teacher.lock;/*取钱线程类*/
public class DrawMoney extends Thread{// 定义一个账户的成员变量 --初始化交给了构造 由外界传递进来private Account account;// 构造第一个参数 是 取钱的账户对象 第二参数 表示线程的名字public DrawMoney(Account account, String name){super(name);this.account = account;}@Overridepublic void run() {//取钱就是在账户里面 减少余额// 调用账户对象 的 取钱方法// 小明线程 小红线程 操作的 账户是同一个// 在测试类创建 一个账户 传递进来account.drawMoney(100000); // 通过账户对象 取钱 (取的钱数)}
}---------------------
package com.itheima.day12.teacher.lock;public class ThreadTest {public static void main(String[] args) {//创建一个账户对象 小明和小红 共享账户 卡号 余额Account account = new Account("ICBC-114",100000);//创建两个线程对象 分别代表小红 和 小明// 线程对象中传递 共享的账户 以及 线程名字new DrawMoney(account,"小明").start();new DrawMoney(account,"小红").start();}
}
6.线程通信
1.什么是线程通信?
当多个线程共同操作共享的资源时,线程间通过某种方式互相告知自己的状态,以相互协调,并避免无效的资源争夺。
2.线程通信的常见模型(生产者与消费者模型)
生产者线程负责生产数据
消费者线程负责消费生产者生产的数据。
注意:生产者生产完数据应该等待自己,通知消费者消费;消费者消费完数据也应该等待自己,再通知生产者生产!
3.Object类的等待和唤醒方法:

注意事项:上述方法应该使用当前同步锁对象进行调用。
package com.itheima.day12.teacher.wait_notify;import java.util.ArrayList;
import java.util.List;/*定义桌子类定义一个存储包子的集合 存储包子定义一个厨师生产包子的方法定义一个吃货吃包子的方法*/
public class Desk {// 定义一个存储包子的集合 存储包子private List<String> list = new ArrayList<>();// 定义一个厨师生产包子的方法public synchronized void put() {try{//获取厨师线程对象的名称String name = Thread.currentThread().getName();//判断有没有包子if(list.size()==0){//没有包子 厨师要做包子list.add(name+"做的包子..");//生产包子 把包子放到集合中System.out.println("厨师:"+name+"正在做包子.....");//模拟做包子的时间Thread.sleep(2000);//等待和唤醒方法 一般采用共享资源进行调用//包子做完了this.notifyAll();//唤醒所有的吃货this.wait();//厨师进入休息 等待}else {
// this.notifyAll();//唤醒所有的吃货this.wait();//厨师进入休息 等待 因为桌子上有包子了。}}catch (InterruptedException e){e.printStackTrace();}}//定义一个 吃货 吃包子的方法public synchronized void get() {try{//获取吃货的名称String name = Thread.currentThread().getName();//先判断有没有包子if(list.size()==1){//模拟吃包子System.out.println("吃货:"+name+"正在吃"+list.get(0));list.clear();//清空Thread.sleep(1500);//吃饭之后//唤醒厨师this.notifyAll();//吃货休息this.wait();}else {//唤醒厨师
// this.notifyAll();//吃货休息this.wait();}}catch (Exception e){e.printStackTrace();}}}---------------------
package com.itheima.day12.teacher.wait_notify;public class QingFengBaoZiPu {public static void main(String[] args) {//开始准备吃包子System.out.println("进入庆丰包子铺 坐了下来 点餐");/*new Thread().start() 是开启一个新的线程new Thread(线程任务,线程名字).start()给新的线程绑定一个线程任务和线程的名字new Thread(()->{},线程名字).start()因为线程任务是 函数式接口 所以可以使用 lambda去表达()->{//厨师要生产包子while(true){desk.put();}}厨师生产包子 只要包子没有了就可以去生产 所以写了一个循环()->{//吃货要次包子while(true){desk.get();}}吃货吃包子 只要包子还有 就可以吃 所以也写了一个循环*///创建共享资源Desk desk = new Desk();//有三个厨师 线程new Thread(()->{//厨师要生产包子while(true){desk.put();}},"霍大厨").start();new Thread(()->{//厨师要生产包子while(true){desk.put();}},"雷大厨").start();new Thread(()->{//厨师要生产包子while(true){desk.put();}},"卧龙凤厨").start();//有两个吃货 线程new Thread(()->{//吃货要次包子while(true){desk.get();}},"乐吃货").start();new Thread(()->{//吃货要次包子while(true){desk.get();}},"李逵吃货").start();}
}
7.线程池
1.什么是线程池?
线程池就是一个可以复用线程的技术。
2.如何创建线程池?
方式一:使用ExecutorService的实现类ThreadPoolExecutor创建一个线程池对象。


package com.itheima.day12.teacher.threadpool;public class CuoZao implements Runnable{@Overridepublic void run() {//任务是搓澡String name = Thread.currentThread().getName();System.out.println("号码为:"+name+"的师傅正在给客人搓澡=====盐搓---奶搓---醋搓--");//模拟搓澡时间try {//5秒搓一个Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}}
}--------------
package com.itheima.day12.teacher.threadpool;import java.util.concurrent.*;public class Demo {public static void main(String[] args) throws InterruptedException {// 先构建一个 线程池对象ExecutorService pool = new ThreadPoolExecutor(3,//核心线程数量5,// 最大线程数量 = 核心线程数量+临时线程数量;8,//临时存活时间 时间的数量TimeUnit.SECONDS,// 超过核心现场后 如果有线程超过8秒中没有被使用 就销毁掉new ArrayBlockingQueue<>(4),//指定任务队列 任务阻塞队列 阻塞长度是4Executors.defaultThreadFactory(),//用户创建线程对象的工程对象 固定代码new ThreadPoolExecutor.CallerRunsPolicy());//任务拒绝策略 四个 我选取最后 忙不过来 找外援// 线程池 澡堂// 核心线程数量 老板招聘的 三个搓澡师傅//执行搓澡任务CuoZao cz = new CuoZao();//来一个顾客 搓一个顾客//接客pool.execute(cz);//核心pool.execute(cz);//核心pool.execute(cz);//核心//三个客人pool.execute(cz);//第四个客人 先等待了 核心线程为他服务pool.execute(cz);//第五个客人pool.execute(cz);//第六个客人pool.execute(cz);//第七个客人// 7-3 = 4pool.execute(cz);//第八个客人 触发了 招聘临时工 阻塞队列4 一旦超出阻塞队列 就增派人手pool.execute(cz);//第九个客人 阻塞队列4 超出阻塞队列两个 增派两个人手// 第九个客人 已经有五个搓澡师傅 已经达到 最大线程数量pool.execute(cz);//第十个客人 阻塞队列满了 超出阻塞队列的 用两个人手 但是还少一个// 这个时候拒绝策略 -- 增派人手 main来处理。。。Thread.sleep(20000);//时间过了十一秒 没有新的任务 肯定有线程没有处理任务的 这种任务就会销毁System.out.println("至少空闲了12秒 已经有被销毁的线程了...销毁之后 ");pool.execute(cz);pool.execute(cz);pool.execute(cz);pool.execute(cz);pool.execute(cz);pool.shutdown();//都搓完了 把 澡堂关闭
// pool.shutdownNow();//里面关闭 没搓完的任务回到队列中// 临时线程什么时候创建? 核心线程忙 + 任务队列满了。可以创建临时线程。// 如果临时线程也满了,触发了拒绝策略,// 可能1: 找主线程帮忙处理。// 可能2: 抛弃 说声对不起 异常// 可能3: 抛弃 什么也不说// 可能4: 抛弃对头 放入新的}
}
submit方法:
package com.itheima.day12.teacher.threadpool;import java.util.concurrent.Callable;public class Download implements Callable<String> {@Overridepublic String call() throws Exception {System.out.println(Thread.currentThread().getName()+"正在下载程序.....");return "任务下载完成";}
}--------------------
package com.itheima.day12.teacher.threadpool;import java.util.concurrent.*;public class XunLei {public static void main(String[] args) throws ExecutionException, InterruptedException {ExecutorService pool = new ThreadPoolExecutor(3,//核心线程数量5,// 最大线程数量 = 核心线程数量+临时线程数量;8,//临时存活时间 时间的数量TimeUnit.SECONDS,// 超过核心现场后 如果有线程超过8秒中没有被使用 就销毁掉new ArrayBlockingQueue<>(4),//指定任务队列 任务阻塞队列 阻塞长度是4Executors.defaultThreadFactory(),//用户创建线程对象的工程对象 固定代码new ThreadPoolExecutor.CallerRunsPolicy());//任务拒绝策略 四个 我选取最//线程池 处理 带返回值的任务Download d = new Download();//下载任务Future<String> f1 = pool.submit(d);pool.submit(d);pool.submit(d);pool.submit(d);pool.submit(d);System.out.println(f1.get());}
}
方式二:使用Executors(线程池的工具类)调用方法返回不同特点的线程池对象。
线程池的注意事项:
1、临时线程什么时候创建?
新任务提交时发现核心线程都在忙,任务队列也满了,并且还可以创建临时线程,此时才会创建临时线程。
2、什么时候会开始拒绝新任务?
核心线程和临时线程都在忙,任务队列也满了,新的任务过来的时候才会开始拒绝任务。
3.使用ExecutorService线程池对象的常用方法
void execute(Runnable command)
4.新任务拒绝策略

5.线程池处理Callable任务
使用ExecutorService线程池对象的常用方法---Future<T> submit(Callable<T> task)
6.Executors工具类实现线程池
是一个线程池的工具类,提供了很多静态方法用于返回不同特点的线程池对象。
注意 :这些方法的底层,都是通过线程池的实现类ThreadPoolExecutor创建的线程池对象。
7.Executors使用可能存在的陷阱
大型并发系统环境中使用Executors如果不注意可能会出现系统风险。
package com.itheima.day12.teacher.threadpool;import java.util.concurrent.Callable;public class Download implements Callable<String> {@Overridepublic String call() throws Exception {System.out.println(Thread.currentThread().getName()+"正在下载程序.....");return "任务下载完成";}
}-------------------
package com.itheima.day12.teacher.threadpool;import java.util.concurrent.*;public class XunLei2 {public static void main(String[] args) throws ExecutionException, InterruptedException {ExecutorService pool = Executors.newFixedThreadPool(3);//线程池 处理 带返回值的任务Download d = new Download();//下载任务Future<String> f1 = pool.submit(d);pool.submit(d);pool.submit(d);pool.submit(d);pool.submit(d);System.out.println(f1.get());}
}
8.其它细节知识:并发、并行
1.进程:
正在运行的程序(软件)就是一个独立的进程。
线程是属于进程的,一个进程中可以同时运行很多个线程。
进程中的多个线程其实是并发和并行执行的。
2.并发的含义
进程中的线程是由CPU负责调度执行的,但CPU能同时处理线程的数量有限,为了保证全部线程都能往前执行,CPU会轮询为系统的每个线程服务,由于CPU切换的速度很快,给我们的感觉这些线程在同时执行,这就是并发。
3.并行的理解
在同一个时刻上,同时有多个线程在被CPU调度执行。
4.多线程是怎么执行的?
并发和并行同时进行的
9.其它细节知识:线程的生命周期
1.线程的6种状态

2.线程6中状态互相转换
相关文章:
Thread多线程(创建,方法,安全,通信,线程池,并发,并行,线程的生命周期)【全详解】
目录 1.多线程概述 2.多线程的创建 3.Thread的常用方法 4.线程安全 5.线程同步 6.线程通信 7.线程池 8.其它细节知识:并发、并行 9.其它细节知识:线程的生命周期 1.多线程概述 线程是什么? 线程(Thread)是一个程序内部的一条执行…...
自定义View中的ListView和ScrollView嵌套的问题
当我们在使用到ScrollView和ListView的时候可能会出现显示不全的问题。那我们可以进行以下分析 ScrollView在测量子布局的时候会用UNSPECIFIED。通过源码观察, 在ScrollView的onMeasure方法中 Overrideprotected void onMeasure(int widthMeasureSpec, int heightMe…...
支持向量机 SVM | 线性可分:硬间隔模型公式推导
目录 一. SVM的优越性二. SVM算法推导小节概念 在开始讲述SVM算法之前,我们先来看一段定义: 支持向量机(Support VecorMachine, SVM)本身是一个二元分类算法,支持线性分类和非线性分类的分类应用,同时通过OvR或者OvO的方式可以应用…...
【Unity实战】UGUI和Z轴排序那点事儿
如果读者是从Unity 4.x时代过来的,可能都用过NGUI这个插件(后来也是土匪成了正规军),NGUI一大特点是可以靠transform位移的Z值进行遮挡排序,然而这个事情在UGUI成了难题(Sorting Layer、Inspector顺序等因素…...
Vue/React 前端高频面试
说一说vue钩子函数 钩子函数是Vue实例创建和销毁过程中自动执行的函数。按照组件生命周期的过程分为:挂载阶段 -> 更新阶段 -> 销毁阶段。 每个阶段对应的钩子函数分别为:挂载阶段(beforeCreate,created,befor…...
[技巧]Arcgis之图斑四至范围批量计算
ArcGIS图层(点、线、面三类图形)四至范围计算 例外一篇介绍:[技巧]Arcgis之图斑四至点批量计算 说明:如下图画出来的框(范围标记不是很准) ,图斑的x最大和x最小,y最大,…...
C/C++工程师面试题(STL篇)
STL 中有哪些常见的容器 STL 中容器分为顺序容器、关联式容器、容器适配器三种类型,三种类型容器特性分别如下: 1. 顺序容器 容器并非排序的,元素的插入位置同元素的值无关,包含 vector、deque、list vector:动态数组…...
Effective Programming 学习笔记
1 基本语句 1.1 断言 在南溪看来,断言可以用来有效地确定编程中当前代码运行的前置条件,尤其是以下情况: 第三方工具库对输入数据的依赖,例如:minitouch库对Android版本的要求...
【MGR】MySQL Group Replication 背景
目录 17.1 Group Replication Background 17.1.1 Replication Technologies 17.1.1.1 Primary-Secondary Replication 17.1.1.2 Group Replication 17.1.2 Group Replication Use Cases 17.1.2.1 Examples of Use Case Scenarios 17.1.3 Group Replication Details 17.1…...
300分钟吃透分布式缓存-17讲:如何理解、选择并使用Redis的核心数据类型?
Redis 数据类型 首先,来看一下 Redis 的核心数据类型。Redis 有 8 种核心数据类型,分别是 : & string 字符串类型; & list 列表类型; & set 集合类型; & sorted set 有序集合类型&…...
思科网络设备监控
思科是 IT 行业的先驱之一,提供从交换机到刀片服务器的各种设备,以满足中小企业和企业的各种 IT 管理需求。管理充满思科的 IT 车间涉及许多管理挑战,例如监控可用性和性能、管理配置更改、存档防火墙日志、排除带宽问题等等,这需…...
深入剖析k8s-控制器思想
引言 本文是《深入剖析Kubernetes》学习笔记——《深入剖析Kubernetes》 正文 控制器都遵循K8s的项目中一个通用的编排模式——控制循环 for {实际状态 : 获取集群中对象X的实际状态期望状态 : 获取集群中对象X的期望状态if 实际状态 期望状态 {// do nothing} else {执行…...
go并发模式之----使用时顺序模式
常见模式之二:使用时顺序模式 定义 顾名思义,起初goroutine不管是怎么个先后顺序,等到要使用的时候,需要按照一定的顺序来,也被称为未来使用模式 使用场景 每个goroutine函数都比较独立,不可通过参数循环…...
[动态规划]---part1
前言 作者:小蜗牛向前冲 专栏:小蜗牛算法之路 专栏介绍:"蜗牛之道,攀登大厂高峰,让我们携手学习算法。在这个专栏中,将涵盖动态规划、贪心算法、回溯等高阶技巧,不定期为你奉上基础数据结构…...
java 关于 Object 类中的 wait 和 notify 方法。(生产者和消费者模式!)
4、关于 Object 类中的 wait 和 notify 方法。(生产者和消费者模式!) 第一:wait 和 notify 方法不是线程对象的方法,是 java 中任何一个 java 对象都有的方法,因为这两个方法是 Object 类中自带的。 wait 方…...
YOLOv8姿态估计实战:训练自己的数据集
课程链接:https://edu.csdn.net/course/detail/39355 YOLOv8 基于先前 YOLO 版本的成功,引入了新功能和改进,进一步提升性能和灵活性。YOLOv8 同时支持目标检测和姿态估计任务。 本课程以熊猫姿态估计为例,将手把手地教大家使用C…...
【海贼王的数据航海:利用数据结构成为数据海洋的霸主】链表—双向链表
目录 往期 1 -> 带头双向循环链表(双链表) 1.1 -> 接口声明 1.2 -> 接口实现 1.2.1 -> 双向链表初始化 1.2.2 -> 动态申请一个结点 1.2.3 -> 双向链表销毁 1.2.4 -> 双向链表打印 1.2.5 -> 双向链表判空 1.2.6 -> 双向链表尾插 1.2.7 -&…...
做测试还是测试开发,选职业要慎重!
【软件测试面试突击班】2024吃透软件测试面试最全八股文攻略教程,一周学完让你面试通过率提高90%!(自动化测试) 突然发现好像挺多人想投测开和测试的,很多人面试的时候也会被问到这几个职位的区别,然后有测…...
Java面试题总结200道(二)
26、简述Spring中Bean的生命周期? 在原生的java环境中,一个新的对象的产生是我们用new()的方式产生出来的。在Spring的IOC容器中,将这一部分的工作帮我们完成了(Bean对象的管理)。既然是对象,就存在生命周期,也就是作用…...
面试数据库篇(mysql)- 03MYSQL支持的存储引擎有哪些, 有什么区别
存储引擎就是存储数据、建立索引、更新/查询数据等技术的实现方式 。存储引擎是基于表的,而不是基于库的,所以存储引擎也可被称为表类型。 MySQL体系结构 连接层服务层引擎层存储层 存储引擎特点 InnoDB MYSQL支持的存储引擎有哪些, 有什么区别 ? my…...
23-Oracle 23 ai 区块链表(Blockchain Table)
小伙伴有没有在金融强合规的领域中遇见,必须要保持数据不可变,管理员都无法修改和留痕的要求。比如医疗的电子病历中,影像检查检验结果不可篡改行的,药品追溯过程中数据只可插入无法删除的特性需求;登录日志、修改日志…...
Java如何权衡是使用无序的数组还是有序的数组
在 Java 中,选择有序数组还是无序数组取决于具体场景的性能需求与操作特点。以下是关键权衡因素及决策指南: ⚖️ 核心权衡维度 维度有序数组无序数组查询性能二分查找 O(log n) ✅线性扫描 O(n) ❌插入/删除需移位维护顺序 O(n) ❌直接操作尾部 O(1) ✅内存开销与无序数组相…...
【SQL学习笔记1】增删改查+多表连接全解析(内附SQL免费在线练习工具)
可以使用Sqliteviz这个网站免费编写sql语句,它能够让用户直接在浏览器内练习SQL的语法,不需要安装任何软件。 链接如下: sqliteviz 注意: 在转写SQL语法时,关键字之间有一个特定的顺序,这个顺序会影响到…...
让AI看见世界:MCP协议与服务器的工作原理
让AI看见世界:MCP协议与服务器的工作原理 MCP(Model Context Protocol)是一种创新的通信协议,旨在让大型语言模型能够安全、高效地与外部资源进行交互。在AI技术快速发展的今天,MCP正成为连接AI与现实世界的重要桥梁。…...
基于matlab策略迭代和值迭代法的动态规划
经典的基于策略迭代和值迭代法的动态规划matlab代码,实现机器人的最优运输 Dynamic-Programming-master/Environment.pdf , 104724 Dynamic-Programming-master/README.md , 506 Dynamic-Programming-master/generalizedPolicyIteration.m , 1970 Dynamic-Programm…...
Typeerror: cannot read properties of undefined (reading ‘XXX‘)
最近需要在离线机器上运行软件,所以得把软件用docker打包起来,大部分功能都没问题,出了一个奇怪的事情。同样的代码,在本机上用vscode可以运行起来,但是打包之后在docker里出现了问题。使用的是dialog组件,…...
ABAP设计模式之---“简单设计原则(Simple Design)”
“Simple Design”(简单设计)是软件开发中的一个重要理念,倡导以最简单的方式实现软件功能,以确保代码清晰易懂、易维护,并在项目需求变化时能够快速适应。 其核心目标是避免复杂和过度设计,遵循“让事情保…...
CVE-2020-17519源码分析与漏洞复现(Flink 任意文件读取)
漏洞概览 漏洞名称:Apache Flink REST API 任意文件读取漏洞CVE编号:CVE-2020-17519CVSS评分:7.5影响版本:Apache Flink 1.11.0、1.11.1、1.11.2修复版本:≥ 1.11.3 或 ≥ 1.12.0漏洞类型:路径遍历&#x…...
【Linux】Linux安装并配置RabbitMQ
目录 1. 安装 Erlang 2. 安装 RabbitMQ 2.1.添加 RabbitMQ 仓库 2.2.安装 RabbitMQ 3.配置 3.1.启动和管理服务 4. 访问管理界面 5.安装问题 6.修改密码 7.修改端口 7.1.找到文件 7.2.修改文件 1. 安装 Erlang 由于 RabbitMQ 是用 Erlang 编写的,需要先安…...
前端高频面试题2:浏览器/计算机网络
本专栏相关链接 前端高频面试题1:HTML/CSS 前端高频面试题2:浏览器/计算机网络 前端高频面试题3:JavaScript 1.什么是强缓存、协商缓存? 强缓存: 当浏览器请求资源时,首先检查本地缓存是否命中。如果命…...
