系列文章目录
JavaSE进阶01:继承、修饰符
JavaSE进阶02:多态、抽象类、接口
JavaSE进阶03:内部类、Lambda表达式
JavaSE进阶04:API中常用工具类
JavaSE进阶05:包装类、递归、数组的高级操作、异常
JavaSE进阶06:Collection集合、迭代器、List、ArrayList、LinkedList
JavaSE进阶07:泛型、Set集合、TreeSet、二叉树、红黑树
JavaSE进阶08:HashSet、Map集合、HashMap、TreeMap、可变参数、不可变集合
JavaSE进阶09:Stream流、File类
JavaSE进阶10:IO流、字节流、字节缓冲流
JavaSE进阶11:字符流、字符缓冲流、转换流、对象操作流、Properties集合
JavaSE进阶扩充:JDK8 HashMap底层分析(了解)
JavaSE进阶12:多线程
Java进阶作业
文章目录
1.多线程的概念
1.1 并发和并行【重点】
并行:在同一时刻,有多个指令在多个CPU上同时执行
并发:在一段时间内,有多个指令在单个CPU上交替执行
如:以前淘宝双十一的高并发导致服务器崩溃了
1.2 进程和线程【重点】
- 进程:是正在运行的程序
- 独立性:进程是一个能独立运行的基本单位,也是操作系统分配和调度的最小单元
- 动态性:进程的实质是程序的一次执行过程,进程是动态产生,动态消亡的。
- 并发性:任何进程都可以同其他进程一起并发制作
- 线程:是进程中的单个顺序控制流,是一条执行路径
- 程序中做的事情, 线程是CPU调度最小单元
- 单线程:一个进程只有一条执行路径
- 多线程:一个进程有多条执行路径
- 采用多线程技术可以同时执行多个任务
- 多线程需要硬件支持
- 线程和进程的关系
- 线程它是进程的一部分,不能独立存在
- 一个进程,可以有多条线程,至少有一条线程
- 多线程的作用
- 可以让程序同时做不同的事情,提高程序的执行效率(例如迅雷同时下载多个文件)
2.多线程的实现方式
- 实现多线程的方式有哪些?
- 继承Thread类的方式进行实现
- 实现Runnable接口的方式进行实现
- 利用Callable和Future接口方式实现
- 多线程执行特点
运行的结果每次有可能不一样,表示有随机性,因为线程的执行是由CPU来执行的,由CPU说了算。
2.1实现多线程方式一:继承Thread类【重点】
- Thread类:表示线程的类
方法名 | 说明 |
---|---|
void run() | 在线程开启后,此方法将被调用执行 |
void start() | 使此线程开始执行,Java虚拟机会调用此线程的run方法() |
public class MyThread extends Thread{//继承Thread类
private String name;
public MyThread(String name) {
this.name = name;
}
@Override
public void run() {//重写run()方法
//代码就是线程在开启之后执行的代码
for (int i = 0; i < 100; i++) {
System.out.println(name+"线程开启了" + i);
}
}
}
class Demo {
public static void main(String[] args) {
//创建一个线程对象
MyThread t1 = new MyThread("线程t1");
//创建一个线程对象
MyThread t2 = new MyThread("线程t2");
//开启一条线程
t1.start();
//开启第二条线程
t2.start();
}
}
/*
线程t2线程开启了0
线程t1线程开启了0
线程t2线程开启了1
线程t1线程开启了1
线程t2线程开启了2
线程t1线程开启了2
线程t2线程开启了3
线程t1线程开启了3
线程t2线程开启了4
线程t1线程开启了4
线程t2线程开启了5
线程t2线程开启了6
线程t2线程开启了7
线程t1线程开启了5
...
*/
2.2 多线程的实现方式-两个小问题【了解】
- 为什么要重写run()方法?
- 因为run()是用来封装被线程执行的代码
- run()方法和start()方法的区别?
- run():封装线程执行的代码,直接调用,相当于普通方法的调用
- start():启动线程;然后由JVM调用此线程的run()方法
2.3实现多线程方式二:实现Runnable接口【重点】
1.Thread构造方法
方法名 | 说明 |
---|---|
Thread(Runnable target) | 分配一个新的Thread对象 |
2.实现步骤
- 定义一个类MyRunnable实现Runnable接口
- 在MyRunnable类中重写run()方法
- 创建MyRunnable类的对象
- 创建Thread类的对象,把MyRunnable对象作为构造方法的参数
- 启动线程
public class MyRunnable implements Runnable{
@Override
public void run() {
//线程启动后执行的代码
for (int i = 0; i < 100; i++) {
System.out.println("第二种方式实现多线程" + i);
}
}
}
public class Demo {
public static void main(String[] args) {
//创建了一个参数的对象
MyRunnable mr = new MyRunnable();
//创建了一个线程对象,并把参数传递给这个线程.
//在线程启动之后,执行的就是参数里面的run方法
Thread t1 = new Thread(mr);
//开启线程
t1.start();
MyRunnable mr2 = new MyRunnable();
Thread t2 = new Thread(mr2);
t2.start();
}
}
/*
第二种方式Runnable实现多线程0
第二种方式Runnable实现多线程0
第二种方式Runnable实现多线程1
第二种方式Runnable实现多线程1
第二种方式Runnable实现多线程2
第二种方式Runnable实现多线程2
第二种方式Runnable实现多线程3
第二种方式Runnable实现多线程3
第二种方式Runnable实现多线程4
第二种方式Runnable实现多线程4
第二种方式Runnable实现多线程5
第二种方式Runnable实现多线程6
...
*/
2.4 实现多线程方式三: 实现Callable接口【重点】
1.方法介绍
方法名 | 说明 |
---|---|
V call() | 计算结果,如果无法计算结果,则抛出一个异常 |
FutureTask(Callable callable) | 创建一个 FutureTask,一旦运行就执行给定的 Callable |
V get() | 如有必要,等待计算完成,然后获取其结果 |
2.实现步骤
- 定义一个类MyCallable实现Callable接口
- 在MyCallable类中重写call()方法
- 创建MyCallable类的对象
- 创建Future的实现类FutureTask对象,把MyCallable对象作为构造方法的参数
- 创建Thread类的对象,把FutureTask对象作为构造方法的参数
- 启动线程
- 再调用get方法,就可以获取线程结束之后的结果。
3.注意事项
get()方法的调用一定要在Thread类对象调用start()方法之后
import java.util.concurrent.Callable;
public class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
for (int i = 0; i < 5; i++) {
System.out.println("跟女孩表白" + i);
}
//返回值就表示线程运行完毕之后的结果
return "答应";
}
}
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class Demo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//线程开启之后需要执行里面的call方法
MyCallable mc = new MyCallable();
//Thread t1 = new Thread(mc);//报错,Thread的参数只有Runnable
//可以获取线程执行完毕之后的结果.也可以作为参数传递给Thread对象
FutureTask<String> ft = new FutureTask<>(mc);
//创建线程对象
Thread t1 = new Thread(ft);
//String s = ft.get(); //若写在开启线程前,就会在此死等不向下继续运行。
//开启线程
t1.start();
String s = ft.get();//返回值是callable接口的,所以用ft调用
System.out.println(s);
}
}
/*
跟女孩表白0
跟女孩表白1
跟女孩表白2
跟女孩表白3
跟女孩表白4
答应
*/
2.5 三种实现方式的对比【重点】
实现方式 | 优点 | 缺点 |
---|---|---|
实现Runnable、Callable接口 | 扩展性强,实现该接口的同时还可以继承其他的类 | 编程相对复杂,不能直接使用Thread类中的方法 |
继承Thread类 | 编程比较简单,可以直接使用Thread类中的方法 | 可以扩展性较差,不能再继承其他的类 |
- 实际开发过程中如何选择?
- 如果不需要任务的返回结果,就实现Runnable接口,需要的话,实现Callable接口
- 如何去写多线程程序
- 1.确定要做什么,执行什么任务
- 2.要不要用多线程,要任务返回的结果,如果要实现Callable接口,如果不要实现Runnable接口
- 3.实现call方法或run方法
- 4.交给线程类,执行start(),开启线程干活
3.线程类中的常见方法
3.1getName/setName获取设置线程名称【重点】
方法名 | 说明 |
---|---|
void setName(String name) | 将此线程的名称更改为等于参数name |
String getName() | 返回此线程的名称 |
public class MyThread extends Thread {
public MyThread() {
}
//构造方法不能继承,快捷键加入有参构造
public MyThread(String name) {
super(name);
}
//getName方法获取线程名
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(getName() + "@@@" + i);
}
}
}
class Demo {
//1,线程是有默认名字的,格式:Thread-编号
public static void main(String[] args) {
//方式一:有参构造设置线程名
MyThread t1 = new MyThread("小蔡");
MyThread t2 = new MyThread("小强");
//方法二:setName设置线程名
//t1.setName("小蔡");
//t2.setName("小强");
t1.start();
t2.start();
}
}
/*
小蔡@@@0
小强@@@0
小蔡@@@1
小强@@@1
小蔡@@@2
小强@@@2
小蔡@@@3
小强@@@3
小强@@@4
小蔡@@@4
*/
3.2 currentThread获得当前线程对象【重点】
作用:因为用接口实现多线程不能使用Thread类中的方法,但通过获取当前线程对象方法currentThread就可以。
方法名 | 说明 |
---|---|
static Thread currentThread() | 返回对当前正在执行的线程对象的引用 |
public class Demo {
public static void main(String[] args) {
String name = Thread.currentThread().getName();//getName是Thread类中的静态方法,Demo没有继承线程类
System.out.println(name);
}
}
/*
main
*/
3.3 sleep线程休眠【重点】
方法名 | 说明 |
---|---|
static void sleep(long millis) | 使当前正在执行的线程停留(暂停执行)指定的毫秒数 |
1秒=1000毫秒
package sleep;
public class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
try {
Thread.sleep(100);//接口实现时只能捕获异常
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "---" + i);
}
}
}
class Demo {
public static void main(String[] args) {
MyRunnable mr = new MyRunnable();
Thread t1 = new Thread(mr);
Thread t2 = new Thread(mr);
t1.start();
t2.start();
}
}
注意:
Runnable接口中的run方法没有抛出异常,所以其所有实现类和子类都不能抛出异常,只能自己捕获异常。
3.4 getPriority/setPriority获取设置线程优先级【了解】
1.线程调度
线程有两种调度模型:
- 分时调度模型:所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片
- 抢占式调度模型:优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的 CPU 时间片相对多一些
- Java使用的是抢占式调度模型
2.优先级相关方法
项目中一般很少使用,一般都是公平竞争。
方法名 | 说明 |
---|---|
final int getPriority() | 返回此线程的优先级 |
final void setPriority(int newPriority) | 更改此线程的优先级线程默认优先级是5;线程优先级的范围是:1-10 |
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + "---" + i);
}
return "线程执行完毕了";
}
}
class Demo {
public static void main(String[] args) {
//优先级: 1 - 10 默认值:5
MyCallable mc = new MyCallable();
FutureTask<String> ft = new FutureTask<>(mc);
Thread t1 = new Thread(ft);
t1.setName("飞机");
t1.setPriority(10);
System.out.println(t1.getPriority());//10
t1.start();
FutureTask<String> ft2 = new FutureTask<>(mc);
Thread t2 = new Thread(ft2);
t2.setName("坦克");
t2.setPriority(1);
System.out.println(t2.getPriority());//1
t2.start();
}
}
/*
10
1
坦克---0
飞机---0
坦克---1
飞机---1
坦克---2
飞机---2
坦克---3
飞机---3
坦克---4
飞机---4
坦克---5
飞机---5
飞机---6
飞机---7
飞机---8
飞机---9
...
*/
3.5 setDaemon设置守护线程【了解】
1.什么是守护线程?
- 守护其它线程,当普通线程执行完毕了,守护线程就没有存在的必要了
2.如何使用守护线程?
相关方法
方法名 | 说明 |
---|---|
void setDaemon(boolean on) | 将此线程标记为守护线程,当运行的线程都是守护线程时,Java虚拟机将退出 |
代码演示
public class MyThread1 extends Thread{
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(this.getName()+"---"+i);
}
}
}
class MyThread2 extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(this.getName()+"---"+i);
}
}
}
class Test {
public static void main(String[] args) {
MyThread1 t1 = new MyThread1();//主人
MyThread2 t2 = new MyThread2();//保镖
t2.setDaemon(true);//用
t1.setName("主人");
t2.setName("保镖");
t2.start();
t1.start();
}
}
/*
主人---0
保镖---0
主人---1
保镖---1
主人---2
主人---3
主人---4
保镖---2
保镖---3
保镖---4
保镖---5
保镖---6
保镖---7
保镖---8
保镖---9
保镖---10//没有普通线程,守护现在再运行一会儿就提前结束了
*/
4.线程的生命周期
5.线程同步
5.1 卖票【难点】
- 案例需求
- 某电影院目前正在上映国产大片,共有100张票,而它有3个窗口卖票,请设计一个程序模拟该电影院卖票
没注意线程安全的错误示范:
public class Ticket implements Runnable {
//票的数量
private int ticket = 10;//三个线程共有
@Override
public void run() {
while(true){
if(ticket <= 0){//卖完了
break;
}else{
try {
Thread.sleep(100);//机器出票时间
} catch (InterruptedException e) {
e.printStackTrace();
}
ticket--;//出票成功
System.out.println(Thread.currentThread().getName() + "在卖票,还剩下" + ticket + "张票");
}
}
}
}
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();
}
}
/*
窗口一在卖票,还剩下7张票
窗口三在卖票,还剩下7张票
窗口二在卖票,还剩下7张票
窗口二在卖票,还剩下5张票
窗口三在卖票,还剩下4张票
窗口一在卖票,还剩下5张票
窗口三在卖票,还剩下3张票
窗口二在卖票,还剩下2张票
窗口一在卖票,还剩下2张票
窗口三在卖票,还剩下-1张票
窗口二在卖票,还剩下-1张票
窗口一在卖票,还剩下-1张票
*/
5.2 线程安全问题-原因分析【难点】
1.什么是线程安全问题?
所谓的线程安全问题就是多线程环境下,出现数据结果与预期不一致的情况就称为线程安全问题
2.卖票出现了问题
三个线程几乎同时出票时,ticket减1同时执行了3次后再打印。
3.出现重复票和负号票的原因
卖票的过程中有多条线程操作了共享数据
5.3 解决方式一:synchronized同步代码块【重点】
1.如何解决上述问题呢?
-
任意时刻只有一条线程可以**操作(增、删、改、查)**共享变量
-
Java中如何解决?使用同步代码块。
//同步代码块格式:
synchronized(任意对象) {
操作共享数据的代码
}
- 同步的好处和弊端
- 好处:解决了多线程的数据安全问题
- 弊端:当线程很多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率
synchronized(任意对象):就相当于给代码加锁了,任意对象就可以看成是一把锁
2.synchronized同步代码块的特点?
- 默认情况下是打开的,只要有一个线程进去执行代码了,锁就会关闭
- 当锁中代码执行完,锁才会自动打开
3.什么情况下会出现线程安全问题呢?
- 多条线程操作共享数据(可以拆分为三个条件):
- 1.多线程环境
- 2.有共享数据
- 3.多条线程操作了共享数据
因此,在卖票的时候,加不加Thread.sleep()代码中都会有线程安全问题,只不过加上之后,线程执行的速度慢了下来,我们可以观察到问题而已
正确代码演示
package Ticket;
public class Ticket implements Runnable {
//票的数量
private int ticket = 10;
private Object obj = new Object();
@Override
public void run() {
while(true){
synchronized (obj){//多个线程必须使用同一把锁
if(ticket <= 0){//卖完了
break;
}else{
try {
Thread.sleep(100);//机器出票时间
} catch (InterruptedException e) {
e.printStackTrace();
}
ticket--;//出票成功
System.out.println(Thread.currentThread().getName() + "在卖票,还剩下" + ticket + "张票");
}
}
}
}
}
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();
}
}
/*
窗口一在卖票,还剩下9张票
窗口一在卖票,还剩下8张票
窗口二在卖票,还剩下7张票
窗口二在卖票,还剩下6张票
窗口二在卖票,还剩下5张票
窗口二在卖票,还剩下4张票
窗口二在卖票,还剩下3张票
窗口二在卖票,还剩下2张票
窗口三在卖票,还剩下1张票
窗口三在卖票,还剩下0张票
*/
5.4 线程安全问题-锁对象唯一【重点】
锁对象为什么要唯一?
相当于在操作共享数据的房子中,每个线程都有对应的门,都可以通过自己的门进入房子中。
public class MyThread extends Thread {
private static int ticketCount = 100;
private static final Object obj = new Object();//同一把锁不想被修改加final
@Override
public void run() {
while(true){
synchronized (obj){ //锁唯一
if(ticketCount <= 0){
//卖完了
break;
}else{
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
ticketCount--;
System.out.println(Thread.currentThread().getName() + "在卖票,还剩下" + ticketCount + "张票");
}
}
}
}
}
class Demo {
public static void main(String[] args) {
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
t1.setName("窗口一");
t2.setName("窗口二");
t1.start();
t2.start();
}
}
- 注意
- 重写父类Thread 的run方法:锁对象为this时,每个Thread实现类的锁对象是Thread实现类本身,锁对象不唯一。
- 重写接口Runnable 的run方法:锁对象为this时,每个Thread的锁对象都是Runnable 的实现类,锁对象唯一。
5.5 解决方式二:synchronized 同步方法【重点】
1.同步方法的格式
同步方法:就是把synchronized关键字加到方法上
修饰符 synchronized 返回值类型 方法名(方法参数) {
方法体;
}
2.同步方法的锁对象是:this
3.静态同步方法的锁对象是:类名.class。static方法不能用this和super等关键字。
4.静态同步方法的格式
同步静态方法:就是把synchronized关键字加到静态方法上
修饰符 static synchronized 返回值类型 方法名(方法参数) {
方法体;
}
同步方法示例代码:
public class MyRunnable implements Runnable {
private int ticketCount = 100;
@Override
public void run() {
while(true){
if("窗口一".equals(Thread.currentThread().getName())){
//同步方法
boolean result = synchronizedMthod();
if(result){
break;
}
}
if("窗口二".equals(Thread.currentThread().getName())){
//同步代码块
synchronized (this){//同步方法锁对象this
if(ticketCount == 0){
break;
}else{
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
ticketCount--;
System.out.println(Thread.currentThread().getName() + "在卖票,还剩下" + ticketCount + "张票");
}
}
}
}
}
private synchronized boolean synchronizedMthod() {
if(ticketCount == 0){
return true;
}else{
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
ticketCount--;
System.out.println(Thread.currentThread().getName() + "在卖票,还剩下" + ticketCount + "张票");
return false;
}
}
}
class Demo {
public static void main(String[] args) {
MyRunnable myRunnable=new MyRunnable();
Thread t1 = new Thread(myRunnable);
Thread t2 = new Thread(myRunnable);
t1.setName("窗口一");
t2.setName("窗口二");
t1.start();
t2.start();
}
}
静态同步方法示例:
public class MyRunnable implements Runnable {
private static int ticketCount = 100;//变更处1:静态方法访问静态变量
@Override
public void run() {
while(true){
if("窗口一".equals(Thread.currentThread().getName())){
//同步方法
boolean result = synchronizedMthod();
if(result){
break;
}
}
if("窗口二".equals(Thread.currentThread().getName())){
//同步代码块
synchronized (MyRunnable.class){//变更处2:静态同步方法的锁对象:类名.class
if(ticketCount == 0){
break;
}else{
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
ticketCount--;
System.out.println(Thread.currentThread().getName() + "在卖票,还剩下" + ticketCount + "张票");
}
}
}
}
}
private static synchronized boolean synchronizedMthod() {//变更处3:在静态方法加synchronized
if(ticketCount == 0){
return true;
}else{
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
ticketCount--;
System.out.println(Thread.currentThread().getName() + "在卖票,还剩下" + ticketCount + "张票");
return false;
}
}
}
class Demo {
public static void main(String[] args) {
MyRunnable myRunnable=new MyRunnable();
Thread t1 = new Thread(myRunnable);
Thread t2 = new Thread(myRunnable);
t1.setName("窗口一");
t2.setName("窗口二");
t1.start();
t2.start();
}
}
5.6 解决方式三:Lock锁【重点】
1.如何手动开关锁呢?答:使用lock
2.如何使用Lock?答:Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化
ReentrantLock构造方法
方法名 | 说明 |
---|---|
ReentrantLock() | 创建一个ReentrantLock的实例 |
加锁解锁方法
方法名 | 说明 |
---|---|
void lock() | 获得锁 |
void unlock() | 释放锁 |
package lock;
import java.util.concurrent.locks.ReentrantLock;
public class Ticket implements Runnable {
//票的数量
private int ticket = 100;
private ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
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();//不管是否异常,一定释放锁
}
}
}
}
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();
}
}
5.7 死锁【了解】
1.什么是死锁:线程死锁是指由于两个或者多个线程互相持有对方所需要的资源,导致这些线程处于等待状态,无法前往执行。
2.导致死锁的原因:进行了锁的嵌套,以后不准写锁的嵌套。
代码演示
6.线程的等待和唤醒
6.1 生产者和消费者思路分析【了解】
目的:两个线程轮流执行。
6.2 生产者和消费者案例【了解】
Object类的等待和唤醒方法
方法名 | 说明 |
---|---|
void wait() | 导致当前线程等待同时释放锁,直到另一个线程调用该对象的 notify()方法或 notifyAll()方法 |
void notify() | 唤醒正在等待单个线程,并不立即释放锁 |
void notifyAll() | 唤醒正在等待所有线程,并不立即释放锁 |
-
案例需求
-
桌子类(Desk):定义表示包子数量的变量,定义锁对象变量,定义标记桌子上有无包子的变量
-
生产者类(Cooker):实现Runnable接口,重写run()方法,设置线程任务
1.判断是否有包子,决定当前线程是否执行
2.如果有包子,就进入等待状态,如果没有包子,继续执行,生产包子
3.生产包子之后,更新桌子上包子状态,唤醒消费者消费包子
-
消费者类(Foodie):实现Runnable接口,重写run()方法,设置线程任务
1.判断是否有包子,决定当前线程是否执行
2.如果没有包子,就进入等待状态,如果有包子,就消费包子
3.消费包子后,更新桌子上包子状态,唤醒生产者生产包子
-
测试类(Demo):里面有main方法,main方法中的代码步骤如下
创建生产者线程和消费者线程对象
分别开启两个线程
-
-
代码实现
/*桌子类*/
public class Desk {
//定义一个标记
//true 就表示桌子上有汉堡包的,此时允许吃货执行
//false 就表示桌子上没有汉堡包的,此时允许厨师执行
public static boolean flag = false;
//能吃汉堡包的总数量
public static int count = 3;
//锁对象
public static final Object lock = new Object();
}
/*厨师类*/
public class Cooker extends Thread {
@Override
public void run() {
while(true){
synchronized (Desk.lock){
if(Desk.count == 0){//吃不下不生产了
break;
}else{
if(!Desk.flag){
//生产
System.out.println("厨师正在生产汉堡包");
Desk.flag = true;
Desk.lock.notifyAll();//唤醒消费者
}else{
try {
Desk.lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
}
/*消费者类*/
public class Foodie extends Thread {
@Override
public void run() {
//套路:
//1. while(true)死循环
//2. synchronized 锁,锁对象要唯一
//3. 判断,共享数据是否结束. 结束
//4. 判断,共享数据是否结束. 没有结束
while(true){
synchronized (Desk.lock){
if(Desk.count == 0){//吃不下了
break;
}else{
if(Desk.flag){
//有
System.out.println("吃货在吃汉堡包");
Desk.flag = false;
Desk.lock.notifyAll();唤醒厨师
Desk.count--;
}else{
//没有就等待
try {
Desk.lock.wait();//使用什么对象当做锁,那么就必须用这个对象去调用等待和唤醒的方法.
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
}
/*测试类*/
public class Demo {
public static void main(String[] args) {
Foodie f = new Foodie();
Cooker c = new Cooker();
f.start();
c.start();
}
}
/*
厨师正在生产汉堡包
吃货在吃汉堡包
厨师正在生产汉堡包
吃货在吃汉堡包
厨师正在生产汉堡包
吃货在吃汉堡包
*/