第二十六章
一、多线程
1.1 并发与并行的概念
并发:指两个或多个事件在同一时间段内发生。
并行:指两个或多个事件在同一时刻发生(同时发生)。
1.2 进程的概念
进程:是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间,一个应用程序可以同时运行多个进程,
1.3 线程的概念
线程:线程是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程。一个进程中是可以有多个线程的,这个应用程序也可以称之为,多线程程序。
即:一个程序运行后至少有一个进程,一个进程可以包含多个线程
1.3 线程调度
-
分时调度:
所有线程轮流使用CPU的使用权,平均分配每个线程栈中的CPU的时间
-
抢占式调度:
优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度。
1.4 主线程
/*
主线程:执行(main)方法的线程
单线程程序:java程序中只有一个线程
执行从main方法开始,从上到下依次执行
JVM会找操作系统开辟一条通向CPU的执行路径,cpu就可以通过这个路径来执行main方法
而这个路径有一个名字叫做 main(主)线程
*/
public class Demo01MainThread {
public static void main(String[] args) {
Person p1 = new Person("小强");
p1.run();
//主线程中间出现错误会导致之后程序无法执行
// System.out.println(0/0);//ArithmeticException: / by zero
Person p2 = new Person("旺财");
p2.run();
}
}
1.5 创建多线程程序的第一种方式:创建Thread类的子类
Java.lang.Thread
创建新执行线程有两种方法。一种方法是将类声明为 Thread 的子类。该子类应重写 Thread 类的 run 方法。接下来可以分配并启动该子类的实例。
创建多线程程序的第一种方式: 创建Thread类的子类
java.lang.Thread类:是描述线程的类,我们想要实现多线程的程序,就必须继承Thread类
实现步骤:
1.创建一个Thread类的子类
2.在Thread类的子类中重写Thread类中的run方法,设置线程任务(开启线程要做什么) **void run()** 是Thread类中的一个方法
3.创建Thread类的子类对象
4.调用Thread类中的方法start方法,开启新的线程,执行run方法
void start() 使该线程开始执行;Java 虚拟机调用该线程的 run 方法。
结果是两个线程并发的运行;当前线程(main线程)和另一个线程(创建的新线程,执行其run方法)
多次启动一个线程是非法的,特别是当线程已经结束执行后,不能再重新启动。
java程序属于抢占式调度,那个线程优先级高哪个线程优先执行;同一个优先级那就随机选择一个执行
public class Demo02Thread {
public static void main(String[] args) {
// 3.创建Thread类的子类对象
MyThread mt = new MyThread();
// 4.调用Thread类中的方法start方法,开启新的线程,执行run方法
mt.start();
for (int i = 0; i < 20; i++) {
System.out.println("main" + i);
}
}
}
Thread子类
//1.创建一个Thread类的子类
public class MyThread extends Thread{
//2.在Thread类的子类中重写Thread类中的run方法,设置线程任务(开启线程要做什么)
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println("run" + i);
}
}
}
1.6 多线程原理–随机性打印结果
1.6 多线程原理–多线程内存图解
1.7 Thread类的常用方法——获取线程名称的方法
获取线程名称的两种方式:
1.使用Thread类中的getName()方法:String getName()返回线程名称
2.可以先获取当前正在执行的线程,使用线程中的方法getName()获取线程名称
static Thread currentThread() 返回对当前正在执行的线程对象的引用。
线程的名称:
主线程:main
新线程:Thread-0,Thread-1,Thread-2
public class Demo03MyThreadMain {
public static void main(String[] args) {
MyThreadZi mt = new MyThreadZi();
//调用start方法开启新线程,执行run方法
mt.start();//Thread-0
new MyThreadZi().start();
new MyThreadZi().start();
//获取主线程名称
System.out.println(Thread.currentThread().getName());
}
}
子类
//定义一个Thread子类
public class MyThreadZi extends Thread {
//重写Thread类中的run方法。设置线程任务
@Override
public void run() {
//获取线程名称
//String name = getName();
//System.out.println(name);
//获取当前执行的线程,由于currentThread()是静态方法,直接由Thread类调用即可,不需要对象
// Thread t = Thread.currentThread();
//System.out.println(t);
//String name = t.getName();
//System.out.println(name);
//写成一句,链式编程
System.out.println(Thread.currentThread().getName());
}
}
1.8 Thread类的常用方法——设置线程名称的方法
设置线程的名称(了解):
1.使用Thread类中的方法setName(名字);
void setName(String name) 改变线程名称,使之与参数 name 相同
2.创建一个带参数的构造方法,参数传递线程的名称;调用父类的带参构造方法,
把线程名称传递给父类,让父类(Thread)给子线程起一个名字
Thread(String name) 分配新的Thread对象
public class Demo04TreadSet {
public static void main(String[] args) {
//开启多线程
ThreadSetName mt = new ThreadSetName();
mt.setName("小强");
mt.start();
//开启多线程
new ThreadSetName("旺财").start();
}
}
1.9 Thread类的常用方法——sleep
static void sleep(long millis)
在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。
毫秒数结束后,线程继续进行
public class Demo05Sleep {
public static void main(String[] args) {
//模拟秒表
for (int i = 0; i <= 60; i++) {
System.out.println(i);
//使用Thread类的sleep方法让程序睡眠1秒钟,结果一秒打印一次
//JAVA里的Thread.sleep方法一定有异常 所以用sleep方法要用try...catch
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
1.10 创建多线程程序的第二种方式:实现Runnable接口
创建线程的另一种方法是
声明实现 Runnable 接口的类。该类然后实现 run 方法。然后可以分配该类的实例,在创建 Thread 时作为一个参数来传递并启动。
创建多线程程序的第二种方式:实现Runnable接口
Runnable 接口应该由那些打算通过某一线程执行其实例的类来实现。
类必须定义一个称为 run 的无参数方法。
java.lang.Thread类的构造方法
Thread(Runnable target) 分配新的Thread 对象
Thread(Runnable target,String name) 分配新的Thread对象。
实现步骤:
1.创建一个Runnable接口的实现类
2.在实现类中重写Runnable接口的run方法,设置线程任务
3.创建一个Runnable接口的实现类对象
4.创建Thread类对象,构造方法中传递Runnable接口的实现类对象
5.调用Thread类中的start方法,开启新的线程执行run方法
public class Demo01Runnable {
public static void main(String[] args) {
//3.创建一个Runnable接口的实现类对象
RunnableImpl run = new RunnableImpl();
// 4.创建Thread类对象,构造方法中传递Runnable接口的实现类对象
Thread t = new Thread(run);//构造方法中传递
//5.调用Thread类中的start方法,开启新的线程执行run方法
t.start();
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName()+"-->"+i);
}
}
}
实现类
// 1.创建一个Runnable接口的实现类
public class RunnableImpl implements Runnable {
//2.在实现类中重写Runnable接口的run方法,设置线程任务
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName()+"-->"+i);
}
}
}
1.11 Runnable和Thread的区别
实现Runnable接口创建多线程程序的好处:
1.避免了单继承的局限性
一个类只能继承一个类,类继承了Thread类就不能继承其他的类
实现了Runnable接口,还可以继承其他的类,实现其他的接口
2.增强了程序的扩展性,降低了程序的耦合性(解耦)
实现Runnable接口的方式,把设置线程任务和开启新线程进行了分离(解耦)
实现类中,重写了run方法:用来设置线程任务
创建Thread类对象,调用start方法:用来开启新线程
public class Demo01Runnable {
public static void main(String[] args) {
//3.创建一个Runnable接口的实现类对象
RunnableImpl run = new RunnableImpl();
// 4.创建Thread类对象,构造方法中传递Runnable接口的实现类对象
// Thread t = new Thread(run);//打印线程名称
Thread t = new Thread(new RunnableImpl2());//打印HelloWorld,使用接口可随意调换
//5.调用Thread类中的start方法,开启新的线程执行run方法
t.start();
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName()+"-->"+i);
}
}
}
实现类
// 1.创建一个Runnable接口的实现类
public class RunnableImpl implements Runnable {
//2.在实现类中重写Runnable接口的run方法,设置线程任务
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName()+"-->"+i);
}
}
}
实现类2
// 1.创建一个Runnable接口的实现类
public class RunnableImpl2 implements Runnable {
//2.在实现类中重写Runnable接口的run方法,设置线程任务
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println("Hello World!");
}
}
}
1.12 匿名内部类方式实现线程的创建
匿名内部类方式实现线程的创建
匿名:没有名字
内部类:写在其他类内部的类
匿名内部类作用:简化代码
把子类继承父类,重写父类方法,创建子类对象合一步完成
匿名内部类最终产物:子类/实现对象,而这个类没有名字
格式:
new 父类/接口(){
重写父类/接口中的方法
};
public class Demo01InnerClassThread {
public static void main(String[] args) {
//线程的父类是Thread
//不用匿名内部类,需要创建new MyThread.()start();来启动线程
//使用匿名内部类
new Thread() {
//重写run方法
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName() + "-->" + "A");
}
}
}.start();//开启多线程
//线程的接口Runnable
// RunnableImpl r = new RunnableImpl()
Runnable r = new RunnableImpl() {//多态
//重写run方法,设置线程任务
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName() + "-->" + "B");
}
}
};
new Thread(r).start();
//简化接口的方式,直接接收实现类对象,不用变量
new Thread(new RunnableImpl() {//多态
//重写run方法,设置线程任务
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName() + "-->" + "C");
}
}
}).start();
}
}
二、线程安全
2.1 线程安全问题的概述
/*
实现卖票案例:
多个线程访问共享数据,会出现卖同一张票以及卖不存在票的情况
Thread-2-->正在卖第100张票
Thread-1-->正在卖第100张票
Thread-0-->正在卖第100张票
*/
public class RunnableImpl implements Runnable{
//定义一个多个线程共享的票源
private int ticket = 100;
//设置线程任务
@Override
public void run() {
//使用死循环让卖票重复进行
while (true) {
//先判断票是否存在
if (ticket > 0) {
//提高安全问题出现的概率,让程序睡眠
try{
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//票存在,卖票 ticket
System.out.println(Thread.currentThread().getName() + "-->正在卖第" + ticket + "张票");
ticket--;
}
}
}
}
/*
模拟卖票案例
创建三个线程,同时开启,对共享的票进行出售
*/
public class Demo01Ticket {
public static void main(String[] args) {
//创建Runnable接口的实现类对象
RunnableImpl run = new RunnableImpl();
//创建Thread对象,构造方法中传递Runnable接口的实现类对象
Thread t0 = new Thread(run);
Thread t1 = new Thread(run);
Thread t2 = new Thread(run);
//调用方法开启多线程
t0.start();
t1.start();
t2.start();
}
}
2.2 线程安全问题产生的原理
进入if语句是指已经判断完ticket的值大于零,但是没有执行打印和ticket–,然后cpu转去执行其他的线程。导致其他的线程传入的值跟前边传入的值是i相同的。
2.3 解决线程安全问题–线程同步
线程同步:
- 同步代码块
- 同步方法
- 锁机制
2.4 解决线程安全问题–同步代码块
synchronized关键字可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问
格式:
synchronized(同步锁){
需要同步的代码
}
/*
模拟卖票案例
创建三个线程,同时开启,对共享的票进行出售
*/
public class Demo01Ticket {
public static void main(String[] args) {
//创建Runnable接口的实现类对象
RunnableImpl run = new RunnableImpl();
//创建Thread对象,构造方法中传递Runnable接口的实现类对象
Thread t0 = new Thread(run);
Thread t1 = new Thread(run);
Thread t2 = new Thread(run);
//调用方法开启多线程
t0.start();
t1.start();
t2.start();
}
}
import java.util.Objects;
/*
卖票案例出现了线程安全问题
卖出了不存在的票和重复的票
解决线程安全问题的一种方案:使用同步代码块
格式:
synchronized(锁对象){
可能会出现线程安全问题的代码(访问了共享数据的代码)
}
注意:
1.通过代码块中的锁对象,可以使用任意的对象
2.但是必须保证多个线程使用的锁对象是同一个
3.锁对象的作用:
把同步代码块锁住,只让一个线程在同步代码块中执行
*/
public class RunnableImpl implements Runnable{
//定义一个多个线程共享的票源
private int ticket = 100;
//创建一个锁对象,写在线程外边。
Object obj = new Object();
//设置线程任务
@Override
public void run() {
//使用死循环让卖票重复进行
while (true) {
synchronized (obj){
//先判断票是否存在
if (ticket > 0) {
//提高安全问题出现的概率,让程序睡眠
try{
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//票存在,卖票 ticket
System.out.println(Thread.currentThread().getName() + "-->正在卖第" + ticket + "张票");
ticket--;
}
}
}
}
}
2.5 同步技术的原理
2.6 解决线程安全问题—同步方法
**同步方法:**使用sychronized修饰的方法就叫做同步方法,保证A线程执行该方法的时候其他方法只能在线程外等着
pubic sychronized void method(){
可能会产生安全问题的代码
}
对于非static方法,同步锁就是this
对于static方法,我们使用当前方法所在类的字节码对象(类名.class)
/*
模拟卖票案例
创建三个线程,同时开启,对共享的票进行出售
*/
public class Demo01Ticket {
public static void main(String[] args) {
//创建Runnable接口的实现类对象
RunnableImpl run = new RunnableImpl();
System.out.println("run" + run);
//创建Thread对象,构造方法中传递Runnable接口的实现类对象
Thread t0 = new Thread(run);
Thread t1 = new Thread(run);
Thread t2 = new Thread(run);
//调用方法开启多线程
t0.start();
t1.start();
t2.start();
}
}
/*
实现卖票案例:
解决线程安全问题的第二种方案,使用同步方法
使用步骤:
1.把访问了共享数据的代码抽取出来,放入一个方法中
2.在方法上添加synchronized修饰符
格式:
synchronized 返回值类型 方法名(参数列表){
可能会出现线程安全问题的代码(访问了共享数据的代码)
}
*/
public class RunnableImpl implements Runnable{
//定义一个多个线程共享的票源
private int ticket = 100;
//设置线程任务
@Override
public void run() {
System.out.println("this" + this);
//使用死循环让卖票重复进行
while (true) {
payTicket();
}
}
/*
定义一个同步方法
同步方法会吧方法内部的代码所著
只让一个线程执行,同步方法的锁对象是谁
就是实现类对象 new RunnableImpl(),也就是this
*/
public synchronized void payTicket(){
//先判断票是否存在
if (ticket > 0) {
//提高安全问题出现的概率,让程序睡眠
try{
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//票存在,卖票 ticket
System.out.println(Thread.currentThread().getName() + "-->正在卖第" + ticket + "张票");
ticket--;
}
}
}
2.7 静态同步方法(了解)
public class RunnableImpl implements Runnable{
//定义一个多个线程共享的票源
private static int ticket = 100;
//设置线程任务
@Override
public void run() {
System.out.println("this" + this);
//使用死循环让卖票重复进行
while (true) {
payTicketStatic();
}
}
/*
静态的同步方法
锁对象:
不能是this,
this是创建对象之后产生的,静态方法优先于对象
静态方法的锁对象是本类的class属性-->class文件对象(反射)
*/
public static synchronized void payTicketStatic(){
//先判断票是否存在
// synchronized (RunnableImpl.class) { 在静态方法中 传入RunnableImpl.class也可以 不能用this
if (ticket > 0) {//方法是静态的,必须访问静态变量
//提高安全问题出现的概率,让程序睡眠
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//票存在,卖票 ticket
System.out.println(Thread.currentThread().getName() + "-->正在卖第" + ticket + "张票");
ticket--;
}
//}
}
}
public class Demo01Ticket {
public static void main(String[] args) {
//创建Runnable接口的实现类对象
RunnableImpl run = new RunnableImpl();
System.out.println("run" + run);
//创建Thread对象,构造方法中传递Runnable接口的实现类对象
Thread t0 = new Thread(run);
Thread t1 = new Thread(run);
Thread t2 = new Thread(run);
//调用方法开启多线程
t0.start();
t1.start();
t2.start();
}
}
2.8 解决线程安全问题第三种方式—Lock锁
/*
实现卖票案例:
解决线程安全问题的第三种方案,使用Lock锁
java.util.current.Locks.Lock接口
Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作
Lock接口中的方法:
void lock()获取锁。
void unlock() 释放锁。
Lock 是一个接口 需要一个实现类 ---ReentrantLock
java.util.concurrent.locks.ReentrantLock implements Lock接口
使用步骤:
1.在成员位置创建一个Lock的实现类对象
2.在可能会出现安全问题的代码前,调用Lock接口中的方法Lock获取锁
3.在可能会出现安全问题的代码后,调用Lock接口中的方法UnLock解锁
*/
public class RunnableImpl implements Runnable{
//定义一个多个线程共享的票源
private int ticket = 100;
// 1.在成员位置创建一个Lock的实现类对象
Lock l = new ReentrantLock();//多态写法
//设置线程任务,优化写法
@Override
public void run() {
//使用死循环让卖票重复进行
while (true) {
// 2.在可能会出现安全问题的代码前,调用Lock接口中的方法Lock获取锁
l.lock();
//先判断票是否存在
if (ticket > 0) {
//提高安全问题出现的概率,让程序睡眠
try{
//票存在,卖票 ticket
System.out.println(Thread.currentThread().getName() + "-->正在卖第" + ticket + "张票");
ticket--;
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
// 3.在可能会出现安全问题的代码后,调用Lock接口中的方法UnLock解锁
l.unlock();//无论程序是否异常都会把锁释放
}
}
}
}
/* //设置线程任务
@Override
public void run() {
//使用死循环让卖票重复进行
while (true) {
// 2.在可能会出现安全问题的代码前,调用Lock接口中的方法Lock获取锁
l.lock();
//先判断票是否存在
if (ticket > 0) {
//提高安全问题出现的概率,让程序睡眠
try{
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//票存在,卖票 ticket
System.out.println(Thread.currentThread().getName() + "-->正在卖第" + ticket + "张票");
ticket--;
}
// 3.在可能会出现安全问题的代码后,调用Lock接口中的方法UnLock解锁
l.unlock();
}
}*/
}
/*
模拟卖票案例
创建三个线程,同时开启,对共享的票进行出售
*/
public class Demo01Ticket {
public static void main(String[] args) {
//创建Runnable接口的实现类对象
RunnableImpl run = new RunnableImpl();
//创建Thread对象,构造方法中传递Runnable接口的实现类对象
Thread t0 = new Thread(run);
Thread t1 = new Thread(run);
Thread t2 = new Thread(run);
//调用方法开启多线程
t0.start();
t1.start();
t2.start();
}
}
三、线程状态
3.1 线程状态概述
NEW:
至今尚未启动的线程处于这种状态。
RUNNABLE:
正在 Java 虚拟机中执行的线程处于这种状态。
BLOCKED:
受阻塞并等待某个监视器锁的线程处于这种状态。
WAITING:
无限期地等待另一个线程来执行某一特定操作的线程处于这种状态。
TIMED_WAITING:
等待另一个线程来执行取决于指定等待时间的操作的线程处于这种状态。
TERMINATED:
已退出的线程处于这种状态。
3.1.1 线程间通信
通信:多个线程在处理同一个资源,但是处理的动作确不相同。
多个线程在操作同一份数据时,为了避免争夺,各个线程有效利用资源,使用等待唤醒机制。
3.2 等待唤醒机制
3.2.1 等待唤醒机制需求分析
3.2.2 等待唤醒机制代码实现–包子类,包子铺类
测试类
/*
测试类:
包含main方法,程序的执行入口,启动程序
创建包子对象
创建包子铺线程,开启,生产包子
创建吃货线程,开启,吃包子
*/
public class Demo {
public static void main(String[] args) {
//创建包子对象
BaoZi bz = new BaoZi();
//创建包子铺线程,开启,生产包子
new BaoZiPu(bz).start();
//创建吃货线程,开启,吃包子
new ChiHuo(bz).start();
}
}
包子类
/*
资源类:包子类
设置包子的属性
皮
馅
包子的状态: 有 true 无 false
*/
public class BaoZi {
String pi;
String Xian;
// 包子的状态: 初始false
Boolean flag = false;
}
吃货类:线程类
*
消费者(吃货):是一个线程类,可以继承Thread
设置线程任务(run):吃包子
对包子的状态进行判断
false:没有包子
吃货调用wait方法进入等待状态
true:有包子
吃货吃包子
吃货吃完包子
修改包子状态为false
吃货唤醒包子铺的线程,生产包子
*/
public class ChiHuo extends Thread{
//1.需要在成员位置创建一个包子变量
private BaoZi bz;
//2.使用带参数的构造方法,为这个包子变量赋值
public ChiHuo(BaoZi bz) {
this.bz = bz;
}
//设置线程任务(run):吃包子
@Override
public void run() {
//使用死循环,让吃货一直吃包子
while(true){
//必须同时同步技术保证两个线程只能有一个在执行
synchronized (bz){
//对包子状态进行判断
if (bz.flag == false){
try {
bz.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//被唤醒之后的代码
System.out.println("吃货正在吃:"+ bz.pi + bz.Xian + "的包子,2秒吃完");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//吃货吃完包子,修改包子的状态为false
bz.flag = false;
bz.notify();
System.out.println("吃货已经把"+ bz.pi + bz.Xian +"的包子吃完了,包子铺开始生产包子了");
System.out.println("--------------------------------------");
}
}
}
}
包子铺:线程类
/*
生产者(包子铺)类:是一个线程类,可以继承Thread
设置线程任务(run):生产包子
对包子的状态进行判断
true:有包子
包子铺调用wait方法进入等待状态
false:没有包子
包子铺生产包子
增加一些趣味性:交替生产两种包子
有两种状态:(i % 2 == 0)
包子铺生产好了包子
修改包子的状态为true有
唤醒吃货线程,让吃货线程吃包子
注意事项:
包子铺线程和包子线程关系--通信关系(互斥)
必须使用同步技术保证两个线程只能有一个在执行
锁对象必须保证唯一,可以使用包子对象作为锁对象
包子铺类和吃货的类就需要把包子对象作为参数传递进来
1.需要在成员位置创建一个包子变量
2.使用带参构造方法,为这个包子变量赋值
*/
public class BaoZiPu extends Thread{
//1.需要在成员位置创建一个包子变量
private BaoZi bz;
//2.使用带参数的构造方法,为这个包子变量赋值
public BaoZiPu(BaoZi bz) {
this.bz = bz;
}
//设置线程任务,生产包子
@Override
public void run() {
while(true){//让包子铺一直生产包子
//定义一个变量
int count = 0;
// 必须使用同步技术保证两个线程只能有一个在执行
synchronized (bz) {
if (bz.flag == true) {
//包子铺调用wait方法进入等待状态
try {
bz.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//唤醒之后执行,包子铺生产包子
if (count % 2 == 0) {
//生产薄皮三鲜馅包子
bz.pi = "薄皮";
bz.Xian = "三鲜馅";
} else {
//生产冰皮,牛肉大葱馅
bz.pi = "冰皮";
bz.Xian = "牛肉大葱馅";
}
count++;//每次生产不同的包子
System.out.println("包子铺正在生产" + bz.pi + bz.Xian + "包子");
//生产包子需要3秒
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//包子铺生产好了包子 修改包子的状态为true有
bz.flag = true;
//唤醒吃货线程,让吃货线程吃包子
bz.notify();
System.out.println("包子铺已经生产好了" + bz.pi + bz.Xian + "包子,可以吃了");
}
}
}
}
3.3 等待唤醒案例分析
计时等待:
Block阻塞状态
Waiting(无限等待状态)
等待唤醒 线程之间的通信
3.4 等待唤醒案例代码实现
等待唤醒案例:线程之间的通信
创建一个顾客线程(消费者):告知老板要的包子的种类和数量,调用wait()方法,放弃cup的执行
进入waiting状态(无限等待)
创建一个老板线程(生产者):花了五秒做包子,做好包子之后,调用notify方法,唤醒顾客吃包子
注意:
顾客和老板线程必须使用同步代码块包裹起来,保证等待和唤醒,只能有一个执行
同步使用的锁对象必须保证是唯一的。
只有锁对象才能调用wait和notify方法
Object类:
void wait()
在其他线程调用此对象的 notify() 方法或 notifyAll() 方法前,导致当前线程等待。
void notify()
唤醒在此对象监视器上等待的单个线程。 会继续执行wait之后的代码
public class Demo01WaitAndNotify {
public static void main(String[] args) {
//创建锁对象,保证线程唯一
Object obj = new Object();
//创建一个顾客线程(消费者) 匿名内部类创建
new Thread(){
@Override
public void run() {
//保证等待和唤醒的线程只能有一个执行,需要使用同步技术
synchronized (obj){
System.out.println("告知老板要的包子的种类和数量");
//调用wait()方法,进入等待状态
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
//唤醒之后执行的代码
System.out.println("包子已经做好了,开吃");
}
}
}.start();
//创建一个老板线程:
new Thread(){
@Override
public void run() {
//花五秒钟做包子
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (obj){
System.out.println("五秒之后包子做好了,告诉顾客可以吃了");
// 做好包子之后,调用notify方法,唤醒顾客吃包子
obj.notify();
}
}
}.start();
}
}