多线程,线程类三种方式,线程调度,线程同步,死锁,线程间的通信,阻塞队列,wait和sleep区别?
赶星而至 人气:0重难点梳理
知识点梳理
学习目标
1、能够知道什么是进程什么是线程(进程和线程的概述,多进程和多线程的意义)
2、能够掌握线程常见API的使用
3、能够理解什么是线程安全问题
4、能够知道什么是锁
5、能够知道什么是死锁
6、能够掌握线程3种创建方式(3种创建方式)
7、能够知道什么是等待唤醒机制
超详细讲义
==知识点==
-
多线程的概念
-
多线程的实现方式
-
线程类的常见方法
-
线程同步
-
死锁
-
生产者消费者
1.多线程的概念
1.1初步了解多线程【重点】
1.什么是多线程?
采用多线程技术可以同时执行多个任务(比如:迅雷同时下载多个任务),多线程需要硬件支持
1.2并发和并行【重点】
1.什么是并行?
在同一时刻,有多条线程在多个CPU上同时执行
2.什么是并发?
在一段时间内,有多条线程在单个CPU上交替执行
1.3进程和线程【重点】
(共4点)
1.什么是进程?
可以理解为正在运行的程序
2.什么是线程?
线程它是进程的一部分,是进程中的单个控制流,是一条执行路径(执行一项任务)
一个进程,可以有多条线程,至少有一条线程(线程才是CPU执行的最小单元)
线程只能在进程中运行
==3.为什么要学习多线程==
可以让程序同时执行多个任务,提高程序的执行效率(例如一次上传多张图片,同时打开多个网页)
2.多线程的实现方式
2.1实现多线程方式一:继承Thread类【重点】
1.实现多线程的方式有哪些?
-
继承Thread类的方式进行实现
-
实现Runnable接口的方式进行实现
-
利用Callable和Future接口方式实现
2.方法介绍
方法名 | 说明 |
---|---|
void run() | 在线程开启后,此方法将被调用执行 |
void start() | 使此线程开始执行,Java虚拟机会调用run方法() |
3.实现步骤
-
定义一个类MyThread继承Thread类
-
在MyThread类中重写run()方法
-
创建MyThread类的对象
-
启动线程
代码演示
package com.itheima.threaddemo1;
public class MyThread extends Thread{
@Override
public void run() {
//代码就是线程在开启之后执行的代码
for (int i = 0; i < 100; i++) {
System.out.println("线程开启了" + i);
}
}
}
package com.itheima.threaddemo1;
public class Demo {
public static void main(String[] args) {
//创建一个线程对象
MyThread t1 = new MyThread();
//创建一个线程对象
MyThread t2 = new MyThread();
//t1.run();//表示的仅仅是创建对象,用对象去调用方法,并没有开启线程.
//t2.run();
//开启一条线程
t1.start();
//开启第二条线程
t2.start();
}
}
2.2 多线程的实现方式-两个小问题【了解】
1.为什么要重写run()方法?
因为run()是用来封装被线程执行的代码
2.run()方法和start()方法的区别?
run():封装线程执行的代码,直接调用,相当于普通方法的调用
start():启动线程;然后由JVM调用此线程的run()方法
2.3实现多线程方式二:实现Runnable接口【重点】
(共3点)
1.Thread构造方法
方法名 | 说明 |
---|---|
Thread(Runnable target) | 分配一个新的Thread对象 |
2.实现步骤
-
定义一个类MyRunnable实现Runnable接口
-
在MyRunnable类中重写run()方法
-
创建MyRunnable类的对象
-
创建Thread类的对象,把MyRunnable对象作为构造方法的参数
-
启动线程
代码演示
package com.itheima.threaddemo2;
public class MyRunnable implements Runnable{
@Override
public void run() {
//线程启动后执行的代码
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + "第二种方式实现多线程" + i);
}
}
}
package com.itheima.threaddemo2;
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();
}
}
==3.线程的执行是顺序是随机的==
2.4 实现多线程方式三: 实现Callable接口【重点】
1.方法介绍
方法名 | 说明 |
---|---|
V call() | 计算结果,如果无法计算结果,则抛出一个异常 |
FutureTask(Callable<V> callable) | 创建一个 FutureTask,一旦运行就执行给定的 Callable |
V get() | 如有必要,等待计算完成,然后获取其结果 |
2.实现步骤
-
定义一个类MyCallable实现Callable接口
-
在MyCallable类中重写call()方法
-
创建MyCallable类的对象
-
创建Future的实现类FutureTask对象,把MyCallable对象作为构造方法的参数
-
创建Thread类的对象,把FutureTask对象作为构造方法的参数
-
启动线程
-
再调用get方法,就可以获取线程结束之后的结果。
3.注意事项
get()方法的调用一定要在Thread类对象调用start()方法之后
代码演示
package com.itheima.threaddemo3;
import java.util.concurrent.Callable;
public class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
for (int i = 0; i < 100; i++) {
System.out.println("跟女孩表白" + i);
}
//返回值就表示线程运行完毕之后的结果
return "答应";
}
}
package com.itheima.threaddemo3;
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对象
FutureTask<String> ft = new FutureTask<>(mc);
//创建线程对象
Thread t1 = new Thread(ft);
String s = ft.get();
//开启线程
t1.start();
//String s = ft.get();
System.out.println(s);
}
}
2.5 三种实现方式的对比【重点】
1.实现Runnable、Callable接口
-
好处: 扩展性强,实现该接口的同时还可以继承其他的类
-
缺点: 编程相对复杂,不能直接使用Thread类中的方法
2.继承Thread类
-
好处: 编程比较简单,可以直接使用Thread类中的方法
-
缺点: 可以扩展性较差,不能再继承其他的类
3.线程类中的常见方法
3.1设置和获取线程名称【重点】
方法介绍
方法名 | 说明 |
---|---|
void setName(String name) | 将此线程的名称更改为等于参数name |
String getName() | 返回此线程的名称 |
代码演示
package com.itheima.threaddemo4;
public class MyThread extends Thread {
public MyThread() {
}
public MyThread(String name) {
super(name);
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName() + "@@@" + i);
}
}
}
package com.itheima.threaddemo4;
public class Demo {
//1,线程是有默认名字的,格式:Thread-编号
public static void main(String[] args) {
MyThread t1 = new MyThread("小蔡");
MyThread t2 = new MyThread("小强");
//t1.setName("小蔡");
//t2.setName("小强");
t1.start();
t2.start();
}
}
3.2 Tread方法-获得当前线程对象【重点】
方法名 | 说明 |
---|---|
static Thread currentThread() | 返回对当前正在执行的线程对象的引用 |
package com.itheima.threaddemo5;
public class Demo {
public static void main(String[] args) {
String name = Thread.currentThread().getName();
System.out.println(name);
}
}
3.3 线程休眠【应用】【重点】
1.相关方法
方法名 | 说明 |
---|---|
static void sleep(long millis) | 使当前正在执行的线程停留(暂停执行)指定的毫秒数 |
代码演示
package com.itheima.threaddemo6;
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);
}
}
}
package com.itheima.threaddemo6;
public class Demo {
public static void main(String[] args) throws InterruptedException {
/*System.out.println("睡觉前");
Thread.sleep(3000);
System.out.println("睡醒了");*/
MyRunnable mr = new MyRunnable();
Thread t1 = new Thread(mr);
Thread t2 = new Thread(mr);
t1.start();
t2.start();
}
}
3.4 线程优先级【了解】
1.优先级相关方法
方法名 | 说明 |
---|---|
final int getPriority() | 返回此线程的优先级 |
final void setPriority(int newPriority) | 更改此线程的优先级线程默认优先级是5;线程优先级的范围是:1-10 |
代码演示
package com.itheima.threaddemo7;
import java.util.concurrent.Callable;
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 "线程执行完毕了";
}
}
package com.itheima.threaddemo7;
import java.util.concurrent.FutureTask;
public 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());//5
t1.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());//5
t2.start();
}
}
3.5 守护线程【了解】
1.什么是守护线程?
守护线程是程序运行的时候在后台提供一种通用服务的线程(保镖与主人)
2.守护线程的特点?
被守护的线程结束,不会立即结束,挣扎一会儿才结束
3.如何使用守护线程?
相关方法
方法名 | 说明 |
---|---|
void setDaemon(boolean on) | 将此线程标记为守护线程,当运行的线程都是守护线程时,Java虚拟机将退出 |
代码演示
package com.itheima.threaddemo8.example;
public class MyThread1 extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(this.getName()+"---"+i);
}
}
}
package com.itheima.threaddemo8.example;
public class MyThread1 extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(this.getName()+"---"+i);
}
}
}
package com.itheima.threaddemo8.example;
public class Test {
public static void main(String[] args) {
MyThread1 t1 = new MyThread1();//保镖
t1.setDaemon(true);
MyThread2 t2 = new MyThread2();//主人
t1.setName("保镖");
t2.setName("主人");
t2.start();
t1.start();
}
}
4.线程同步
4.1卖票【难点】
-
案例需求
某电影院目前正在上映国产大片,共有100张票,而它有3个窗口卖票,请设计一个程序模拟该电影院卖票
-
实现步骤
-
定义一个类SellTicket实现Runnable接口,里面定义一个成员变量:private int tickets = 100;
-
在SellTicket类中重写run()方法实现卖票,代码步骤如下
-
判断票数大于0,就卖票,并告知是哪个窗口卖的
-
卖了票之后,总票数要减1
-
票卖没了,线程停止
-
定义一个测试类SellTicketDemo,里面有main方法,代码步骤如下
-
创建SellTicket类的对象
-
创建三个Thread类的对象,把SellTicket对象作为构造方法的参数,并给出对应的窗口名称
-
启动线程
-
-
代码实现
package com.itheima.threaddemo9;
public class Ticket implements Runnable {
//票的数量
private int ticket = 100;
@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 + "张票");
}
}
}
}
package com.itheima.threaddemo9;
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();
}
}
4.2 线程安全问题-原因分析【难点】
1.卖票出现了问题
-
相同的票出现了多次
-
出现了负数的票
出现问题的根本原因?
线程执行的随机,在卖票的过程中CPU随机的执行了多条线程(也可以说是在卖票的过程中多条线程操作了共享数据ticket)
4.3同步代码块解决数据安全问题【重点】
1.如何解决上述问题呢?
-
任意时刻只有一条线程可以操作共享变量
-
Java中如何解决?
同步代码块格式:
synchronized(任意对象) {
多条语句操作共享数据的代码
}
synchronized(任意对象):就相当于给代码加锁了,任意对象就可以看成是一把锁
2.synchronized同步代码块的特点?
默认情况下是打开的,只要有一个线程进去执行代码了,锁就会关闭
当线程执行完出来时,锁才会自动打开
3.什么样的情况下,会有线程安全问题
3.1 多线程环境
3.2 有共享数据
3.3 有对共享数据的操作(增、删、改(除了long、double类的基本数据类型直接赋值)、查)
代码演示
package com.itheima.threaddemo9;
public class Ticket implements Runnable {
//票的数量
private int ticket = 100;
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 + "张票");
}
}
}
}
}
package com.itheima.threaddemo9;
public class Demo {
public static void main(String[] args) {
/*Ticket ticket1 = new Ticket();
Ticket ticket2 = new Ticket();
Ticket ticket3 = new Ticket();
Thread t1 = new Thread(ticket1);
Thread t2 = new Thread(ticket2);
Thread t3 = new Thread(ticket3);*/
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();
}
}
4.4 线程安全问题-锁对象唯一【重点】
1.锁对象为什么要唯一?
不同线程如果锁的不是同一个对象,就解决不了线程的安全问题
package com.itheima.threaddemo10;
public class MyThread extends Thread {
private static int ticketCount = 100;
private static Object obj = new Object();
@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 + "张票");
}
}
}
}
}
package com.itheima.threaddemo010;
public class Demo {
public static void main(String[] args) {
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
t1.setName("窗口一");
t2.setName("窗口二");
t1.start();
t2.start();
}
}
4.5同步方法解决数据安全问题【重点】
(共4点)
1.同步方法的格式
同步方法:就是把synchronized关键字加到方法上
修饰符 synchronized 返回值类型 方法名(方法参数) { 方法体; }
2.同步方法的锁对象是什么呢?
this
public class MyRunnableCommon implements Runnable { private static 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){ 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; } } }
3.静态同步方法的格式
同步静态方法:就是把synchronized关键字加到静态方法上
修饰符 static synchronized 返回值类型 方法名(方法参数) { 方法体; }
4.同步静态方法的锁对象是什么呢?
类名.class
代码演示
package com.itheima.threaddemo011; public class MyRunnable implements Runnable { private static 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 (MyRunnable.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() { 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; } } } package com.itheima.threaddemo011; public class Demo { public static void main(String[] args) { MyRunnable mr = new MyRunnable(); Thread t1 = new Thread(mr); Thread t2 = new Thread(mr); t1.setName("窗口一"); t2.setName("窗口二"); t1.start(); t2.start(); } }
4.6 Lock锁【重点】(视频19 5‘’)
1.如何手动开关锁呢?
使用lock
2.如何使用Lock?
Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化
ReentrantLock构造方法
方法名 | 说明 |
---|---|
ReentrantLock() | 创建一个ReentrantLock的实例 |
加锁解锁方法
方法名 | 说明 |
---|---|
void lock() | 获得锁 |
void unlock() | 释放锁 |
代码演示
package com.itheima.threaddemo012; import java.util.concurrent.locks.ReentrantLock; public class Ticket implements Runnable { //票的数量 private int ticket = 100; private Object obj = new Object(); private ReentrantLock lock = new ReentrantLock(); @Override public 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(); } // } } } } package com.itheima.threaddemo012; public class Demo { public static void main(String[] args) { /*Ticket ticket1 = new Ticket(); Ticket ticket2 = new Ticket(); Ticket ticket3 = new Ticket(); Thread t1 = new Thread(ticket1); Thread t2 = new Thread(ticket2); Thread t3 = new Thread(ticket3);*/ 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(); } }
4.7 死锁【了解】
1.什么是死锁?(吃饭)
线程死锁是指由于两个或者多个线程互相持有对方所需要的资源,导致这些线程处于等待状态,无法前往执行
代码演示
5.生产者消费者
5.1 生产者和消费者思路分析【难点】
5.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方法中的代码步骤如下
创建生产者线程和消费者线程对象
分别开启两个线程
代码实现
package com.itheima.threaddemo014; public class Desk { //定义一个标记 //true 就表示桌子上有汉堡包的,此时允许吃货执行 //false 就表示桌子上没有汉堡包的,此时允许厨师执行 public static boolean flag = false; //汉堡包的总数量 public static int count = 10; //锁对象 public static final Object lock = new Object(); } package com.itheima.threaddemo014; public class Cooker extends Thread { // 生产者步骤: // 1,判断桌子上是否有汉堡包 // 如果有就等待,如果没有才生产。 // 2,把汉堡包放在桌子上。 // 3,叫醒等待的消费者开吃。 @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(); } } } } } } } package com.itheima.threaddemo014; public class Foodie extends Thread { @Override public void run() { // 1,判断桌子上是否有汉堡包。 // 2,如果没有就等待。 // 3,如果有就开吃 // 4,吃完之后,桌子上的汉堡包就没有了 // 叫醒等待的生产者继续生产 // 汉堡包的总数量减一 //套路: //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(); } } } } } } }
5.3生产者和消费者案例优化【难点】
-
需求
-
将Desk类中的变量,采用面向对象的方式封装起来
-
生产者和消费者类中构造方法接收Desk类对象,之后在run方法中进行使用
-
创建生产者和消费者线程对象,构造方法中传入Desk类对象
-
开启两个线程
-
-
代码实现
package com.itheima.threaddemo015; public class Desk { //定义一个标记 //true 就表示桌子上有汉堡包的,此时允许吃货执行 //false 就表示桌子上没有汉堡包的,此时允许厨师执行 //public static boolean flag = false; private boolean flag; //汉堡包的总数量 //public static int count = 10; //以后我们在使用这种必须有默认值的变量 // private int count = 10; private int count; //锁对象 //public static final Object lock = new Object(); private final Object lock = new Object(); public Desk() { this(false,10); } public Desk(boolean flag, int count) { this.flag = flag; this.count = count; } public boolean isFlag() { return flag; } public void setFlag(boolean flag) { this.flag = flag; } public int getCount() { return count; } public void setCount(int count) { this.count = count; } public Object getLock() { return lock; } @Override public String toString() { return "Desk{" + "flag=" + flag + ", count=" + count + ", lock=" + lock + '}'; } } package com.itheima.threaddemo015; public class Cooker extends Thread { private Desk desk; public Cooker(Desk desk) { this.desk = desk; } // 生产者步骤: // 1,判断桌子上是否有汉堡包 // 如果有就等待,如果没有才生产。 // 2,把汉堡包放在桌子上。 // 3,叫醒等待的消费者开吃。 //套路: //1. while(true)死循环 //2. synchronized 锁,锁对象要唯一 //3. 判断,共享数据是否结束. 结束 //4. 判断,共享数据是否结束. 没有结束 @Override public void run() { while(true){ synchronized (desk.getLock()){ if(desk.getCount() == 0){ break; }else{ //System.out.println("验证一下是否执行了"); if(!desk.isFlag()){ //生产 System.out.println("厨师正在生产汉堡包"); desk.setFlag(true); desk.getLock().notifyAll(); }else{ try { desk.getLock().wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } } } } } package com.itheima.threaddemo015; public class Desk { //定义一个标记 //true 就表示桌子上有汉堡包的,此时允许吃货执行 //false 就表示桌子上没有汉堡包的,此时允许厨师执行 //public static boolean flag = false; private boolean flag; //汉堡包的总数量 //public static int count = 10; //以后我们在使用这种必须有默认值的变量 // private int count = 10; private int count; //锁对象 //public static final Object lock = new Object(); private final Object lock = new Object(); public Desk() { this(false,10); } public Desk(boolean flag, int count) { this.flag = flag; this.count = count; } public boolean isFlag() { return flag; } public void setFlag(boolean flag) { this.flag = flag; } public int getCount() { return count; } public void setCount(int count) { this.count = count; } public Object getLock() { return lock; } @Override public String toString() { return "Desk{" + "flag=" + flag + ", count=" + count + ", lock=" + lock + '}'; } } package com.itheima.threaddemo015; public class Foodie extends Thread { private Desk desk; public Foodie(Desk desk) { this.desk = desk; } @Override public void run() { // 1,判断桌子上是否有汉堡包。 // 2,如果没有就等待。 // 3,如果有就开吃 // 4,吃完之后,桌子上的汉堡包就没有了 // 叫醒等待的生产者继续生产 // 汉堡包的总数量减一 //套路: //1. while(true)死循环 //2. synchronized 锁,锁对象要唯一 //3. 判断,共享数据是否结束. 结束 //4. 判断,共享数据是否结束. 没有结束 while(true){ synchronized (desk.getLock()){ if(desk.getCount() == 0){ break; }else{ //System.out.println("验证一下是否执行了"); if(desk.isFlag()){ //有 System.out.println("吃货在吃汉堡包"); desk.setFlag(false); desk.getLock().notifyAll(); desk.setCount(desk.getCount() - 1); }else{ //没有就等待 //使用什么对象当做锁,那么就必须用这个对象去调用等待和唤醒的方法. try { desk.getLock().wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } } } } }
5.4阻塞队列基本使用【重点】
(共4点)
1.什么是阻塞队列?
阻塞队列是一个在队列基础上又支持了两个附加操作的队列
2.附加的方法
put(anObject): 将参数放入队列,如果放不进去会阻塞
take(): 取出第一个数据,取不到会阻塞
3.阻塞队列继承结构
4.常见BlockingQueue实现类
ArrayBlockingQueue: 底层是数组,有界
LinkedBlockingQueue: 底层是链表,无界.但不是真正的无界,最大为int的最大值
代码示例
package com.itheima.threaddemo016; import java.util.concurrent.ArrayBlockingQueue; public class Demo { public static void main(String[] args) throws InterruptedException { // 创建阻塞队列的对象,容量为 1 ArrayBlockingQueue<String> arrayBlockingQueue = new ArrayBlockingQueue<>(1); // 存储元素 arrayBlockingQueue.put("汉堡包"); // 取元素 System.out.println(arrayBlockingQueue.take()); System.out.println(arrayBlockingQueue.take()); // 取不到会阻塞 System.out.println("程序结束了"); } }
5.5阻塞队列实现等待唤醒机制【难点】
案例需求
-
生产者类(Cooker):实现Runnable接口,重写run()方法,设置线程任务
1.构造方法中接收一个阻塞队列对象
2.在run方法中循环向阻塞队列中添加包子
3.打印添加结果
-
消费者类(Foodie):实现Runnable接口,重写run()方法,设置线程任务
1.构造方法中接收一个阻塞队列对象
2.在run方法中循环获取阻塞队列中的包子
3.打印获取结果
-
测试类(Demo):里面有main方法,main方法中的代码步骤如下
创建阻塞队列对象
创建生产者线程和消费者线程对象,构造方法中传入阻塞队列对象
分别开启两个线程
代码实现
package com.itheima.threaddemo016; import java.util.concurrent.ArrayBlockingQueue; public class Demo { public static void main(String[] args) throws InterruptedException { ArrayBlockingQueue<String> bd = new ArrayBlockingQueue<>(1); Foodie f = new Foodie(bd); Cooker c = new Cooker(bd); f.start(); c.start(); } }
package com.itheima.threaddemo016; import com.itheima.threaddemo015.Desk; import java.util.concurrent.ArrayBlockingQueue; public class Cooker extends Thread { private ArrayBlockingQueue<String> bd; public Cooker(ArrayBlockingQueue<String> bd) { this.bd = bd; } @Override public void run() { while (true) { try { bd.put("汉堡包"); System.out.println("厨师放入一个汉堡包"); } catch (InterruptedException e) { e.printStackTrace(); } } } }
package com.itheima.threaddemo016; import com.itheima.threaddemo015.Desk; import java.util.concurrent.ArrayBlockingQueue; public class Foodie extends Thread { private ArrayBlockingQueue<String> bd; public Foodie(ArrayBlockingQueue<String> bd) { this.bd = bd; } @Override public void run() { while (true) { try { String take = bd.take(); System.out.println("吃货将" + take + "拿出来吃了"); } catch (InterruptedException e) { e.printStackTrace(); } } } }
总结:
在java中如何实现等待和唤醒机制?
1.wait和notify()或notifyAll()
wait:让线程等待,同时立即释放锁 --->sleep():让线程休眠,但是不会释放锁
notify()或notifyAll(): 唤醒等待的线程,但是不会立即释放锁
2.阻塞队列
put:存,如果队例满的话,会阻塞(等待)
take:取,如果取不到,会阻塞(等待)
扩展练习
题目1
编写程序,创建两个线程对象,一根线程循环输出“播放背景音乐”,另一根线程循环输出“显示画面”; 要求:
1: 1个线程使用Runnable接口的匿名内部类实现
2: 另一个线程使用lambda实现
效果:
参考答案:
package day13.No_1;
public class Demo {
public static void main(String[] args) {
//匿名内部类实现
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
System.out.println("播放背景音乐!");
}
}
}).start();
//lambda实现
new Thread(() -> {
while (true) {
System.out.println("显示画面!");
}
}).start();
}
}
题目2
3.请使用继承Thread类的方式定义一个线程类,在run()方法中循环10次,每1秒循环1次,每次循环按“yyyy-MM-dd HH:mm:ss”的格式打印当前系统时间。 请定义测试类,并定义main()方法,启动此线程,观察控制台打印。
要求:
1: 使用匿名内部类配合SimpleDateFormat和Date实现
2: 使用lambda配合LocalDateTime和DateTimeFormatter实现
效果:
参考答案:
package day13.No_2;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Date;
public class Demo {
public static void main(String[] args) {
/*
//方式一内部类实现
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Date date = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String format = sdf.format(date);
System.out.println(format);
}
}
}).start();
*/
//方式二:lambda实现
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
LocalDateTime now = LocalDateTime.now();
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String format = dateTimeFormatter.format(now);
System.out.println(format);
}
}).start();
}
}
运行效果:
题目3
请编写多线程应用程序,模拟多个人通过一个山洞: (1).这个山洞每次只能通过一个人,每个人通过山洞的时间为1秒; (2).创建10个线程,同时准备过此山洞,并且定义一个变量用于记录通过隧道的人数。显示每次通过山洞人的姓名,和通过顺序;
要求:
保证安全问题,不能出现多个人同时通过山洞的现象;(必须逐一通过)
效果:
参考答案:
线程文件:
package day13.No_3;
public class MyRunnable implements Runnable {
private static int count = 10;
private final static Object obj = new Object();
private static int i = 1;
@Override
public void run() {
synchronized (obj) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println(Thread.currentThread().getName() + "通过山洞,他是第" + i + "个通过");
i++;
}
}
}
测试文件:
package day13.No_3;
public class Demo {
public static void main(String[] args) {
//此处可以使用for循环创建线程
MyRunnable mr = new MyRunnable();
Thread tr1 = new Thread(mr, "线程1");
tr1.start();
Thread tr2 = new Thread(mr, "线程2");
tr2.start();
Thread tr3 = new Thread(mr, "线程3");
tr3.start();
Thread tr4 = new Thread(mr, "线程4");
tr4.start();
Thread tr5 = new Thread(mr, "线程5");
tr5.start();
Thread tr6 = new Thread(mr, "线程6");
tr6.start();
Thread tr7 = new Thread(mr, "线程7");
tr7.start();
Thread tr8 = new Thread(mr, "线程8");
tr8.start();
Thread tr9 = new Thread(mr, "线程9");
tr9.start();
Thread tr10 = new Thread(mr, "线程10");
tr10.start();
}
}
运行效果:
题目4
拼手速抽奖案例.
1.现有一个集合装了10个奖品在里面,分别是:{"电视机","电冰箱","电脑","游戏机","洗衣机","空调","手机","平板电脑","电动车","电饭煲"};
2.假如有3个人同时去抽这10个奖品.最后打印出来.三个人各自都抽到了什么奖品.
例如:
张三: “电视机”,”电冰箱”,”电脑”,”游戏机”,”洗衣机”
李四: ”空调”,”手机”,”平板电脑”,
王五: ”电动车”,”电饭煲
要求:
1:3个人同时开始抽奖,每次抽奖需要使用0.5秒才能完成抽奖;
2:需要控制住同一个奖项不能同时被多个人抽走;
效果:
参考答案:
线程类
package day13.No_4;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
public class MyRunnable implements Runnable {
private static ArrayList<String>list=new ArrayList<>(List.of("电视机","电冰箱","电脑","游戏机","洗衣机","空调","手机","平板电脑","电动车","电饭煲"));
private static Object object=new Object();
@Override
public void run() {
while (list.size()>0){
synchronized (object){
if (list.size()==0){
return;
}
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
String name = list.remove(new Random().nextInt(list.size()));
System.out.println(Thread.currentThread().getName()+"抽到了"+name);
}
}
}
}
测试类:
package day13.No_4;
public class Demo {
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.start();
t2.start();
t3.start();
}
}
运行效果:
加载全部内容