简介

image-20200509081827566

看起来是多个任务都在做,其实本质上我们的大脑在同一时间依旧只做了一件时间。

image-20200509082243211

充分利用道路,加入多个车道

image-20200509082437764

image-20200509082550946

在操作系统中运行的程序就是进程。一个进程可以有多个线程,如视频中同时听声音,看图像,看弹幕等等。

image-20200509082931143

程序是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念。进程则是执行程序的一次执行过程,是一个动态的概念。是系统资源分配的单位。通常一个进程中可以包含若干个线程,一个进程中至少有一个线程(main线程)。线程是CPU调度和执行的单位。

很多多线程是模拟出来的,真正的多线程是指有多个cpu,即多核,如服务器。如果模拟出来的多线程,即在一个cpu的情况下,在同一时间点,cpu只能执行一个代码,因为切换的很快,所以就有同时执行的错觉。

image-20200509083455572

每个线程在自己的工作区间内交互,内存控制不当会造成数据不一致

线程创建

image-20200509083752714

继承Thread类

image-20200509084055959

线程不一定立即执行,CPU安排调度

image-20200509084420452

执行顺序是主线程调用线程之后不一定立即执行,由CPU安排调度。安排调度后是两线程个随机交叉执行

image-20200509085056117

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//创建线程方式一:继承Thread类,重写run()方法,调用start开启线程
//总结:线程开启后不一定立即执行,由CPU调度执行
public class TestThread01 extends Thread{

@Override
public void run() {
//run方法线程体
for (int i = 0; i < 200; i++) {
System.out.println("我在看代码------" + i);
}
}

public static void main(String[] args) {
//main线程,主线程

//创建一个线程对象
TestThread01 testThread01 = new TestThread01();

//调用start()方法开启线程
testThread01.start();

for (int i = 0; i < 1000; i++) {
System.out.println("我在学习多线程------" + i);
}
}
}

实例下载网图

1.创建lib项目,导入jar包

image-20200509090417198

image-20200509090459952

image-20200509090519751

2.编写代码

image-20200509090729313

image-20200509090756409

image-20200509090913950

image-20200509090929058

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
//练习Thread,实现多线程同步下载图片
public class TestThread02 extends Thread {

private String url;//网络图片地址
private String name;//保存的文件名

public TestThread02(String url, String name) {
this.url = url;
this.name = name;
}

@Override
public void run() {

WebDownloader1 webDownloader1 = new WebDownloader1();
webDownloader1.downloader(url, name);
System.out.println("下载了文件名为" + name);
}

public static void main(String[] args) {
TestThread02 t1 = new TestThread02("https://qiniu.codekylin.cn/github/img/img/%E5%AE%87%E8%88%AA%E5%91%98.png", "1.png");
TestThread02 t2 = new TestThread02("https://qiniu.codekylin.cn/github/img/img/%E5%AE%87%E8%88%AA%E5%91%98.png", "2.png");
TestThread02 t3 = new TestThread02("https://qiniu.codekylin.cn/github/img/img/%E5%AE%87%E8%88%AA%E5%91%98.png", "3.png");

t1.start();
t2.start();
t3.start();
}
}


//下载器
class WebDownloader1 {
//下载方法
public void downloader(String url, String name) {

try {
FileUtils.copyURLToFile(new URL(url), new File(name));
} catch (IOException e) {
e.printStackTrace();
System.out.println("IO异常,downloader方法出现问题");
}
}
}

实现Runnable接口

image-20200509091047216

image-20200509091742714

image-20200509091827730

执行顺序是主线程调用线程之后不一定立即执行,由CPU安排调度。安排调度后是两个随机交叉执行

image-20200509092021731

实例抢票——并发问题

image-20200509092341855

线程延迟增大了问题的发生概率

image-20200509092634841

实现Callable接口

image-20200509092939763

image-20200509163449671

image-20200509163647534

image-20200509163808219

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
//线程创建方式三:实现callable接口

/**
* 1。可以定义返回值
* 2.可以抛出异常
*/
public class TestThreadCallable implements Callable<Boolean> {

private String url;//网络图片地址
private String name;//保存的文件名

public TestThreadCallable(String url, String name) {
this.url = url;
this.name = name;
}

@Override
public Boolean call() {
WebDownloader1 webDownloader1 = new WebDownloader1();
webDownloader1.downloader(url, name);
System.out.println("下载了文件名为" + name);
return true;
}

public static void main(String[] args) throws ExecutionException, InterruptedException {
TestThreadCallable t1 = new TestThreadCallable("https://qiniu.codekylin.cn/github/img/img/%E5%AE%87%E8%88%AA%E5%91%98.png", "1.png");
TestThreadCallable t2 = new TestThreadCallable("https://qiniu.codekylin.cn/github/img/img/%E5%AE%87%E8%88%AA%E5%91%98.png", "2.png");
TestThreadCallable t3 = new TestThreadCallable("https://qiniu.codekylin.cn/github/img/img/%E5%AE%87%E8%88%AA%E5%91%98.png", "3.png");

//创建执行服务
ExecutorService es = Executors.newFixedThreadPool(3);

//提交执行
Future<Boolean> r1 = es.submit(t1);
Future<Boolean> r2 = es.submit(t2);
Future<Boolean> r3 = es.submit(t3);

//获取结果
Boolean rs1 = r1.get();
Boolean rs2 = r2.get();
Boolean rs3 = r3.get();

//关闭服务
es.shutdown();
}
}

//下载器
class WebDownloader1 {
//下载方法
public void downloader(String url, String name) {

try {
FileUtils.copyURLToFile(new URL(url), new File(name));
} catch (IOException e) {
e.printStackTrace();
System.out.println("IO异常,downloader方法出现问题");
}
}
}

线程方法

image-20200509164552398

方法 说明
setPriority(int newPriority) 更改线程的优先级
static void sleep(long millis) 在指定的毫秒数内让当前正在执行的线程休眠
void join() 等待该线程终止
static void yield() 暂停当前正在执行的线程对象,并执行其他线程
void interrupt 中断线程(不使用这个方式)
boolean isAlive() 测试线程是否处于活动状态

线程停止

image-20200509165206708

image-20200509165531969

image-20200509165706095

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
//测试线程停止
//1.建议线程正常停止--->利用次数,不建议死循环
//2.建议使用标志位--->设置一个标志位
//3.不要使用stop或者destroy等过时或者JDK不建议使用的方法
public class TestStop implements Runnable {
//1.设置一个标识位
private boolean flag = true;

@Override
public void run() {
int i = 0;
while (flag) {
System.out.println("run.....Thread" + i++);
}
}
//2.设置一个公开的方法停止线程,转换标志位
public void stop() {
this.flag = false;
}
public static void main(String[] args) {

TestStop testStop = new TestStop();
new Thread(testStop).start();

for (int i = 0; i < 1000; i++) {
System.out.println("main"+i);
if (i == 900) {
testStop.stop();
System.out.println("线程已经停止了");
}
}
}
}

线程休眠

image-20200509165854599

sleep不会释放锁

image-20200509171321995

GIF

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
//模拟倒计时
public class TestSleep2 {

public static void main(String[] args) throws InterruptedException {
//systemDate();
tenDown();
}
//打印当前系统的时间
public static void systemDate(){

Date startTime = new Date(System.currentTimeMillis());//获取系统当前时间
while (true){
try {
System.out.println(new SimpleDateFormat("HH:mm:ss").format(startTime));
Thread.sleep(1000);
startTime = new Date(System.currentTimeMillis());//更新系统当前时间
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//倒计时
public static void tenDown() throws InterruptedException {
int num = 10;
while (true){
Thread.sleep(1000);
System.out.println(num--);
if (num<=0){
break;
}
}
}
}

线程礼让

image-20200510092451081

image-20200510092648337

image-20200510092727581

image-20200510092808666

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//测试礼让线程,礼让不一定成功。看cpu调度心情
public class TestYield {

public static void main(String[] args) {
MyYield yield = new MyYield();

new Thread(yield,"后裔").start();
new Thread(yield,"嫦娥").start();
}


}

class MyYield implements Runnable{


@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"线程开始执行");
Thread.yield();//线程礼让
System.out.println(Thread.currentThread().getName()+"线程停止执行");
}
}

线程强制执行

image-20200510092930922

image-20200510093224122

image-20200510093319921

线程状态

image-20200509164248991

线程是一个动态执行的过程,它也有一个从产生到死亡的过程。

下图显示了一个线程完整的生命周期。

image-20200509164502918

img

  • 新建状态:

    使用 new 关键字和 Thread 类或其子类建立一个线程对象后,该线程对象就处于新建状态。它保持这个状态直到程序 start() 这个线程。

  • 就绪状态:

    当线程对象调用了start()方法之后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,要等待JVM里线程调度器的调度。

  • 运行状态:

    如果就绪状态的线程获取 CPU 资源,就可以执行 run(),此时线程便处于运行状态。处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。

  • 阻塞状态:

    如果一个线程执行了sleep(睡眠)、suspend(挂起)等方法,失去所占用资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间已到或获得设备资源后可以重新进入就绪状态。可以分为三种:

    • 等待阻塞:运行状态中的线程执行 wait() 方法,使线程进入到等待阻塞状态。
    • 同步阻塞:线程在获取 synchronized 同步锁失败(因为同步锁被其他线程占用)。
    • 其他阻塞:通过调用线程的 sleep() 或 join() 发出了 I/O 请求时,线程就会进入到阻塞状态。当sleep() 状态超时,join() 等待线程终止或超时,或者 I/O 处理完毕,线程重新转入就绪状态。
  • 死亡状态:

    一个运行状态的线程完成任务或者其他终止条件发生时,该线程就切换到终止状态。

线程状态观测

image-20200510093415850

image-20200510093949382

image-20200510094041609

image-20200510094129381

线程优先级

image-20200510095356360

优先级低只是意味着获得调度的概率低,并不会一定在优先级高之后被CPU调度。

image-20200510095920647

image-20200510100047875

image-20200510100208403

守护线程

image-20200510100301121

用户线程定义:平时用到的普通线程均是用户线程,当在Java程序中创建一个线程,它就被称为用户线程

image-20200510103420059

image-20200510103558913

虚拟机必须确保用户线程执行完毕

虚拟机不用等待守护线程执行完毕

线程同步

多个线程操作同一个资源线程不安全

image-20200511092805724

image-20200511092922715

线程同步其实就是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面线程使用完毕,下一个线程再使用。

image-20200511093356467

不安全的买票案例

image-20200511102200726

image-20200511102213836

image-20200511102246042

同票:每个线程都有自己的工作空间,都与主存交互。将主存中的数据拷贝到工作区之后,还没或者正要进行将对数据的修改返回给主存时,此时的cpu调度了另一个线程对其进行同样的操作。导致拿到了同一张票。线程切换带来的原子性问题

image-20200512164355963

负票:在票的临界处也就是最后一张票的时候,A线程被CPU调度执行进行买票,A线程延迟100毫秒。在这100毫秒的时间间隔里,线程B被CPU调度执行进行买票。此时的票数还是为1

没有return出去。然后B线程延迟,A线程延迟过后票数-1此时为0票。B线程延迟过后买票,票数-1此时0-1等于-1票。于是出现了负票。

image-20200512165330931

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
//不安全的买票。会拿到同一张票,或者拿到负数
public class UnsafeBuyTicket {

public static void main(String[] args) {
BuyTicket ticket = new BuyTicket();

new Thread(ticket, "苦逼的我").start();
new Thread(ticket, "努力的你").start();
new Thread(ticket, "可恶的黄牛").start();
}
}

class BuyTicket implements Runnable {

//票
private int tickeNums = 10;
boolean flag = true;//外部停止方式

@Override
public void run() {
//买票
while (flag) {
try {
buy();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

//加上synchronized
private void buy() throws InterruptedException {
//判断是否有票
if (tickeNums <= 0) {
flag = false;
return;
}
//模拟延时
Thread.sleep(100);
//买到票
System.out.println(Thread.currentThread().getName() + "拿到" + tickeNums-- + "票");
}
}

不安全的银行取钱

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
public class UnsafeBank {

public static void main(String[] args) {
Account account = new Account(100, "积蓄");
Drawing you = new Drawing(account, 50, "你");

Drawing girlFriend = new Drawing(account, 100, "女朋友");

you.start();
girlFriend.start();

}
}

//账户
class Account{
int money;//余额
String name;//卡名

public Account(int money, String name) {
this.money = money;
this.name = name;
}
}

//银行:模拟取款
class Drawing extends Thread{
Account account;//账户
//取了多少钱
int drawingMoney;
//现在手里有多少钱
int nowMoney;

public Drawing(Account account,int drawingMoney,String name){
super(name);
this.account = account;
this.drawingMoney= drawingMoney;
}

//取钱
@Override
public void run() {
if (account.money-drawingMoney<0){
System.out.println(Thread.currentThread().getName()+"钱不够,取不了");
return;
}

try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//卡内余额 = 余额 - 你取的钱
account.money = account.money-drawingMoney;
//你手里的钱
nowMoney = nowMoney + drawingMoney;


System.out.println(account.name+"余额为:"+account.money);
// Thread.currentThread().getName() = this.name 继承了thread
System.out.println(this.getName()+"手里的钱:"+nowMoney);

}
}

案例:女朋友取款100,你取款50。银行卡余额100,却出现你两都取钱成功,而余额为负数的结果。

image-20200512170602888

出现负数的解释同买票出现负数原因一样。在对余额进行操作时A线程延迟,此时B线程被CPU调度执行取钱,延迟。A线程取钱成功余额减去对应的数目(假设取钱50:100-50=50),B线程进行取钱成功余额减去对应的数目(假设取钱100:50-100=-50)。

线程不安全的集合

image-20200512171249658

image-20200512171443741

集合个数本来应该是10000的,出现了少了1个的原因是。当某个线程正在执行向集合添加元素的时候。同一时间CPU调度执行了另一个线程向集合添加元素,添加成功。此时刚才那个线程也向集合中添加元素,在list的同一位置,旧的元素值被新的覆盖。买票同票问题

image-20200512172652197

同步方法

image-20200512172940612

image-20200512173907884

方法里面也有分只读和修改的代码块,这时候使用同步方法会浪费资源

改造不安全买票,使用synchronized

image-20200512202515772

image-20200512202738176

同步方法的同步监视器是默认是this

同步块

image-20200512174858969

解决不安全的银行取钱案例。首先我们还是使用同步方法

image-20200512203542044

image-20200512203625846

说明我们并没有锁正确。原因是因为同步方法默认锁的是this对象,在这里也就是Drawing对象。而我们操控的是账户对象的值,所以导致并没有起作用。

image-20200512203927363

使用同步块,对account对象进行锁定

image-20200512204030505

还可以对当前账户的余额进行判断,从而优化我们的代码

image-20200512210031433

image-20200512210059641

解决线程不安全的集合。

image-20200512210909012

image-20200512211344447

同步是高开销的操作,因此尽量减少同步的内容。通常没有必要同步整个方法,同步部分代码块即可。
同步方法默认用this或者当前类class对象作为锁。
同步代码块可以选择以什么来加锁,比同步方法要更颗粒化,我们可以选择只同步会发生问题的部分代码而不是整个方法。

死锁

image-20200512213554529

多个线程互相抱着对方需要的资源同时又请求对方的资源,然后形成僵持

image-20200512214435803

程序一直在运行,僵持

image-20200512214458981

image-20200512215456746

如何解决死锁?很简单我们请求对方的资源的同时,放下手里的资源

image-20200512215830203

image-20200512215855316

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
//死锁:多个线程互相抱着对方需要的资源同时又请求对方的资源,然后形成僵持
public class TestDeadLock {
public static void main(String[] args) {
MakeUp g1 = new MakeUp(0, "灰姑凉");
MakeUp g2 = new MakeUp(1, "白雪公主");

g1.start();
g2.start();
}
}


//口红
class Lipstick {

}

//镜子
class Mirror {

}

class MakeUp extends Thread {

//需要的资源只有一份,用static来保证只有一份
static Lipstick lipstick = new Lipstick();
static Mirror mirror = new Mirror();

int choice; //选择
String girlName;//使用化妆品的人

MakeUp(int choice, String girlName) {
this.choice = choice;
this.girlName = girlName;
}

@Override
public void run() {
try {
makeup();
} catch (InterruptedException e) {
e.printStackTrace();
}
}

private void makeup() throws InterruptedException {
if (choice == 0) {
synchronized (lipstick) {//获得口红的锁
System.out.println(this.girlName + "获得口红的锁");
Thread.sleep(1000);
}
synchronized (mirror) {//一秒后想要镜子
System.out.println(this.girlName + "获得镜子的锁");
Thread.sleep(1000);
}
} else {
synchronized (mirror) {//获得镜子的锁
System.out.println(this.girlName + "获得镜子的锁");
Thread.sleep(1000);
}
synchronized (lipstick) {//一秒后想要口红的锁
System.out.println(this.girlName + "获得口红的锁");
}
}
}
}

Lock锁

image-20200512220240509

image-20200512221525803

image-20200512221555380

image-20200512222758535

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public class TestLock {
public static void main(String[] args) {
TestLock2 testLock2 = new TestLock2();

new Thread(testLock2).start();
new Thread(testLock2).start();
new Thread(testLock2).start();

}
}

class TestLock2 implements Runnable {

int ticketNums = 10;
//定义lock锁 可重复锁
private final ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
try {
lock.lock();//加锁
if (ticketNums > 0) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(ticketNums--);
} else {
break;
}
} finally {
lock.unlock();//解锁
}
}
}
}

线程协作

image-20200513094328469

image-20200513094425359

image-20200513094615131

方法名 作用
wait() 表示线程一直等待,直到其他线程通知,与sleep不同,会释放锁
wait(long timeout) 指定等待的毫秒数
notify() 唤醒一个处于等待状态的线程
notifyAll() 唤醒同一个对象上所有调用wait()方法的线程,优先级别高的线程优先调度

管程法

image-20200513095320710

生产者将生产好的数据放入缓冲区,消费者从缓冲区拿出数据

image-20200513101113569

image-20200513101231378

image-20200513101937138

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
//测试:生产者消费者模型-->利用缓冲区解决:管程法

//生产者,消费者,产品,缓冲区
public class TestPC {

public static void main(String[] args) {
SynContainer container = new SynContainer();

new Product(container).start();
new Consumer(container).start();
}
}

//生产者
class Product extends Thread {
SynContainer container;

public Product (SynContainer container){
this.container=container;
}

//生产
@Override
public void run() {
for (int i = 0; i < 100; i++) {
container.push(new Chicken(i));
System.out.println("生产了"+1+"只鸡");
}
}
}

//消费者
class Consumer extends Thread {

SynContainer container;

public Consumer (SynContainer container){
this.container=container;
}

//消费
@Override
public void run() {
for (int i = 0; i < 100; i++) {
Chicken chicken = container.pop();
System.out.println("消费了"+1+"只鸡");
}
}
}

//产品
class Chicken {
int id;//产品编号

public Chicken(int id) {
this.id = id;
}
}

//缓冲区
class SynContainer {

//需要一个容器大小
Chicken[] chickens = new Chicken[10];
//容器计数器
int count = 0;

//生产者放入产品
public synchronized void push(Chicken chicken) {
//如果容器满了,就需要等待消费者消费
if (count == chickens.length) {
//通知消费者消费,生产等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果没有满,我们就需要丢入产品
chickens[count] = chicken;
count++;

//可以通知消费者消费了
this.notifyAll();
}

//消费者消费产品
public synchronized Chicken pop(){
//判断是否能消费,缓冲区为空
if (count==0){
//等待生产者生产
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//可以消费,缓冲区不为空
count--;//count=10。数组下标最大为
Chicken chicken = chickens[count];


//吃完了,通知生产者生产
this.notifyAll();

return chicken;
}
}

信号灯法

image-20200513104804663

通过标志位来判断进行操作和等待操作

image-20200513104647560

image-20200513104741167

image-20200513104859563

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
//测试生产者消费者问题2 ,信号灯法。标志位
public class TestPC2 {

public static void main(String[] args) {
TV tv = new TV();
new Player(tv).start();
new Watcher(tv).start();
}
}


//生产者--->演员
class Player extends Thread {
TV tv;

public Player(TV tv) {
this.tv = tv;
}

@Override
public void run() {
for (int i = 0; i < 20; i++) {
if (i % 2 == 0) {
this.tv.play("快乐大本营播放中");
} else {
this.tv.play("抖音记录美好生活");
}
}
}
}

//消费者-->观众
class Watcher extends Thread{
TV tv;
public Watcher(TV tv){
this.tv=tv;
}

@Override
public void run() {
for (int i = 0; i < 20; i++) {
tv.watch();
}
}
}

//产品-->节目
class TV {
//演员表演,观众等待 T
//观众观看,演员等待 F

String voice;//表演界面
boolean flag = true;//标志位

//演员表演
public synchronized void play(String voice) {
if (!flag) {//flag为false
//等待观众观看
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}

System.out.println("演员表演了:" + voice);
//通知观众观看
this.notifyAll();//通知唤醒
this.voice = voice;
this.flag = !this.flag;
}

//观众表演
public synchronized void watch() {
if (flag) {//flag为true
//等待演员表演
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}

System.out.println("观看了" + voice);
//通知演员观看
this.notifyAll();//通知唤醒
this.flag = !this.flag;
}

}

线程池

image-20200513105038582

image-20200513105144482

image-20200513105351820

image-20200513105620134

image-20200513105639055

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

//测试线程池
public class TestPool {
public static void main(String[] args) {
//1.创建服务,创建线程池
//newFixedThreadPool 参数为线程池的大小
ExecutorService service = Executors.newFixedThreadPool(10);

//2.执行
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());

//3.关闭连接
service.shutdown();

}
}

class MyThread implements Runnable{

@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}