目录
并发和并行
并行∶在同一时刻,有多个指令在多个CPU上同时执行。
并发 :在同一时刻,有多个指今在单个CPU上交替执行
进程和线程
进程︰就是操作系统中正在运行的一个应用程序。
线程︰就是应用程序中做的事情。比如:360软件中的杀毒,扫描木马,清理垃圾。
是进程中的单个顺序控制流,是一条执行路径
单线程:一个进程如果只有一条执行路径,则称为单线程程序
多线程:一个进程如果有多条执行路径,则称为多线程程序
主线程:执行主(main)方法的线程 单线程程序:java程序中只有【一个】线程 执行从main方法开始,从上到下依次执行 JVM执行main方法,main方法会进入到栈内存 JVM会找操作系统开辟一条main方法通向cpu的执行路径 cpu就可以通过这个路径来执行main方法 而这个路径有一个名字,叫main(主)线程
一、 实现多线程方式
1.继承Thread类的方式进行实现
2.实现Runnable接口的方式进行实现
3.利用Callable和Future接口方式实现
获取线程的名称: 1.使用Thread类中的方法getName() String getName() 返回正在执行的【当前线程】线程的名称。 2.可以先获取到【当前正在执行】的线程,使用线程中的方法getName()获取线程的名称 static Thread currentThread() 返回当前正在执行的线程对象。
1.1继承Thread类
实现步骤
-
定义一个类MyThread继承Thread类
-
在MyThread类中重写run()方法【方法的代码就是线程在开启之后执行的代码】
-
创建MyThread类的对象
-
启动线程 【start()方法】
调用Thread类中的方法start方法,开启新的线程,执行run方法 void start() 使该线程开始执行;Java 虚拟机调用该线程的 run 方法。
方法介绍
方法名 说明 void run() 在线程开启后,此方法将被调用执行 void start() 使此线程开始执行,Java虚拟机会调用run方法() -
两个小问题
-
为什么要重写run()方法?
因为run()是用来封装被线程执行的代码
-
run()方法和start()方法的区别?
run():相当于普通方法的调用,并没有开始线程
start():启动线程;然后由JVM调用此线程的run()方法
-
//1.创建一个Thread类的子类
public class MyThread extends Thread{
//2.在Thread类的子类中重写Thread类中的run方法,设置线程任务(开启线程要做什么?)
@Override
public void run() {
for (int i = 0; i <20 ; i++) {
System.out.println("run:"+i);
}
}
}
【主方法】
public class Demo01Thread {
public static void main(String[] args) {
//3.创建Thread类的子类对象
MyThread mt = new MyThread();【创建一个线程对象】
MyThread mt2 = new MyThread();【创建第二个线程对象】
//4.调用Thread类中的方法start方法,开启新的线程,执行run方法
mt.start();【开启一条线程】
mt2.start();【开启一条线程】
for (int i = 0; i <20 ; i++) {
System.out.println("main:"+i);
}
}
}
1.2实现Runnable接口
Thread构造方法 :
实现步骤
定义一个类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(Thread.currentThread().getName() + "第二种方式实现多线程" + i);
}
}
}
【主函数】
public class Demo {
public static void main(String[] args) {
//创建了一个参数的对象
MyRunnable mr = new MyRunnable();
//Thread:创建了一个线程对象,并把参数传递给这个线程.
//在线程启动之后,执行的就是参数里面的run方法
Thread t1 = new Thread(mr);
//开启线程
t1.start();
MyRunnable mr2 = new MyRunnable();【创建第二个线程对象】
Thread t2 = new Thread(mr2);
t2.start();【开启第二条线程】
}
}
1.3实现Callable接口
方法介绍
方法名 说明 V call() 计算结果,如果无法计算结果,则抛出一个异常 FutureTask(Callable<V> callable) 创建一个 FutureTask,一旦运行就执行给定的 Callable V get() 如有必要,等待计算完成,然后获取其结果 实现步骤
定义一个类MyCallable实现Callable接口
在MyCallable类中重写call()方法【线程开启后执行call方法的内容】
创建MyCallable类的对象
创建Future的实现类FutureTask类的对象,把MyCallable对象作为构造方法的参数
创建Thread类的对象,把FutureTask对象作为构造方法的参数传递给Thread类
启动线程
再调用get方法,就可以获取线程结束之后的结果。
public class MyCallable implements Callable<Object>【此接口有一个泛型】【目前不知怎末写,先写一个Object】 【此泛型表示线程执行完之后的数据类型】
{
@Override【实现接口就要重写里面所有的抽象方法】
public 【Object】 call() throws Exception {【注意:此call方法有一个返回值】
【之前的两种创建线程方式中run方法没有返回值,是void】
//返回值就表示线程任务运行完毕之后,return的结果
//开头的泛型就表示返回值的数据类型
//当你线程执行完毕,想把什么返回,就写什么数据类型
return null;
}
}
当我想返回字符串类型,就把泛型写成String
public class MyCallable implements Callable【<String>】 {
@Override
public 【String】 call() throws Exception {
for (int i = 0; i < 100; i++) {
System.out.println("跟女孩表白" + i);
}
//返回值就表示线程运行完毕之后的结果
return "答应";
}
}
public class Demo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//线程开启之后需要执行里面的call方法
MyCallable mc = new MyCallable();
//Thread t1 = new Thread(mc);×
【Thread是一个线程对象,构造参数传递的应该是runable的是实现类】
【而MyCallable实现的是Callable接口,而Callable接口没有继承Runable接口】
【所以不能直接传递】
【利用一个中间件: FutureTask】
【而 FutureTask<V>也有一个泛型,此泛型应与 MyCallable中线程任务call方法的返回值类型一致】
//FutureTask里有个get方法
//可以获取线程【MyCallable实现类里Call方法】执行完毕之后的结果.也可以作为参数传递给Thread对象
FutureTask<String> ft = new FutureTask<>(mc);
//创建线程对象
Thread t1 = new Thread(ft);// FutureTask继承了Runable,可以作为Thread的构造参数
//String s = ft.get();如果此方法写在start方法之前,获取不到,程序停不下来【解释如下图】
//开启线程
t1.start();
String s = ft.get();//获取结果
System.out.println(s);
MyCallable mc2 = new MyCallable();【创建第二条线程】
FutureTask<String> fu2=new FutureTask<>(mc2);
Thread th2 = new Thread(fu2);
th2.setName("BBBBB");
th2.start();
th.start();
}
}
虚拟机刚开始启动的时候,会先启动main线程,会调用main方法,程序从上往下进行 ,前三行执行完毕后,线程并未开启,开始线程是start方法,如果在县城开启之前调用了get方法,而get作用是:获得线程运行之后的结果,如果线程没有运行结束,那么get方法就会死等。
如果get方法在start之后,那么get方法就停在那等待start方法执行完毕之后,将线程的执行结果返回给get方法,下面的代码继续执行。如果grt在start之前,就会死等,线程也没法开启
三种实现方式的对比
+ 实现Runnable、Callable接口
好处: 扩展性强,实现该接口的同时还可以继承其他的类【如果因业务需求下,继承其他类,可以用extends继承关系】
缺点: 编程相对复杂,不能直接使用Thread类中的方法
+ 继承Thread类
好处: 编程比较简单,可以直接使用Thread类中的方法【直接继承了Thread】
缺点: 可以扩展性较差,不能再继承其他的类【java是单继承的,不能一次继承多个类】
二、线程类的常见方法【Thread类下】
2.1设置和获取线程名称
-
方法介绍
方法名 说明 void setName(String name) 将此线程的名称更改为等于参数name String getName() 返回此线程的名称 Static Thread currentThread() 返回对当前正在执行的线程对象
获取名字:
getName:线程是有默认名字的,格式:Thread—编号 eg:Thread—1
1.使用Thread类中的方法getName()
String getName() 返回【该】【当前线程】线程的名称。
2.可以先获取到【当前正在执行】的线程,使用线程中的方法getName()获取线程的名称
static Thread currentThread() 返回对当前正在执行的线程对象的引用。
// 定义一个Thread类的子类
public class MyThread extends Thread{
//重写Thread类中的run方法,设置线程任务
@Override
public void run() {
//获取线程名称
String name = getName();//获取当前线程的名字 继承下来getname方法,直接调用
System.out.println(name);
Thread t = Thread.currentThread();//【得到当前正在运行的线程对象】
System.out.println(t);//Thread[Thread-0,5,main]
System.out.println(this);//t等价于this,获取的是当前的线程对象 重写了tostring
//String name = t.getName();
//System.out.println(name);
//链式编程
System.out.println(Thread.currentThread().getName());
}
}
设置名字:
Thread类中设置线程的名字
void setName(String name):将此线程的名称更改为等于参数name
通过构造方法也可以设置线程名称
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);//把线程名称传递给父类,让父类(Thread)给子线程起一个名字
}
@Override
public void run() {
//获取线程的名称
System.out.println(Thread.currentThread().getName());
}
}
public class Demo01SetThreadName {
public static void main(String[] args) {
//开启多线程
MyThread mt = new MyThread();
mt.setName("小强");
mt.start();
//开启多线程
new MyThread("旺财").start();
}
}
注意:
getname,Setname是Thread的特有方法,而创建线程的第二三种方式就无法调用此方法。
如果想在二三种方式中获取线程名字等,就可以利用StaticThread currentThread()
2.2线程休眠【Thread类的方法】
相关方法
方法名 | 说明 |
---|---|
static void sleep(long millis) | 使当前正在执行的线程停留(暂停执行)指定的毫秒数 |
【如果一个类或者一个接口的方法没有抛出异常,那么他们的子类或实现类中的方法就不能抛异常,只能自己try catch】
runnable接口中的run抽象方法没有抛出动作。
三、线程调度,调用优先级
cpu只能同时执行一条线程,所以多线程操作时就要考虑cpu的使用。
-
两种调度方式
-
分时调度模型:所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片
-
抢占式调度模型:优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的 CPU 时间片相对多一些
-
-
Java使用的是抢占式调度模型
优先级相关方法
方法名 | 说明 |
---|---|
final int getPriority() | 返回(获得)此线程的优先级是几 |
final void setPriority(int newPriority) | 设置线程的优先级,线程默认优先级是5;线程优先级的范围是:1-10 |
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 "线程执行完毕了";
}
}
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();
}
}
四、守护线程
相关方法
方法名 | 说明 |
---|---|
void setDaemon(boolean on) | 将此线程标记为守护线程,当运行的线程都是守护线程时,Java虚拟机将退出 |
public class MyThread1 extends Thread {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(getName() + "---" + i);
}
}
}
public class MyThread2 extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName() + "---" + i);
}
}
}
public class Demo {
public static void main(String[] args) {
MyThread1 t1 = new MyThread1();
MyThread2 t2 = new MyThread2();
t1.setName("女神");
t2.setName("备胎");
//把第二个线程设置为守护线程
//当普通线程执行完之后,那么守护线程也没有继续运行下去的必要了.
【当普通线程执行完后,守护线程不会立马停止,因为还占有cpu执行权,会执行一会】
t2.setDaemon(true);【将t2线程设置为守护线程】
t1.start();
t2.start();
}
}
五、 线程的安全问题【线程同步】
案例:卖票
案例需求
某电影院目前正在上映国产大片,共有100张票,而它有3个窗口卖票,请设计一个程序模拟该电影院卖票
实现步骤
①:定义一个类SellTicket实现Runnable接口,里面定义一个成员变量:private int tickets = 100;②:在SellTicket类中重写run()方法实现卖票,代码步骤如下
A:判断票数大于0,就卖票,并告知是哪个窗口卖的
B:卖了票之后,总票数要减1
C:票卖没了,线程停止
③:定义一个测试类SellTicketDemo,里面有main方法,代码步骤如下
A;创建SellTicket类的对象
B:创建三个Thread类的对象,把SellTicket对象作为构造方法的参数,并给出对应的窗口名称
C:启动线程
public class Ticket implements Runnable {
private int tickets = 100;
//在SellTicket类中重写run()方法实现卖票,代码步骤如下
@Override
public void run() {
while (true) {
if(ticket == 0){
//卖完了
break;
}else{
ticket--;
System.out.println(Thread. currentThread().getName() + "在卖票,还剩下" + ticket + "张票");
}
}
}
}
public class TicketDemo {
public static void main(String[] args) {
【注意:
Ticket作为参数,不能创建多次,只能创建一次。
Ticket st1 = newTicket();
Ticket st2 = newTicket();
Ticket st3 = newTicket();
Thread t1 = new Thread(st1);
Thread t2 = new Thread(st2);
Thread t3 = new Thread(st3);
【错误写法】
原因: Ticket st = newTicket();是多线程要执行的参数
如果每一条线程都执行不同的参数,那么三个new Ticket()对象各自有100张票
三个线程都有各自的100张票,而需求是三个线程都卖同一个100张票
所以只需创建一个线程任务对象,三条线程公用一个任务
】
//创建Ticket类的对象
Ticket st = newTicket();
//创建三个Thread类的对象,把Ticket对象作为构造方法的参数,并给出对应的窗口名称
Thread t1 = new Thread(st,"窗口1");
Thread t2 = new Thread(st,"窗口2");
Thread t3 = new Thread(st,"窗口3");
//启动线程
t1.start();
t2.start();
t3.start();
}
}
出现问题,1窗口卖第78张票时才显示2窗口卖第99张票.
原因:2窗口抢到cpu执行权,自减完为99后刚准备开始执行打印操作就被1窗口抢走cpu执行权 ,执行自减并打印。先把98打印出来,执行权还在1窗口,继续自减打印下去
public class Ticket implements Runnable {
private int tickets = 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 + "张票");
}
}
}
}
发现问题:出现相同票,负数票
解决:判断代码改为:if(ticket <= 0)
卖票案例的问题
-
卖票出现了问题
-
相同的票出现了多次
-
出现了负数的票
-
-
问题产生原因
线程执行的随机性导致的,可能在卖票过程中丢失cpu的执行权,导致出现问题
相同票:有三条线程,当绿线程执行到sleep睡眠,蓝线程进入也执行到sleep睡眠,以此类推红线程进入sleep,绿线程抢到cpu,Ticket变量执行自减变为99,在执行打印之前失去cpu,蓝色抢到,Ticket变量进行自减变为98,此时绿线程下Ticket的值也变为98。
-
原因:三个线程执行的都是同一个任务,共用一个Ticket变量,当蓝线程为98时,绿色也应为98
- 负号票:
线程123执行到sleep时Ticket为1,当绿线程抢到cpu,主席那个自减变为0,并执行了打印操作后,还未执行下次循环时,红线程抢到cpu,使Ticket值从0自减为-1并执行打印,同理蓝线程
同步代码块解决数据安全问题
-
安全问题出现的条件
-
是多线程环境
-
有共享数据
-
有多条语句操作共享数据
-
-
如何解决多线程安全问题呢?
-
基本思想:让程序没有安全问题的环境
-
-
怎么实现呢?
-
把多条语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行即可
-
Java提供了同步代码块的方式来解决
-
-
同步代码块格式:
synchronized(任意对象) { 多条语句操作共享数据的代码 }
synchronized(任意对象):就相当于给代码加锁了,任意对象就可以看成是一把锁
-
同步的好处和弊端
-
好处:解决了多线程的数据安全问题
-
弊端:当线程很多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率
-
public class SellTicket implements Runnable {
private int tickets = 100;
private Object obj = new Object();
@Override
public void run() {
while (true) {
synchronized (obj) { // 对可能有安全问题的代码加锁,【多个线程必须使用同一把锁】
//t1进来后,就会把这段代码给锁起来
if (tickets > 0) {
try {
Thread.sleep(100);
//t1休息100毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
//窗口1正在出售第100张票
System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
tickets--; //tickets = 99;
}
}
//t1出来了,这段代码的锁就被释放了
}
}
}
public class SellTicketDemo {
public static void main(String[] args) {
SellTicket st = new SellTicket();
Thread t1 = new Thread(st, "窗口1");
Thread t2 = new Thread(st, "窗口2");
Thread t3 = new Thread(st, "窗口3");
t1.start();
t2.start();
t3.start();
}
}
同步方法解决数据安全问题
-
同步方法的格式
同步方法:就是把synchronized关键字加到方法上
修饰符 synchronized 返回值类型 方法名(方法参数) { 方法体; }
同步方法的锁对象是什么呢?
锁对象只能是 this
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){
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;
}
}
public class Demo {
public static void main(String[] args) {
MyRunnable mr = new MyRunnable();【创建的MyRunnable对象相当于一个参数】
此对象在测试类中只创建一次,那么this都是一样的
【创建的两条线程,共用一个参数,执行同一个任务】
与创建多线程的第一种方式不一样,第一种创建线程中MyThread要创建两次,所以每一次的this不一样
但Runnable类只创建一次对象,创建的两个线程都去跑同一个参数,所以this一样的
Thread t1 = new Thread(mr);
Thread t2 = new Thread(mr);
t1.setName("窗口一");
t2.setName("窗口二");
t1.start();
t2.start();
}
【区别于创建两次的】
MyRunnable mr = new MyRunnable();
MyRunnable mr2 = new MyRunnable();
【创建了两个MyRunnable对象,相当于两个参数传递给了Thread构造方法】
Thread t1 = new Thread(mr); 【这种属于创建了两条线程,分别执行各自的线程任务内容,不共用数据】
Thread t2 = new Thread(mr2);
t1.setName("窗口一");
t2.setName("窗口二");
t1.start();
t2.start();
静态同步方法
同步静态方法:就是把synchronized关键字加到静态方法上
修饰符 static synchronized 返回值类型 方法名(方法参数) { 方法体; }
同步静态方法的锁 对象是什么呢?
类名.class
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;
}
}
}
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();
}
Lock锁
虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock
Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化
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();
}
// }
}
}
}
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();
}
}
死锁
-
概述
线程死锁是指由于两个或者多个线程互相持有对方所需要的资源,导致这些线程处于等待状态,无法前往执行
-
什么情况下会产生死锁
-
资源有限
-
同步嵌套
-
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();
}
}
【都被锁住无法执行】
等待唤醒机制
Object类的等待和唤醒方法
方法名 | 说明 |
---|---|
void wait() | 导致当前线程等待,直到另一个线程调用该对象的 notify()方法或 notifyAll()方法 |
void notify() | 唤醒正在等待对象监视器的单个线程 |
void notifyAll() | 唤醒正在等待对象监视器的所有线程 |
wait:假如某条线程抢到了执行权,遇到wait就会进入等待模式,让出cpu执行权。
public class Desk {
//定义一个标记
//true 就表示桌子上有汉堡包的,此时允许吃货执行
//false 就表示桌子上没有汉堡包的,此时允许厨师执行
public static boolean flag = false;
//汉堡包的总数量
public static int count = 10;
//锁对象
public static final Object lock = new Object();
}
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();
}
}
}
}
}
}
}
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();
}
}
}
}
}
}
}
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();
}
}
优化 案例代码
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 +
'}';
}
}
public class Cooker extends Thread {
private Desk desk;
public Cooker(Desk desk) {
this.desk = desk;
}
// 生产者步骤:
// 1,判断桌子上是否有汉堡包
// 如果有就等待,如果没有才生产。
// 2,把汉堡包放在桌子上。
// 3,叫醒等待的消费者开吃。
@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();
}
}
}
}
}
}
}
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();
}
}
}
}
}
}
}
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();
}
}
利用阻塞队列实现等待唤醒机制
【使代码更加简洁,减少自己书写难度】
在两者之间创建一个队列【容器】,将元素都放入其中,在里面进行存取操作
-
阻塞队列继承结构
常见BlockingQueue:
ArrayBlockingQueue: 底层是数组,有界【表示阻塞队列里的内容是有限的】
LinkedBlockingQueue: 底层是链表,无界.但不是真正的无界,最大为int范围的最大值65535
BlockingQueue的核心方法:
put(anObject): 将参数放入队列,如果放不进去会阻塞
take(): 取出第一个数据,取不到会阻塞
public class Demo02 {
public static void main(String[] args) throws Exception {
// 创建阻塞队列的对象,容量为 1【“这个通道”只有一个坑位,只能存一个内容】
ArrayBlockingQueue<String> arrayBlockingQueue = new ArrayBlockingQueue<>(1);
// 存储元素
arrayBlockingQueue.put("汉堡包");
// 取元素
System.out.println(arrayBlockingQueue.take());
System.out.println(arrayBlockingQueue.take()); // 取不到东西,会阻塞(等着)【通道内只有一个内容,已经被取走了,程序进入阻塞状态】
System.out.println("程序结束了");
}
}
执行结果:
汉堡包
【程序结束了 并未被打印出来,程序进入等待状态,程序没有停下来】
阻塞队列实现等待唤醒机制
public class Cooker extends Thread {
private ArrayBlockingQueue<String> bd;
public Cooker(ArrayBlockingQueue<String> bd) {
this.bd = bd;
}
// 生产者步骤:
// 1,判断桌子上是否有汉堡包
// 如果有就等待,如果没有才生产。
// 2,把汉堡包放在桌子上。
// 3,叫醒等待的消费者开吃。
@Override
public void run() {
while (true) {
try {
bd.put("汉堡包");
System.out.println("厨师放入一个汉堡包");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class Foodie extends Thread {
private ArrayBlockingQueue<String> bd;
public Foodie(ArrayBlockingQueue<String> bd) {
this.bd = bd;
}
@Override
public void run() {
// 1,判断桌子上是否有汉堡包。
// 2,如果没有就等待。
// 3,如果有就开吃
// 4,吃完之后,桌子上的汉堡包就没有了
// 叫醒等待的生产者继续生产
// 汉堡包的总数量减一
//套路:
//1. while(true)死循环
//2. synchronized 锁,锁对象要唯一
//3. 判断,共享数据是否结束. 结束
//4. 判断,共享数据是否结束. 没有结束
while (true) {
try {
String take = bd.take();
System.out.println("吃货将" + take + "拿出来吃了");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class Demo {
public static void main(String[] args) {
ArrayBlockingQueue<String> bd = new ArrayBlockingQueue<>(1);
Foodie f = new Foodie(bd);
Cooker c = new Cooker(bd);
f.start();
c.start();
}
}