31.多线程
是指从软件或者硬件上实现多个线程并发执行的技术。


并发:在同一时刻,有多个指令在单个CPU上交替执行。

1.3进程和线程【理解】
-
进程:是正在运行的程序
独立性:进程是一个能独立运行的基本单位,同时也是系统分配资源和调度的独立单位
-
动态性:进程的实质是程序的一次执行过程,进程是动态产生,动态消亡的
-
并发性:任何进程都可以同其他进程一起并发执行
-
线程:是进程中的单个顺序控制流,是一条执行路径
单线程:一个进程如果只有一条执行路径,则称为单线程程序
多线程:一个进程如果有多条执行路径,则称为多线程程序

-
方法介绍
方法名 说明 void run() 在线程开启后,此方法将被调用执行 void start() 使此线程开始执行,Java虚拟机会调用run方法() -
实现步骤
-
定义一个类MyThread继承Thread类
-
在MyThread类中重写run()方法
-
创建MyThread类的对象
-
-
代码演示
package com.itheima.thread.demo;
public class MyThread extends Thread{
@Override
public void run() {
//代码就是线程在开启之后执行的代码
for (int i = 0; i < 100; i++) {
System.out.println("线程开启了"+i);
}
}
}
Demo
package com.itheima.thread.demo;
public class Demo {
public static void main(String[] args) {
//创建一个线程对象
MyThread t1=new MyThread();
//创建一个线程对象
MyThread t2=new MyThread();
//开启一条线程
t1.start();
//开启第二条线程
t2.start();
}
}
两个小问题
- 为什么要重写run()方法?
因为run()是用来封装被线程执行的代码
- run()方法和start()方法的区别?
run():表示的仅仅是创建对象,用对象区调用方法,并没有开启线程
start():启动线程;然后由JVM调用此线程的run()方法
-
Thread构造方法
方法名 说明 Thread(Runnable target) 分配一个新的Thread对象 Thread(Runnable target, String name) 分配一个新的Thread对象 -
实现步骤
-
定义一个类MyRunnable实现Runnable接口
-
在MyRunnable类中重写run()方法
-
创建MyRunnable类的对象
-
创建Thread类的对象,把MyRunnable对象作为构造方法的参数
-
启动线程
-
-
package com.itheima.thread.demo2;
public class MyRunnable implements Runnable{
@Override
public void run() {
//线程启动后执行的代码
for (int i = 0; i < 100; i++) {
System.out.println("第二种方式实现多线程"+i);
}
}
}
Demo
package com.itheima.thread.demo2;
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();
}
}
1.6实现多线程方式三: 实现Callable和Future接口【应用】
-
方法介绍
方法名 说明 V call() 计算结果,如果无法计算结果,则抛出一个异常 FutureTask(Callable<V> callable) 创建一个 FutureTask,一旦运行就执行给定的 Callable V get() 如有必要,等待计算完成,然后获取其结果 -
实现步骤
-
定义一个类MyCallable实现Callable接口
-
在MyCallable类中重写call()方法
-
创建MyCallable类的对象
-
创建Future的实现类FutureTask对象,把MyCallable对象作为构造方法的参数
-
创建Thread类的对象,把FutureTask对象作为构造方法的参数
-
启动线程
-
再调用get方法,就可以获取线程结束之后的结果。
-
-
代码演示
package com.itheima.thread.demo3;
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 "答应";
}
}
Demo
package com.itheima.thread.demo3;
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对象
FutureTask<String> ft=new FutureTask<>(mc);
//创建线程对象,开启线程
Thread t1=new Thread(ft);
t1.start();
String s = ft.get();//获得线程运行之后的结果
//如果线程还没有运行结束,那么get方法就会在这里死等
System.out.println(s);
}
}
-
实现Runnable、Callable接口
-
好处: 扩展性强,实现该接口的同时还可以继承其他的类
-
缺点: 编程相对复杂,不能直接使用Thread类中的方法
-
-
继承Thread类
-
好处: 编程比较简单,可以直接使用Thread类中的方法
-
-
1.7设置和获取线程名称【应用】
-
方法介绍
方法名 说明 void setName(String name) 将此线程的名称更改为等于参数name String getName() 返回此线程的名称 Thread currentThread() 返回对当前正在执行的线程对象的引用 -
代码演示
package com.itheima.thread.demo4;
public class MyThred extends Thread{
public MyThred() {
}
public MyThred(String name) {
super(name);
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName()+"@@@"+i);
}
}
}
Demo
package com.itheima.thread.demo4;
public class Demo {
//1.线程是有默认名字的,格式:Thread-编号
public static void main(String[] args) {
MyThred t1=new MyThred("小菜");
MyThred t2=new MyThred("小强");
//t1.setName("小菜");
//t2.setName("小强");
t1.start();
t2.start();
}
}
获取当前线程的对象
package com.itheima.thread.demo5;
public class Demo {
public static void main(String[] args) {
String name = Thread.currentThread().getName();
System.out.println(name);
}
}
1.8线程休眠【应用】
-
相关方法
方法名 说明 static void sleep(long millis) 使当前正在执行的线程停留(暂停执行)指定的毫秒数 -
代码演示
package com.itheima.thread.demo6;
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);
}
}
}
Demo
package com.itheima.thread.demo6;
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();
}
}
1.9线程优先级【应用】
-
线程调度
- 计算机中的CPU,在任意时刻只能执行一条机器指令,每个线程只有获得CPU的使用权才能执行代码,各个线程轮流获取CPU的使用权,分别执行各自的任务
-
-
两种调度方式
-
分时调度模型:所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片
-
-
-
Java使用的是抢占式调度模型
-
随机性
假如计算机只有一个 CPU,那么 CPU 在某一个时刻只能执行一条指令,线程只有得到CPU时间片,也就是使用权,才可以执行指令。所以说多线程程序的执行是有随机性,因为谁抢到CPU的使用权是不一定的
-

-
方法名 说明 final int getPriority() 返回此线程的优先级 final void setPriority(int newPriority) 更改此线程的优先级线程默认优先级是5;线程优先级的范围是:1-10 -
package com.itheima.thread.demo7;
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 "线程执行完毕了";
}
}
Demo
package com.itheima.thread.demo7;
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());
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());
t2.start();
}
}
1.10守护线程【应用】(备胎线程)
-
相关方法
方法名 说明 void setDaemon(boolean on) 将此线程标记为守护线程,当运行的线程都是守护线程时,Java虚拟机将退出 -
代码演示
package com.itheima.thread.demo8;
public class MyThread1 extends Thread{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(getName()+"==="+i);
}
}
}
MyThread2
package com.itheima.thread.demo8;
public class MyThread2 extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName()+"==="+i);
}
}
}
Demo
package com.itheima.thread.demo8;
public class Demo {
public static void main(String[] args) {
MyThread1 t1=new MyThread1();
MyThread2 t2=new MyThread2();
t1.setName("女神");
t2.setName("备胎");
//把第二个线程设置为守护线程
//当普通线程执行完毕后,那么守护线程也没有继续运行下去的必要了
t2.setDaemon(true);
t1.start();
t2.start();
}
}
线程生命周期 其他线程抢走CPU的执行权 遇到sleep或者其他阻塞式方法-------->没有执行资格,没有执行权--------->sleep方法时间到了或者阻塞结束回到就绪
创建线程对象----------->有执行资格,没有执行权<------------------------------->有执行资格有执行权------------------->线程死亡,变成垃圾
新建 (start()) 就绪 抢到CPU的执行权 运行 run()结束
2.线程同步
2.1卖票【应用】
-
案例需求
某电影院目前正在上映国产大片,共有100张票,而它有3个窗口卖票,请设计一个程序模拟该电影院卖票
-
实现步骤
-
定义一个类SellTicket实现Runnable接口,里面定义一个成员变量:private int tickets = 100;
-
在SellTicket类中重写run()方法实现卖票,代码步骤如下
-
判断票数大于0,就卖票,并告知是哪个窗口卖的
-
卖了票之后,总票数要减1
-
票卖没了,线程停止
-
定义一个测试类SellTicketDemo,里面有main方法,代码步骤如下
-
创建SellTicket类的对象
-
创建三个Thread类的对象,把SellTicket对象作为构造方法的参数,并给出对应的窗口名称
-
启动线程
-
-
代码实现
Ticket
package com.itheima.thread.demo9;
public class Ticket implements Runnable{
//票的数量
private int ticket=100;
@Override
public void run() {
while(true){
if (ticket == 0) {
//卖完了
break;
}else{
ticket--;
System.out.println(Thread.currentThread().getName()+"再卖票,还剩下"+ticket+"张票");
}
}
}
}
Demo
package com.itheima.thread.demo9;
public class Demo {
public static void main(String[] args) {
// Ticket ticket1=new Ticket();
// Ticket ticket2=new Ticket();
// Ticket ticket3=new Ticket();
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();
}
}
卖票案例的思考
刚才讲了电影院卖票程序,好像没有什么问题,但是在实际生活中,售票时出票也是需要时间的,所以,在出售一张票的时候,需要一点时间的延迟,接下来我们去修改卖票程序中卖票的动作:每次出票的时间100毫秒,用sleep()方法实现。
-
卖票出现了问题
-
相同的票出现了多次
-
出现了负数的票
-
-
问题产生原因
-
安全问题出现的条件
-
是多线程环境
-
有共享数据
-
有多条语句操作共享数据
-
-
如何解决多线程安全问题呢?
-
基本思想:让程序没有安全问题的环境
-
-
怎么实现呢?
-
把多条语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行即可
-
Java提供了同步代码块的方式来解决
-
-
同步代码块格式:
synchronized(任意对象) {
多条语句操作共享数据的代码
}synchronized(任意对象):就相当于给代码加锁了,任意对象就可以看成是一把锁
- 默认情况是打开的,只要有一个线程进去执行代码了,锁就会关闭
-
同步的好处和弊端
-
好处:解决了多线程的数据安全问题
-
弊端:当线程很多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率
-
-
package com.itheima.thread.demo9;
import java.io.ObjectOutputStream;
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+"张票");
}
}
}
}
}
Demo
package com.itheima.thread.demo9;
public class Demo {
public static void main(String[] args) {
// Ticket ticket1=new Ticket();
// Ticket ticket2=new Ticket();
// Ticket ticket3=new Ticket();
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();
}
}
MyThread方法
package com.itheima.thread.demo10;
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+"张票");
}
}
}
}
}
Demo
package com.itheima.thread.demo10;
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();
}
}
-
同步方法的格式
同步方法:就是把synchronized关键字加到方法上
修饰符 synchronized 返回值类型 方法名(方法参数) {
方法体;
} -
同步代码块和同步方法的区别:
- 同步代码块可以锁住指定代码,同步方法是锁住方法中所有代码
- 同步代码块可以指定锁对象,同步方法不能指定锁对象
-
同步方法的锁对象是什么呢?
this
-
静态同步方法
同步静态方法:就是把synchronized关键字加到静态方法上
修饰符 static synchronized 返回值类型 方法名(方法参数) {
方法体;
}同步静态方法的锁对象是什么呢?
类名.class 字节码文件的对象
-
MyRunnable
package com.itheima.thread.demo11;
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(100);
} 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(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
ticketCount--;
System.out.println(Thread.currentThread().getName()+"再卖票,还剩下"+ticketCount+"张票");
return false;
}
}
}
Demo
package com.itheima.thread.demo11;
public class Demo {
public static void main(String[] args) {
MyRunnable mr=new MyRunnable();
Thread t1=new Thread(mr);
t1.setName("窗口一");
Thread t2=new Thread();
t2.setName("窗口二");
t1.start();
t2.start();
}
}
### 2.5Lock锁【应用】
虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock
Lock实现提供比使用synchroniezd方法和语句可以获得更广泛的锁定操作
Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化
- ReentrantLock构造方法
| ReentrantLock() | 创建一个ReentrantLock的实例 |
- 加锁解锁方法
| void lock() | 获得锁 |
| void unlock() | 释放锁 |
- 代码演示
Ticket
package com.itheima.thread.demo12;
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) {
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();
}
}
}
}
Demo
package com.itheima.thread.demo12;
public class Demo {
public static void main(String[] args) {
// Ticket ticket1=new Ticket();
// Ticket ticket2=new Ticket();
// Ticket ticket3=new Ticket();
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();
}
}
2.6死锁【理解】
-
概述
线程死锁是指由于两个或者多个线程互相持有对方所需要的资源,导致这些线程处于等待状态,无法前往执行
-
什么情况下会产生死锁
-
资源有限
-
同步嵌套
-
-
代码演示
package com.itheima.thread.demo13;
public class Demo {
public static void main(String[] args) {
Object objA=new Object();
Object objB=new Object();
new Thread(()->{
while(true){
//线程一
synchronized (objA){
synchronized (objB){
System.out.println("小康同学在走路");
}
}
}
}).start();
new Thread(()->{
while(true){
synchronized (objB){
//线程二
synchronized (objA){
System.out.println("小微同学在走路");
}
}
}
}).start();
}
}
如何解决:以后不要写锁的嵌套
3.生产者消费者
3.1生产者和消费者模式概述【应用】(等待唤醒机制)
-
概述
生产者消费者模式是一个十分经典的多线程协作的模式,弄懂生产者消费者问题能够让我们对多线程编程的理解更加深刻。
所谓生产者消费者问题,实际上主要是包含了两类线程:
一类是生产者线程用于生产数据
一类是消费者线程用于消费数据
为了解耦生产者和消费者的关系,通常会采用共享的数据区域,就像是一个仓库
生产者生产数据之后直接放置在共享数据区中,并不需要关心消费者的行为
消费者只需要从共享数据区中去获取数据,并不需要关心生产者的行为
-
Object类的等待和唤醒方法
方法名 说明 void wait() 导致当前线程等待,直到另一个线程调用该对象的 notify()方法或 notifyAll()方法 void notify() 唤醒正在等待对象监视器的单个线程 void notifyAll() 唤醒正在等待对象监视器的所有线程
Desk:
package com.itheima.thread.demo14;
public class Desk {
//定义一个标记
//true 就表示桌子上有汉堡包的,此时允许吃货执行
//false 就表示桌子上没有汉堡包的,此时允许厨师执行
public static boolean flag = false;
//表示汉堡包的总数量
public static int count = 10;
//锁对象
public static final Object lock=new Object();
}
Cooker
package com.itheima.thread.demo14;
public class Cooker extends Thread{
@Override
public void run() {
/*生产者步骤:
1,判断桌子上是否有汉堡包
如果有就等待,如果没有才生产。
2,把汉堡包放在桌子上。
3,叫醒等待的消费者开吃。*/
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();
}
}
}
}
}
}
}
Foodie
package com.itheima.thread.demo14;
public class Foodie extends Thread{
@Override
public void run() {
/*消费者步骤:
1,判断桌子上是否有汉堡包。
2,如果没有就等待。
3,如果有就开吃
4,吃完之后,桌子上的汉堡包就没有了
叫醒等待的生产者继续生产
汉堡包的总数量减一*/
//套路:
//while(true)死循环
//synchronized锁,锁对象要唯一
//判断,共享数据是否结束
//判断,共享数据是否结束,没有结束
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();
}
}
}
}
}
}
}
Demo
package com.itheima.thread.demo14;
public class Demo {
public static void main(String[] args) {
/*消费者步骤:
1,判断桌子上是否有汉堡包。
2,如果没有就等待。
3,如果有就开吃
4,吃完之后,桌子上的汉堡包就没有了
叫醒等待的生产者继续生产
汉堡包的总数量减一*/
/*生产者步骤:
1,判断桌子上是否有汉堡包
如果有就等待,如果没有才生产。
2,把汉堡包放在桌子上。
3,叫醒等待的消费者开吃。*/
Foodie f=new Foodie();
Cooker c=new Cooker();
f.start();
c.start();
}
}
3.3生产者和消费者案例优化【应用】
-
需求
-
将Desk类中的变量,采用面向对象的方式封装起来
-
生产者和消费者类中构造方法接收Desk类对象,之后在run方法中进行使用
-
创建生产者和消费者线程对象,构造方法中传入Desk类对象
-
开启两个线程
-
-
代码实现
Desk:
package com.itheima.thread.demo15;
public class Desk {
//定义一个标记
//true 就表示桌子上有汉堡包的,此时允许吃货执行
//false 就表示桌子上没有汉堡包的,此时允许厨师执行
private boolean flag;
//表示汉堡包的总数量
//以后我们在使用这种必须有默认值的变量的时候
//private int count=10;
private int count;
//锁对象
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 +
'}';
}
}
Cooker
package com.itheima.thread.demo15;
public class Cooker extends Thread{
private Desk desk;
public Cooker(Desk desk) {
this.desk=desk;
}
@Override
public void run() {
/*生产者步骤:
1,判断桌子上是否有汉堡包
如果有就等待,如果没有才生产。
2,把汉堡包放在桌子上。
3,叫醒等待的消费者开吃。*/
while(true){
synchronized (desk.getLock()){
if (desk.getCount() == 0) {
break;
}else{
if(!desk.isFlag()){
//生产
System.out.println("厨师正在生产汉堡包");
desk.setFlag(true);
desk.getLock().notifyAll();
}else{
try {
desk.getLock().wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
}
Foodie
package com.itheima.thread.demo15;
public class Foodie extends Thread{
private Desk desk;
public Foodie(Desk desk) {
this.desk=desk;
}
@Override
public void run() {
/*消费者步骤:
1,判断桌子上是否有汉堡包。
2,如果没有就等待。
3,如果有就开吃
4,吃完之后,桌子上的汉堡包就没有了
叫醒等待的生产者继续生产
汉堡包的总数量减一*/
//套路:
//while(true)死循环
//synchronized锁,锁对象要唯一
//判断,共享数据是否结束
//判断,共享数据是否结束,没有结束
while (true){
synchronized (desk.getLock()){
if (desk.getCount() == 0) {
break;
}else{
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();
}
}
}
}
}
}
}
Demo
package com.itheima.thread.demo15;
public class Demo {
public static void main(String[] args) {
/*消费者步骤:
1,判断桌子上是否有汉堡包。
2,如果没有就等待。
3,如果有就开吃
4,吃完之后,桌子上的汉堡包就没有了
叫醒等待的生产者继续生产
汉堡包的总数量减一*/
/*生产者步骤:
1,判断桌子上是否有汉堡包
如果有就等待,如果没有才生产。
2,把汉堡包放在桌子上。
3,叫醒等待的消费者开吃。*/
Desk desk=new Desk();
Foodie f=new Foodie(desk);
Cooker c=new Cooker(desk);
f.start();
c.start();
}
}
3.4阻塞队列基本使用【理解】
-
阻塞队列继承结构
![]()
-
ArrayBlockingQueue: 底层是数组,有界
LinkedBlockingQueue: 底层是链表,无界.但不是真正的无界,最大为int的最大值
-
BlockingQueue的核心方法:
put(anObject): 将参数放入队列,如果放不进去会阻塞
take(): 取出第一个数据,取不到会阻塞
-
package com.itheima.thread.demo16; import java.util.concurrent.ArrayBlockingQueue; public class Demo { public static void main(String[] args) throws InterruptedException { //阻塞队列的基本用法 //创建阻塞队列的对象,容量为1 ArrayBlockingQueue arrayBlockingQueue=new ArrayBlockingQueue(1); //存储元素 arrayBlockingQueue.put("汉堡包"); //取元素 System.out.println(arrayBlockingQueue.take()); System.out.println(arrayBlockingQueue.take()); System.out.println("程序结束"); } } -
3.5阻塞队列实现等待唤醒机制【理解】
-
案例需求
-
生产者类(Cooker):实现Runnable接口,重写run()方法,设置线程任务
1.构造方法中接收一个阻塞队列对象
2.在run方法中循环向阻塞队列中添加包子
3.打印添加结果
-
消费者类(Foodie):实现Runnable接口,重写run()方法,设置线程任务
1.构造方法中接收一个阻塞队列对象
2.在run方法中循环获取阻塞队列中的包子
3.打印获取结果
-
测试类(Demo):里面有main方法,main方法中的代码步骤如下
创建阻塞队列对象
分别开启两个线程
-
-
代码实现
Cooker
package com.itheima.thread.demo17;
import java.util.concurrent.ArrayBlockingQueue;
public class Cooker extends Thread{
private ArrayBlockingQueue<String> list;
public Cooker(ArrayBlockingQueue list) {
this.list=list;
}
@Override
public void run() {
while (true) {
try {
String take = list.take();
System.out.println("吃货从队列中获取了"+take);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
Foodie
package com.itheima.thread.demo17;
import java.util.concurrent.ArrayBlockingQueue;
public class Foodie extends Thread{
private ArrayBlockingQueue<String> list;
public Foodie(ArrayBlockingQueue list) {
this.list=list;
}
@Override
public void run() {
while (true) {
try {
list.put("汉堡包");
System.out.println("厨师放了一个汉堡包");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
Demo
package com.itheima.thread.demo17;
import java.util.concurrent.ArrayBlockingQueue;
public class Demo {
public static void main(String[] args) {
//创建一个阻塞队列,容量为1
ArrayBlockingQueue list=new ArrayBlockingQueue(1);
//创建线程并开启
Cooker c=new Cooker(list);
Foodie f=new Foodie(list);
c.start();
f.start();
}
}
1.线程池
1.1 线程状态介绍
当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态。线程对象在不同的时期有不同的状态。那么Java中的线程存在哪几种状态呢?Java中的线程
状态被定义在了java.lang.Thread.State枚举类中,State枚举类的源码如下:
public class Thread {
public enum State {
/* 新建 */
NEW ,
/* 可运行状态 */
RUNNABLE ,
/* 阻塞状态 */
BLOCKED ,
/* 无限等待状态 */
WAITING ,
/* 计时等待 */
TIMED_WAITING ,
/* 终止 */
TERMINATED;
}
// 获取当前线程的状态
public State getState() {
return jdk.internal.misc.VM.toThreadState(threadStatus);
}
}
| 线程状态 | 具体含义 |
|---|---|
| NEW | 一个尚未启动的线程的状态。也称之为初始状态、开始状态。线程刚被创建,但是并未启动。还没调用start方法。MyThread t = new MyThread()只有线程象,没有线程特征。 |
| RUNNABLE | 当我们调用线程对象的start方法,那么此时线程对象进入了RUNNABLE状态。那么此时才是真正的在JVM进程中创建了一个线程,线程一经启动并不是立即得到执行,线程的运行与否要听令与CPU的调度,那么我们把这个中间状态称之为可执行状态(RUNNABLE)也就是说它具备执行的资格,但是并没有真正的执行起来而是在等待CPU的度。 |
| BLOCKED | 当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked状态;当该线程持有锁时,该线程将变成Runnable状态。 |
| WAITING | 一个正在等待的线程的状态。也称之为等待状态。造成线程等待的原因有两种,分别是调用Object.wait()、join()方法。处于等待状态的线程,正在等待其他线程去执行一个特定的操作。例如:因为wait()而等待的线程正在等待另一个线程去调用notify()或notifyAll();一个因为join()而等待的线程正在等待另一个线程结束。 |
| TIMED_WAITING | 一个在限定时间内等待的线程的状态。也称之为限时等待状态。造成线程限时等待状态的原因有三种,分别是:Thread.sleep(long),Object.wait(long)、join(long)。 |
| TERMINATED | 一个完全运行完成的线程的状态。也称之为终止状态、结束状态 |
各个状态的转换,如下图所示:

线程池:
1.创建一个池子 ,池子中是空的
2。有任务需要执行时,才会创建线程对象。当任务执行完毕时,线程对象归还给池子
1.2 线程的状态-练习1
目的 : 本案例主要演示TIME_WAITING的状态转换。
需求:编写一段代码,依次显示一个线程的这些状态:NEW -> RUNNABLE -> TIME_WAITING -> RUNNABLE -> TERMINATED
为了简化我们的开发,本次我们使用匿名内部类结合lambda表达式的方式使用多线程。
package com.itheima.threadstatedemo;
public class ThreadStateDemo1 {
public static void main(String[] args) throws InterruptedException {
Thread thread=new Thread(()->{
System.out.println("执行start方法之后,线程的状态"+Thread.currentThread().getState());
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("sleep方法结束之后,线程的状态"+Thread.currentThread().getState());
});
System.out.println("创建一个线程,但是没有调用start方法"+thread.getState());
thread.start();
Thread.sleep(50);
System.out.println("线程在执行sleep方法时候的状态"+thread.getState());
Thread.sleep(200);
System.out.println("线程执行完毕后的状态"+thread.getState());
}
}
//创建一个线程,但是没有调用start方法NEW
/执行start方法之后,线程的状态RUNNABLE
//线程在执行sleep方法时候的状态TIMED_WAITING
//sleep方法结束之后,线程的状态RUNNABLE
//线程执行完毕后的状态TERMINATED
1.3 线程的状态-练习2
目的 : 本案例主要演示WAITING的状态转换。
需求 :编写一段代码,依次显示一个线程的这些状态:NEW -> RUNNABLE -> WAITING -> RUNNABLE -> TERMINATED
代码实现 :
package com.itheima.threadstatedemo;
//NEW -> RUNNABLE -> WAITING -> RUNNABLE -> TERMINATED
public class ThreadStateDemo2 {
public static void main(String[] args) throws InterruptedException {
//锁对象
Object obj=new Object();
Thread thread1=new Thread(()->{
System.out.println("线程开启之后的状态"+Thread.currentThread().getState());
synchronized (obj){
try {
Thread.sleep(100);
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程被唤醒之后的状态"+Thread.currentThread().getState());
}
});
System.out.println("创建线程对象后,但是不调用start方法的状态"+thread1.getState());
thread1.start();
Thread.sleep(200);
System.out.println("线程thread1进入到wait方法时的状态"+thread1.getState());
new Thread(()->{
synchronized (obj){
obj.notifyAll();
}
}).start();
//为了让thread1线程先把代码执行完毕
Thread.sleep(200);
System.out.println("线程执行完毕后的状态"+thread1.getState());
}
}
//
创建线程对象后,但是不调用start方法的状态NEW
线程开启之后的状态RUNNABLE
线程thread1进入到wait方法时的状态WAITING
线程被唤醒之后的状态RUNNABLE
线程执行完毕后的状态TERMINATED
1.4 线程的状态-练习3
目的 : 本案例主要演示BLOCKED的状态转换。
需求 :编写一段代码,依次显示一个线程的这些状态:NEW -> RUNNABLE -> BLOCKED -> RUNNABLE -> TERMINATED
package com.itheima.threadstatedemo;
//NEW -> RUNNABLE -> BLOCKED -> RUNNABLE -> TERMINATED
public class ThreadStateDemo3 {
public static void main(String[] args) throws InterruptedException {
Object obj=new Object();
Thread t1=new Thread(()->{
//枪锁的线程
synchronized (obj){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t1.start();
Thread t2=new Thread(()->{
System.out.println("线程开启后的状态"+Thread.currentThread().getState());
synchronized (obj){
System.out.println("进入之后的线程状态"+Thread.currentThread().getState());
}
});
System.out.println("创建线程对象后,但是不调用start方法"+t2.getState());
t2.start();
Thread.sleep(100);
System.out.println("线程无法获得锁对象时的状态"+t2.getState());
Thread.sleep(2000);
System.out.println("线程完全执行完毕时的状态"+t2.getState());
}
}
1.5 线程池-基本原理
概述 :
提到池,大家应该能想到的就是水池。水池就是一个容器,在该容器中存储了很多的水。那么什么是线程池呢?线程池也是可以看做成一个池子,在该池子中存储很多个线程。
线程池存在的意义:
系统创建一个线程的成本是比较高的,因为它涉及到与操作系统交互,当程序中需要创建大量生存期很短暂的线程时,频繁的创建和销毁线程对系统的资源消耗有可能大于业务处理是对系
统资源的消耗,这样就有点"舍本逐末"了。针对这一种情况,为了提高性能,我们就可以采用线程池。线程池在启动的时,会创建大量空闲线程,当我们向线程池提交任务的时,线程池就
会启动一个线程来执行该任务。等待任务执行完毕以后,线程并不会死亡,而是再次返回到线程池中称为空闲状态。等待下一次任务的执行。
线程池的设计思路 :
-
准备一个任务容器
-
一次性启动多个(2个)消费者线程
-
刚开始任务容器是空的,所以线程都在wait
-
直到一个外部线程向这个任务容器中扔了一个"任务",就会有一个消费者线程被唤醒
-
这个消费者线程取出"任务",并且执行这个任务,执行完毕后,继续等待下一次任务的到来
1.6 线程池-Executors默认线程池
代码实现:
1.创建一个池子,池子中是空的 -----------创建executors中的静态方法
2.有任务需要执行时,创建线程对象。任务执行完毕,线程对象归还给池子。-----------submit方法
池子会自动的帮我们创建对象,任务执行完毕,也会自动的把线程对象归还池子
3.所有的任务全部执行完毕,关闭线程池-------------shutdown方法
概述 : JDK对线程池也进行了相关的实现,在真实企业开发中我们也很少去自定义线程池,而是使用JDK中自带的线程池。
我们可以使用Executors中所提供的静态方法来创建线程池
static ExecutorService newCachedThreadPool() 创建一个默认的线程池
static newFixedThreadPool(int nThreads) 创建一个指定最多线程数量的线程池
代码实现 :
package com.itheima.mythreadpool;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
// static ExecutorService newCachedThreadPool() 创建一个默认的线程池
// static newFixedThreadPool(int nThreads) 创建一个指定最多线程数量的线程池
public class MyThreadPoolDemo {
public static void main(String[] args) throws InterruptedException {
//1.创建一个默认的线程池对象,池子中默认是空的,默认最多可以容纳int类型的最大值
ExecutorService executorService = Executors.newCachedThreadPool();
//Executors---可以啊帮助我们创建线程池对象
//executorService ---可以帮助我们控制线程池
executorService.submit(()->{
System.out.println(Thread.currentThread().getName()+"在执行了");
});
Thread.sleep(2000);
executorService.submit(()->{
System.out.println(Thread.currentThread().getName()+"在执行了");
});
executorService.shutdown();
}
}
1.7 线程池-Executors创建指定上限的线程池
使用Executors中所提供的静态方法来创建线程池
代码实现 :
package com.itheima.mythreadpool;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
// static ExecutorService newFixedThreadPool(int nThreads) :
// 创建一个指定最多线程数量的线程池
public class MyThreadPoolDemo2 {
public static void main(String[] args) {
//参数不是初始值而是最大值
ExecutorService executorService = Executors.newFixedThreadPool(10);
ThreadPoolExecutor pool=(ThreadPoolExecutor)executorService;
System.out.println(pool.getPoolSize());
executorService.submit(()->{
System.out.println(Thread.currentThread().getName()+"在执行了");
});
executorService.submit(()->{
System.out.println(Thread.currentThread().getName()+"在执行了");
});
System.out.println(pool.getPoolSize());
executorService.shutdown();
}
}
1.8 线程池-ThreadPoolExecutor
创建线程池对象 :
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(核心线程数量,最大线程数量,空闲线程最大存活时间和单位,任务队列,创建线程工厂,任务的拒绝策略);
代码实现 :
package com.itheima.mythreadpool;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class MyThreadPoolDemo3 {
public static void main(String[] args) {
//核心线程数量,
// 最大线程数量,
// 空闲线程最大存活时间
// 时间单位,
// 任务队列,
// 创建线程工厂,
// 任务的拒绝策略
ThreadPoolExecutor pool=new ThreadPoolExecutor(2,5,2, TimeUnit.SECONDS,new ArrayBlockingQueue<>(10), Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
pool.submit(new MyRunnable());
pool.submit(new MyRunnable());
pool.shutdown();
}
}
参数一:核心线程数----------------不能小于0
参数二:最大线程数----------------不能小于等于0,最大数量>=核心线程数量
参数三:空闲线程最大存活时间------不能小于0
参数四:时间单位------------TimeUnit.SECONDS
参数五:任务队列------不能为null
参数六:创建线程工厂-----不能为null
参数七任务的拒绝策略------不能为null

1.10 线程池-非默认任务拒绝策略
RejectedExecutionHandler是jdk提供的一个任务拒绝策略接口,它下面存在4个子类。
ThreadPoolExecutor.AbortPolicy: 丢弃任务并抛出RejectedExecutionException异常。是默认的策略。
ThreadPoolExecutor.DiscardPolicy: 丢弃任务,但是不抛出异常 这是不推荐的做法。
ThreadPoolExecutor.DiscardOldestPolicy: 抛弃队列中等待最久的任务 然后把当前任务加入队列中。
ThreadPoolExecutor.CallerRunsPolicy: 调用任务的run()方法绕过线程池直接执行。
注:明确线程池对多可执行的任务数 = 队列容量 + 最大线程数
案例演示1:演示ThreadPoolExecutor.AbortPolicy任务处理策略
package com.itheima.mythreadpool;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class MyThreadPoolDemo4 {
public static void main(String[] args) {
//核心线程数量,
// 最大线程数量,
// 空闲线程最大存活时间
// 时间单位,----TimeUnit
// 任务队列,---让任务在队列中等着,等有线程空闲了,在从这个队列中获取任务并执行
// 创建线程工厂,---按照默认的方式创建线程对象
// 任务的拒绝策略---1.什么时候拒绝任务 当提交的任务>池子中的最大线程数两+队列容量
//2.如何拒绝
ThreadPoolExecutor pool=new ThreadPoolExecutor(
2,
5,
2,
TimeUnit.HOURS,
new ArrayBlockingQueue<>(10),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
for (int i=1;i<=15;i++) {
pool.submit(new MyRunnable());
}
pool.shutdown();
}
}
//
pool-1-thread-2在执行了
pool-1-thread-2在执行了
pool-1-thread-2在执行了
pool-1-thread-2在执行了
pool-1-thread-2在执行了
pool-1-thread-2在执行了
pool-1-thread-2在执行了
pool-1-thread-2在执行了
pool-1-thread-2在执行了
pool-1-thread-2在执行了
pool-1-thread-2在执行了
pool-1-thread-3在执行了
pool-1-thread-1在执行了
pool-1-thread-5在执行了
pool-1-thread-4在执行了
如果超过15 就报错
package com.itheima.mythreadpool;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class MyThreadPoolDemo5 {
public static void main(String[] args) {
//核心线程数量,
// 最大线程数量,
// 空闲线程最大存活时间
// 时间单位,----TimeUnit
// 任务队列,---让任务在队列中等着,等有线程空闲了,在从这个队列中获取任务并执行
// 创建线程工厂,---按照默认的方式创建线程对象
// 任务的拒绝策略---1.什么时候拒绝任务 当提交的任务>池子中的最大线程数两+队列容量
//2.如何拒绝
ThreadPoolExecutor pool=new ThreadPoolExecutor(
1,
2,
2,
TimeUnit.HOURS,
new ArrayBlockingQueue<>(1),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.DiscardPolicy());
for (int i=1;i<=5;i++) {
pool.submit(new MyRunnable());
}
pool.shutdown();
}
}
//
pool-1-thread-2===3
pool-1-thread-1===1
pool-1-thread-2===2
控制台没有报错,仅仅执行了3个任务,有2个任务被丢弃了
package com.itheima.mythreadpool;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class MyThreadPoolDemo6 {
public static void main(String[] args) {
//核心线程数量,
// 最大线程数量,
// 空闲线程最大存活时间
// 时间单位,----TimeUnit
// 任务队列,---让任务在队列中等着,等有线程空闲了,在从这个队列中获取任务并执行
// 创建线程工厂,---按照默认的方式创建线程对象
// 任务的拒绝策略---1.什么时候拒绝任务 当提交的任务>池子中的最大线程数两+队列容量
//2.如何拒绝
ThreadPoolExecutor pool=new ThreadPoolExecutor(
1,
2,
2,
TimeUnit.HOURS,
new ArrayBlockingQueue<>(1),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.DiscardOldestPolicy());
for (int i=1;i<=10;i++) {
pool.submit(new MyRunnable());
}
pool.shutdown();
}
}
//
pool-1-thread-2在执行了
pool-1-thread-1在执行了
pool-1-thread-1在执行了
由于任务1在线程池中等待时间最长,因此任务1被丢弃。
package com.itheima.mythreadpool;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class MyThreadPoolDemo7 {
public static void main(String[] args) {
//核心线程数量,
// 最大线程数量,
// 空闲线程最大存活时间
// 时间单位,----TimeUnit
// 任务队列,---让任务在队列中等着,等有线程空闲了,在从这个队列中获取任务并执行
// 创建线程工厂,---按照默认的方式创建线程对象
// 任务的拒绝策略---1.什么时候拒绝任务 当提交的任务>池子中的最大线程数两+队列容量
//2.如何拒绝
ThreadPoolExecutor pool=new ThreadPoolExecutor(
1,
2,
2,
TimeUnit.HOURS,
new ArrayBlockingQueue<>(1),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.CallerRunsPolicy());
for (int i=1;i<=10;i++) {
pool.submit(new MyRunnable());
}
pool.shutdown();
}
}
main在执行了
main在执行了
main在执行了
main在执行了
main在执行了
main在执行了
main在执行了
pool-1-thread-1在执行了
pool-1-thread-1在执行了
pool-1-thread-2在执行了
通过控制台的输出,我们可以看到次策略没有通过线程池中的线程执行任务,而是直接调用任务的run()方法绕过线程池直接执行。
2. 原子性
2.1 volatile-问题
代码分析 :
Money
package com.itheima.myvolatile;
public class Money {
public static int money=100000;
}
Mythread1
package com.itheima.myvolatile;
public class Mythread1 extends Thread {
@Override
public void run() {
while(Money.money==100000){
}
System.out.println("不是十万了");
}
}
MyThread2
package com.itheima.myvolatile;
public class MyThread2 extends Thread{
@Override
public void run() {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
Money.money=90000;
}
}
Demo
package com.itheima.myvolatile;
public class Demo {
public static void main(String[] args) {
Mythread1 t1=new Mythread1();
t1.setName("小路同学");
t1.start();
MyThread2 t2=new MyThread2();
t2.setName("小皮同学");
t2.start();
}
}
程序问题 : 女孩虽然知道结婚基金是十万,但是当基金的余额发生变化的时候,女孩无法知道最新的余额。
2.2 volatile解决
当A线程修改了共享数据时,B线程没有及时获取到最新的值,如果还在使用原先的值,就会出现问题
1,堆内存是唯一的,每一个线程都有自己的线程栈。
2 ,每一个线程在使用堆里面变量的时候,都会先拷贝一份到变量的副本中。
3 ,在线程中,每一次使用是从变量的副本中获取的。
问题:如果A线程修改了堆中共享变量的值。那么其他线程不一定及时使用最新的值
Volatile关键字 : 强制线程每次在使用的时候,都会看一下共享区域最新的值
代码实现 : 使用volatile关键字解决
只需要修改
Money
package com.itheima.myvolatile;
public class Money {
public static volatile int money = 100000;
}
2.3 synchronized解决
synchronized解决 :
1 ,线程获得锁
2 ,清空变量副本
3 ,拷贝共享变量最新的值到变量副本中
4 ,执行代码
5 ,将修改后变量副本中的值赋值给共享数据
6 ,释放锁
代码实现 :
Money
package com.itheima.myvolatile2;
public class Money {
public static Object lock=new Object();
public static volatile int money=100000;
}
Mythread1
package com.itheima.myvolatile2;
public class Mythread1 extends Thread {
@Override
public void run() {
while (true) {
synchronized (Money.lock){
if(Money.money!=100000){
System.out.println("不是十万了");
break;
}
}
}
}
}
Mythread2
package com.itheima.myvolatile2;
public class MyThread2 extends Thread{
@Override
public void run() {
synchronized (Money.lock) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
Money.money=90000;
}
}
}
Demo
package com.itheima.myvolatile2;
public class Demo {
public static void main(String[] args) {
Mythread1 t1=new Mythread1();
t1.setName("小路同学");
t1.start();
MyThread2 t2=new MyThread2();
t2.setName("小皮同学");
t2.start();
}
}


浙公网安备 33010602011771号