41 多线程
我们在之前,学习的程序在没有跳转语句的前提下,都是由上至下依次执行,那现在想要设计一个程序,边打游戏边听歌,怎么设计?
要解决上述问题,咱们得使用多进程或者多线程来解决.
41.1 并发与并行
- 并发:指两个或多个事件在同一个时间段内发生。
- 并行:指两个或多个事件在同一时刻发生(同时发生)。
在操作系统中,安装了多个程序,并发指的是在一段时间内宏观上有多个程序同时运行,这在单 CPU 系统中,每一时刻只能有一道程序执行,即微观上这些程序是分时的交替运行,只不过是给人的感觉是同时运行,那是因为分时交替运行的时间是非常短的。
而在多个 CPU 系统中,则这些可以并发执行的程序便可以分配到多个处理器上(CPU),实现多任务并行执行,即利用每个处理器来处理一个可以并发执行的程序,这样多个程序便可以同时执行。目前电脑市场上说的多核 CPU,便是多核处理器,核 越多,并行处理的程序越多,能大大的提高电脑运行的效率。
注意:单核处理器的计算机肯定是不能并行的处理多个任务的,只能是多个任务在单个CPU上并发运行。同理,线程也是一样的,从宏观角度上理解线程是并行运行的,但是从微观角度上分析却是串行运行的,即一个线程一个线程的去运行,当系统只有一个CPU时,线程会以某种顺序执行多个线程,我们把这种情况称之为线程调度。
41.2 线程与进程
-
进程:是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间,一个应用程序可以同时运行多个进程;进程也是程序的一次执行过程,是系统运行程序的基本单位;系统运行一个程序即是一个进程从创建、运行到消亡的过程。
-
线程:线程是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程。一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序。
简而言之:一个程序运行后至少有一个进程,一个进程中可以包含多个线程
我们可以再电脑底部任务栏,右键----->打开任务管理器,可以查看当前任务的进程:
进程
线程
线程调度:
-
分时调度
所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间。
-
抢占式调度
优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度。
- 设置线程的优先级
-
抢占式调度详解
大部分操作系统都支持多进程并发运行,现在的操作系统几乎都支持同时运行多个程序。比如:现在我们上课一边使用编辑器,一边使用录屏软件,同时还开着画图板,dos窗口等软件。此时,这些程序是在同时运行,”感觉这些软件好像在同一时刻运行着“。
实际上,CPU(中央处理器)使用抢占式调度模式在多个线程间进行着高速的切换。对于CPU的一个核而言,某个时刻,只能执行一个线程,而 CPU的在多个线程间切换速度相对我们的感觉要快,看上去就是在同一时刻运行。
其实,多线程程序并不能提高程序的运行速度,但能够提高程序运行效率,让CPU的使用率更高。
41.3 创建线程类
Java使用java.lang.Thread
类代表线程,所有的线程对象都必须是Thread类或其子类的实例。每个线程的作用是完成一定的任务,实际上就是执行一段程序流即一段顺序执行的代码。Java使用线程执行体来代表这段程序流。Java中通过继承Thread类来创建并启动多线程的步骤如下:
- 定义Thread类的子类,并重写该类的run()方法,该run()方法的方法体就代表了线程需要完成的任务,因此把run()方法称为线程执行体。
- 创建Thread子类的实例,即创建了线程对象
- 调用线程对象的start()方法来启动该线程
代码如下:
测试类:
package com.tipdm.Demo03;
/**
* 创建多线程程序的第一种方式:创建Thread类的子类
* java.lang.Thread类:是描述线程的类,我们想要实现多线程程序,就必须继承Thread类
*
* 实现步骤:
* 1. 创建一个Thread类的子类
* 2. 在Thread类的子类中重写Thread类中的run方法,设置线程任务(开启线程要做什么?)
* 3. 创建Thread类的子类对象
* 4. 调用Thread类中的方法start方法,开始新的线程,执行run方法
* void start() 使该线程开始执行;Java 虚拟机调用该线程的 run 方法。
* 结果是两个线程并发地运行;当前线程(main)和另一个线程(创建的新线程)。
* 多次启动一个线程是非法的。特别是当线程已经结束执行后,不能再重新启动。
* java程序属于抢占式调度,哪个优先级高优先给哪个线程优先执行;同一个优先级,随机选择一个执行。
*/
public class demo2 {
public static void main(String[] args) {
//3. 创建Thread类的子类对象
MyThread myThread = new MyThread();
// 4. 调用Thread类中的方法start方法,开始新的线程,执行run方法
myThread.start();
for (int i = 0; i < 20; i++) {
System.out.println("main:" + i);
}
}
}
自定义线程类:
package com.tipdm.Demo03;
public class MyThread extends Thread{
public MyThread() {
super();
}
public MyThread(String name) {
super(name);
}
//2. 在Thread类的子类中重写Thread类中的run方法,设置线程任务(开启线程要做什么?)
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println("run:" + i);
}
}
}
41.4 线程名称
41.4.1 获取线程名称
获取线程的名称:
- 使用Thread类中的方法getName()
String getName() 返回该线程的名称。
- 可以先获利到当前正在执行的线程,使用线程中的方法getName()获取线程的名称
static Thread currentThread() 返回对当前正在执行的线程对象的引用
自定义线程类:
package com.tipdm.Demo01;
/**
* 获取线程的名称:
* 1. 使用Thread类中的方法getName()
* String getName() 返回该线程的名称。
* 2. 可以先获利到当前正在执行的线程,使用线程中的方法getName()获取线程的名称
* static Thread currentThread() 返回对当前正在执行的线程对象的引用
*/
// 定义一个Thread类的子类
public class MyThread extends Thread{
// 重写run方法
@Override
public void run() {
// // 获取线程名称
// String name = getName();
// System.out.println(name);
// Thread t = Thread.currentThread();
// System.out.println(t);
//
// String name = t.getName();
// System.out.println(name);
System.out.println(Thread.currentThread().getName());
}
}
主类:
package com.tipdm.Demo01;
/**
* 线程的名称:
* 主线程:main
* 新线程:Thread-0, Thread-1, Thread-2
*/
public class demo1 {
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
new MyThread().start();
new MyThread().start();
System.out.println(Thread.currentThread().getName()); // 主线程名称
}
}
41.4.2 设置线程名称
设置线程名称:(了解)
- 使用Thread类中的方法setName(名字)
void setName(String name) 改变线程名称,使之与参数 name 相同。
- 创建一个带参数的构造方法,参数传递线程的名称;
调用父类的带参构造方法,让线程名称传递给父类,让父类(Thread)给子线程起一个名字
Thread(String name) 分配新的 Thread 对象
自定义线程类:
package com.tipdm.Demo02;
import java.util.TreeMap;
/**
* 设置线程名称:(了解)
* 1. 使用Thread类中的方法setName(名字)
* void setName(String name) 改变线程名称,使之与参数 name 相同。
* 2. 创建一个带参数的构造方法,参数传递线程的名称;
* 调用父类的带参构造方法,让线程名称传递给父类,让父类(Thread)给子线程起一个名字
* Thread(String name) 分配新的 Thread 对象
*/
public class MyThread extends Thread{
public MyThread() {
}
public MyThread(String name) {
super(name);
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
主类:
package com.tipdm.Demo02;
public class demo1 {
public static void main(String[] args) {
// 开启多线程
MyThread mt = new MyThread();
mt.setName("小强");
mt.start();
// 开启多线程
new MyThread("旺财").start();
}
}
41.5 线程等待
package com.tipdm.Demo03;
/**
* public static void sleep(long millis) :使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)。
*
*/
public class demo1 {
public static void main(String[] args) {
for (int i = 0; i < 60; i++) {
System.out.println(i);
// 使用Thread类的sleep方法,让程序睡眠1s
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
41.6 创建多线程的第二种方式
创建多线程程序的第二种方式:实现Runnable接口
java.lang.Runnable
Runnable
接口应该由那些打算通过某一线程执行其实例的类来实现。类必须定义一个称为 run 的无参数方法。
java.lang.Thread
类的构造方法
Thread(Runnable target) 分配新的 Thread 对象。
Thread(Runnable target, String name) 分配新的 Thread 对象。
实现步骤:
-
创建一个Runnable接口的实现类
-
在实现类中重写Runnable接口的run方法,设置线程任务
-
创建一个Runnable接口的实现类对象
-
创建Thread类对象,构造方法中传递Runnable接口的实现类对象
-
调用Thread类中的start方法,开启新的线程执行run方法
实现Runnable接口创建多线程程序的好处:
- 避免了单继承的局限性
一个类只能继承一个类(一个人只能由一个亲爹),类继承了Thread类就不能继承其他的类
实现了Runnable接口,还可以继承其他的类,实现其他的接口
- 增强了程序的扩展性,降低了程序的耦合性(解耦)
实现了Runnable接口的方式,把设置线程任务和开启新线程任务进行了分离(解耦)
实现类中,重写了run方法:用来设置线程任务
创建Thread类对象,调用start方法:用来开启新线程。
自定义线程类1:
package com.tipdm.Demo04;
//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:
package com.tipdm.Demo04;
//1. 创建一个Runnable接口的实现类
public class RunnableImpl2 implements Runnable{
// 2. 在实现类中重写Runnable接口的run方法,设置线程任务
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println("helloWorld!" + i);
}
}
}
主类:
package com.tipdm.Demo04;
public class demo1 {
public static void main(String[] args) {
// 3. 创建一个Runnable接口的实现类对象
RunnableImpl run = new RunnableImpl();
// 4. 创建Thread类对象,构造方法中传递Runnable接口的实现类对象
// Thread t = new Thread(run); // 打印线程名称
Thread t2 = new Thread(new RunnableImpl2()); // 打印线程名称
// 5. 调用Thread类中的start方法,开启新的线程执行run方法
t2.start();
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName() + i);
}
}
}
41.7 匿名内部类方式实现线程的创建
匿名内部类方式实现线程的创建
- 匿名:没有名字
- 内部类:写在其他类内部的类
匿名内部类作用:简化代码
把子类继承父类,重写父类的方法,创建子类对象合一步完成。
把实现类实现类接口,重写接口中的方法,创建实现类对象合成一步完成
匿名内部类的最终产物:子类/实现类对象,而这个类没有名字
格式:
new 父类/接口(){
重写父类/接口中的方法
}
package com.tipdm.Demo05;
public class demo1 {
public static void main(String[] args) {
// 线程的父类是Thread
// new MyThread().start();
new Thread(){
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName() + "-->" + i);
}
}
}.start();
// 线程的接口Runnable
// Runnable r = new RunnableImpl(); // 多态
Runnable r = new Runnable(){
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName() + "-->" + i);
}
}
};
new Thread(r).start();
// 简化接口的方式
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName() + "-->" + i);
}
}
}).start();
}
}
41.8 线程安全
以电影院卖票为例:
线程安全导致的结果:可能会出现同一张电影票被卖给了不同的用户。
实现卖票案例
线程实现类:
package com.tipdm.Demo06;
/**
* 实现卖票案例
*/
public class RunnableImpl implements Runnable{
// 定义一个多线程共享的票源
private int ticket = 100;
// 设置线程任务:卖票
@Override
public void run() {
while(true){
// 先判断票是否存在
if(ticket > 0){
// 票存在,卖票 ticket--
System.out.println(Thread.currentThread().getName() + "--->" + "正在卖第" + ticket + "张票");
ticket--;
}else{
break;
}
// 提高线程安全问题出现的概率,让程序睡眠
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
主类:
package com.tipdm.Demo06;
/**
* 模拟卖票案例
* 创建3个线程,同时开启,对共享的票进行出售
*/
public class demo1Ticket {
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);
// 调用start方法开启多线程
t0.start();
t1.start();
t2.start();
// Thread-2--->正在卖第4张票
// Thread-1--->正在卖第4张票
// Thread-0--->正在卖第4张票
// 出现线程安全问题,出现重复的票
}
}
41.8.1 解决线程安全问题——使用同步代码块
卖票案例出现了线程安全问题
卖出了不存在的票和重复的票
解决线程安全问题的一种方案:使用同步代码块
格式:
synchronized(锁对象){
可能会出现线程安全问题的代码(访问了共享数据的代码)
}
注意:
-
通过代码块中的锁对象,可以使用任意的对象
-
但是必须保证多个线程使用的锁对象是同一个
-
锁对象作用:
把同步代码块锁住,只让一个线程在同步代码块中执行。
线程实现类:
package com.tipdm.Demo07;
public class RunnableImpl implements Runnable{
// 定义一个多线程共享的票源
private int ticket = 100;
// 创建一个锁对象
Object obj = new Object();
// 设置线程任务:卖票
@Override
public void run() {
while(true){
// 同步代码块
synchronized (obj){
// 先判断票是否存在
if(ticket > 0){
// 票存在,卖票 ticket--
System.out.println(Thread.currentThread().getName() + "--->" + "正在卖第" + ticket + "张票");
// 提高线程安全问题出现的概率,让程序睡眠
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
ticket--;
}else{
break;
}
}
}
}
}
主类:
package com.tipdm.Demo07;
/**
* 模拟卖票案例
* 创建3个线程,同时开启,对共享的票进行出售
*/
public class demo1Ticket {
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);
// 调用start方法开启多线程
t0.start();
t1.start();
t2.start();
}
}
41.8.2 解决线程安全问题——使用同步方法
解决线程安全问题的第二种方案:使用同步方法
使用步骤:
-
把访问了共享数据的代码抽取出来,放到一个方法种
-
在方法上添加synchronized修饰符
格式:定义方法的格式
修饰符 synchronized 返回值类型 方法名(参数列表){
可能会出现线程安全问题的代码(访问了共享数据的代码)
}
线程实现类:
package com.tipdm.Demo08;
public class RunnableImpl implements Runnable{
// 定义一个多线程共享的票源
private int ticket = 100;
// 设置线程任务:卖票
@Override
public void run() {
System.out.println("this:" + this);
while(ticket>0){
payTicket();
}
}
/**
* 定义一个同步方法
* 同步方法也会把方法内部的代码锁住
* 只让一个线程执行
* 同步方法的锁对象是谁?
* 就算实现类对象 new RunnableImpl()
* 也就是this
*/
public /*synchronized*/ void payTicket(){
synchronized (this){
// 先判断票是否存在
if(ticket > 0){
// 票存在,卖票 ticket--
System.out.println(Thread.currentThread().getName() + "--->" + "正在卖第" + ticket + "张票");
// 提高线程安全问题出现的概率,让程序睡眠
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
ticket--;
}
}
}
}
主类:
package com.tipdm.Demo08;
/**
* 模拟卖票案例
* 创建3个线程,同时开启,对共享的票进行出售
*/
public class demo1Ticket {
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);
// 调用start方法开启多线程
t0.start();
t1.start();
t2.start();
}
}
41.8.3 解决线程安全问题——使用静态同步方法
解决线程安全问题的第二种方案:使用静态同步方法
使用步骤:
-
把访问了共享数据的代码抽取出来,放到一个方法种
-
在方法上添加synchronized修饰符
格式:定义方法的格式
修饰符 synchronized 返回值类型 方法名(参数列表){
可能会出现线程安全问题的代码(访问了共享数据的代码)
}
线程实现类:
package com.tipdm.Demo09;
public class RunnableImpl implements Runnable{
// 定义一个多线程共享的票源
private static int ticket = 100;
// 设置线程任务:卖票
@Override
public void run() {
System.out.println("this:" + this);
while(ticket>0){
payTicketStatic();
}
}
/**
* 静态的同步方法
* 锁对象是谁?
* 不能是this
* this是创建对象之后产生的,静态方法优先于对象
* 静态方法的锁对象是本类的class属性 --> class文件对象(反射)
*/
public static /*synchronized*/ void payTicketStatic(){
synchronized (RunnableImpl.class) {
// 先判断票是否存在
if (ticket > 0) {
// 票存在,卖票 ticket--
System.out.println(Thread.currentThread().getName() + "--->" + "正在卖第" + ticket + "张票");
// 提高线程安全问题出现的概率,让程序睡眠
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
ticket--;
}
}
}
}
主类:
package com.tipdm.Demo09;
/**
* 模拟卖票案例
* 创建3个线程,同时开启,对共享的票进行出售
*/
public class demo1Ticket {
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);
// 调用start方法开启多线程
t0.start();
t1.start();
t2.start();
}
}
41.8.4 解决线程安全问题——使用Lock锁
解决线程安全问题的第三种方案:使用Lock锁
java.util.concurrent.locks.Lock
接口
Lock接口种的方法:
void Lock() 获取锁
void unlock() 释放锁
java.util.concurrent.locks.ReentrantLock implements Lock
接口
使用步骤:
-
在成员位置创建一个ReentrantLock对象
-
在可能会出现安全问题的代码前调用Lock接口中的方法lock获取锁
-
在可能会出现安全问题的代码后调用Lock接口中的方法unlock释放锁
线程实现类:
package com.tipdm.Demo10;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class RunnableImpl implements Runnable{
// 定义一个多线程共享的票源
private int ticket = 100;
// 1. 在成员位置创建一个ReentrantLock对象
Lock l = new ReentrantLock();
// // 设置线程任务:卖票
// @Override
// public void run() {
// while(ticket > 0){
// l.lock();
// // 票存在,卖票 ticket--
// System.out.println(Thread.currentThread().getName() + "--->" + "正在卖第" + ticket + "张票");
// // 提高线程安全问题出现的概率,让程序睡眠
// try {
// Thread.sleep(10);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// ticket--;
// l.unlock();
// }
// }
// 设置线程任务:卖票
@Override
public void run() {
while(ticket > 0){
l.lock();
// 提高线程安全问题出现的概率,让程序睡眠
try {
// 票存在,卖票 ticket--
System.out.println(Thread.currentThread().getName() + "--->" + "正在卖第" + ticket + "张票");
ticket--;
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
l.unlock(); // 无论程序是否异常都会把锁释放,提高程序效率。
}
}
}
}
主类:
package com.tipdm.Demo10;
/**
* 模拟卖票案例
* 创建3个线程,同时开启,对共享的票进行出售
*/
public class demo1Ticket {
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);
// 调用start方法开启多线程
t0.start();
t1.start();
t2.start();
}
}
41.9 线程状态
当线程被创建并启动后,它既不是一启动就进入了执行状态。在线程的生命周期中,有几种状态呢?在API中java.lang.Thread.State
这个枚举中给出了六个线程状态。
41.9.1 Timed Waiting(计时等待)
Timed Waiting在API中的描述为:一个正在限时等待另一个线程执行一个(唤醒)动作的线程处于这一状态。单独的去理解这句话,真是玄之又玄,其实我们在之前的操作中已经接触过这个状态了,在哪里呢?
在我们写卖票的案例中,为了减少线程执行太快,现象不明显等问题,我们在run方法中添加了sleep语句,这样就强制当前正在执行的线程休眠(暂停执行),以“减慢线程”。
其实当我们调用了sleep方法之后,当前执行的线程就进入到“休眠状态”,其实就是所谓的Timed Waiting(计时等待),那么我们通过一个案例加深对该状态的一个理解。
try{
Thread.sleep(5000);
}catch(Eception e){
e.printStackTrace();
}
41.9.2 Blocked(锁阻塞)
Blocked 状态在API中的介绍为:一个正在阻塞等待一个监视器锁(锁对象)的线程处于这一状态。
我们已经学完同步机制,那么这个状态是非常好理解的了。比如,线程A与线程B代码中使用同一锁,如果线程A获取到锁,线程A进入到Runnable状态,那么线程B就进入到Blocked锁阻塞状态。
41.9.3 Waiting(无限等待)
Wating状态在API中介绍为:一个正在无限期等待另一个线程执行一个特别的(唤醒)动作的线程处于这一状态。
等待唤醒案例:线程之间的通信
创建一个顾客线程(消费者):告知老板要的包子的种类和数量,调用wait方法,放弃cpu的执行,进入到WAITING状态(无线等待)
创建一个老板线程(生产者):花了5秒做包子,做好包子之后,调用notify方法,唤醒顾客吃包子。
注意:
- 顾客和老板线程必须使用同步代码包裹起来,保证等待和唤醒只能有一个在执行
- 同步使用的锁对象必须保证唯一
- 只有锁对象才能调用wait和notify方法
Object类中的方法
- void wait() 在其他线程调用此对象的notify()方法或 notifyAll()方法前,导致当前线程等待。
- void notify() 唤醒在此对象监视器上等待的单个线程。 会继续执行wait方法之后的代码
package com.tipdm.Demo11;
public class demo1 {
public static void main(String[] args) {
// 创建锁对象,保证唯一
Object obj = new Object();
// 创建一个顾客线程(消费者)
new Thread(){
@Override
public void run() {
while(true){
// 告知老板要的包子的种类和数量
System.out.println("告知老板要的包子的种类和数量");
// 保证等待和唤醒的线程只能有一个执行,需要使用同步技术
synchronized (obj){
// 进入等待状态,等老板做包子
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("包子真香!!!!!!!!!!");
System.out.println("------------------");
}
}
}.start();
// 创建一个老板线程(生产者)
new Thread(){
@Override
public void run() {
while(true){
// 花了5秒做包子
try {
Thread.sleep(5000);
System.out.println("包子做好了!");
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (obj){
// 包子做好了唤醒顾客吃包子
obj.notify();
}
}
}
}.start();
}
}
进入到TimeWaiting(计时等待)有两种方式
-
使用sleep(long m)方法,在毫秒值结束之后,线程睡醒进入到Runnable/Blocked状态
-
使用wait(long m)方法,wait方法如果在毫秒值结束之后,还没有被notify唤醒,就会自动醒来,线程睡醒进入到Runnable/Blocked状态
package com.tipdm.Demo11;
public class demo2 {
public static void main(String[] args) {
// 创建锁对象,保证唯一
Object obj = new Object();
// 创建一个顾客线程(消费者)
new Thread(){
@Override
public void run() {
while(true){
// 告知老板要的包子的种类和数量
System.out.println("告知老板要的包子的种类和数量");
// 保证等待和唤醒的线程只能有一个执行,需要使用同步技术
synchronized (obj){
// 进入等待状态,等老板做包子
try {
obj.wait(5000); // 就算不被唤醒,也将在5s后自动醒来
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("包子真香!!!!!!!!!!");
System.out.println("------------------");
}
}
}.start();
}
}
void notifyAll() 唤醒在此对象监视器上等待的所有线程。 会继续执行wait方法之后的代码
package com.tipdm.Demo11;
/**
* void notifyAll() 唤醒在此对象监视器上等待的所有线程。 会继续执行wait方法之后的代码
*/
public class demo3 {
public static void main(String[] args) {
// 创建锁对象,保证唯一
Object obj = new Object();
// 创建一个顾客线程(消费者)
new Thread(){
@Override
public void run() {
while(true){
// 告知老板要的包子的种类和数量
System.out.println("顾客1,告知老板要的包子的种类和数量");
// 保证等待和唤醒的线程只能有一个执行,需要使用同步技术
synchronized (obj){
// 进入等待状态,等老板做包子
try {
obj.wait(5000); // 就算不被唤醒,也将在5s后自动醒来
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("顾客1,包子真香!!!!!!!!!!");
}
}
}.start();
// 创建一个顾客线程(消费者)
new Thread(){
@Override
public void run() {
while(true){
// 告知老板要的包子的种类和数量
System.out.println("顾客2,告知老板要的包子的种类和数量");
// 保证等待和唤醒的线程只能有一个执行,需要使用同步技术
synchronized (obj){
// 进入等待状态,等老板做包子
try {
obj.wait(5000); // 就算不被唤醒,也将在5s后自动醒来
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("顾客2,包子真香!!!!!!!!!!");
}
}
}.start();
// 创建一个老板线程(生产者)
new Thread(){
@Override
public void run() {
while(true){
// 花了5秒做包子
try {
Thread.sleep(5000);
System.out.println("包子做好了!");
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (obj){
// 包子做好了唤醒顾客吃包子
obj.notifyAll(); // 唤醒所有等待的线程
}
}
}
}.start();
}
}