|
# 多線程詳解
## 核心概念
- 線程就是獨(dú)立的執(zhí)行路徑
- 在程序運(yùn)行時(shí),即使沒(méi)有自己創(chuàng)建線程,后臺(tái)也會(huì)有多個(gè)線程,如主線程, gc線程;
- main()稱之為主線程,為系統(tǒng)的入口,用于執(zhí)行整個(gè)程序;
- 在一個(gè)進(jìn)程中,如果開(kāi)辟了多個(gè)線程,線程的運(yùn)行由調(diào)度器安排調(diào)度,調(diào)度器是與操作系統(tǒng)緊密相關(guān)的,先后順序是不能人為的干預(yù)的。
- 對(duì)同一份資源操作時(shí),會(huì)存在資源搶奪的問(wèn)題,需要加入并發(fā)控制;
- 線程會(huì)帶來(lái)額外的開(kāi)銷,如cpu調(diào)度時(shí)間,并發(fā)控制開(kāi)銷。
- 每個(gè)線程在自己的工作內(nèi)存交互,內(nèi)存控制不當(dāng)會(huì)造成數(shù)據(jù)不一致
## Thread類
**線程開(kāi)啟不一定立即執(zhí)行,由CPU調(diào)度執(zhí)行**
創(chuàng)建一個(gè)新的執(zhí)行線程有兩種方法。
1. 一個(gè)是將一個(gè)類聲明為`Thread`的子類。 這個(gè)子類應(yīng)該重寫`run`類的方法`Thread` 。然后可以分配并啟動(dòng)子類的實(shí)例。
2. 另一種方法來(lái)創(chuàng)建一個(gè)線程是聲明實(shí)現(xiàn)類`Runnable`接口。 那個(gè)類然后實(shí)現(xiàn)了`run`方法。 然后可以分配類的實(shí)例,在創(chuàng)建`Thread`時(shí)作為參數(shù)傳遞,并啟動(dòng)。
然后,以下代碼將創(chuàng)建一個(gè)線程并啟動(dòng)它運(yùn)行:
> ```
> PrimeRun p = new PrimeRun(143);
> new Thread(p).start();
> ```
### **方式一**
```java
//創(chuàng)建線程方式一:繼承Thread類,重寫run()方法,調(diào)用start開(kāi)啟線程
public class One extends Thread{
@Override
public void run(){
//run方法線程體
for (int i = 0; i < 10; i++) {
System.out.println("數(shù)字---" + i);
}
}
public static void main(String[] args) {
//main線程,主線程
//創(chuàng)建一個(gè)線程對(duì)象
One one = new One();
//調(diào)用start()方法開(kāi)啟線程
one.start();
for (int i = 0; i < 1000; i++) {
System.out.println("線程---" + i);
}
}
}
```
### **方式二**
```java
//創(chuàng)建線程方式二:實(shí)現(xiàn)runnable接口,重寫run方法,執(zhí)行線程需要丟入runnable接口實(shí)現(xiàn)類,調(diào)用start方法。
public class Two implements Runnable{
@Override
public void run() {
//run方法線程體
for (int i = 0; i < 100; i++) {
System.out.println("數(shù)字---" + i);
}
}
public static void main(String[] args) {
//創(chuàng)建runnable接口的實(shí)現(xiàn)類對(duì)象
Two two = new Two();
//創(chuàng)建線程對(duì)象,通過(guò)線程對(duì)象來(lái)開(kāi)啟我們的線程,代理
// Thread thread = new Thread(two);
// thread.start();
new Thread(two).start();
for (int i = 0; i < 100; i++) {
System.out.println("線程--" + i);
}
}
}
```
## 并發(fā)任務(wù)
初識(shí)并發(fā):
```java
public class Ticket implements Runnable{
//票數(shù)
private int tickNums = 10;
@Override
public void run() {
while (true){
if (tickNums<=0){
break;
}
//模擬延遲
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "-->拿到了第" + tickNums-- + "張票");
}
}
public static void main(String[] args) {
Ticket ticket = new Ticket();
new Thread(ticket,"阿冰").start();
new Thread(ticket,"黃牛").start();
new Thread(ticket,"小澤").start();
}
}
1.png (20.39 KB, 下載次數(shù): 69)
下載附件
2022-3-5 01:33 上傳
在運(yùn)行過(guò)程中會(huì)出現(xiàn)多個(gè)線程同時(shí)處理同一個(gè)資源的現(xiàn)象這個(gè)時(shí)候就需要并發(fā)。
**處理并發(fā)任務(wù)的安全性:隊(duì)列+鎖。**
### 龜兔賽跑例程
```java
import java.util.Random;
//模擬龜兔賽跑
public class Race implements Runnable{
private static String winner;
@Override
public void run() {
Random random = new Random();
int time = random.nextInt();
for (int i = 0; i <= 100; i++) {
//模擬兔子睡覺(jué)
if (Thread.currentThread().getName().equals("兔子") && i % time == 0) {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//判斷比賽是否結(jié)束
boolean flag = gameOver(i);
//如果比賽結(jié)束了就停止程序
if (flag){
break;
}
System.out.println(Thread.currentThread().getName() + "-->跑了" + i + "步");
}
}
//判斷是否完成比賽
private boolean gameOver(int steps) {
if (winner != null) {//已經(jīng)存在勝利者
return true;
}else {
if (steps>=100){
winner = Thread.currentThread().getName();
System.out.println("winner is " + winner);
return true;
}
}
return false;
}
public static void main(String[] args) {
Race race = new Race();
new Thread(race,"兔子").start();
new Thread(race,"烏龜").start();
}
}
```
## 靜態(tài)代理模式
```java
public class StaticType {
public static void main(String[] args) {
WedingCompany wedingCompany = new WedingCompany(new You());
wedingCompany.HappyMarry();
}
}
interface Marry{
void HappyMarry();
}
//真實(shí)對(duì)象,你去結(jié)婚
class You implements Marry{
@Override
public void HappyMarry() {
System.out.println("結(jié)婚了,超級(jí)開(kāi)心!");
}
}
//代理角色,幫助你結(jié)婚
class WedingCompany implements Marry{
private Marry target; //代理誰(shuí)-->真實(shí)目標(biāo)
public WedingCompany(Marry target){
this.target = target;
}
@Override
public void HappyMarry() {
before();
this.target.HappyMarry();
after();
}
private void before() {
System.out.println("結(jié)婚之前");
}
private void after() {
System.out.println("結(jié)婚之后");
}
}
```
靜態(tài)代理模式總結(jié):
- 真實(shí)對(duì)象和代理對(duì)象都要實(shí)現(xiàn)同一個(gè)接口
- 代理對(duì)象要代理真實(shí)角色
好處:
- 代理對(duì)象可以做很多真實(shí)對(duì)象做不了的事情
- 真實(shí)對(duì)象專注做自己的事情
## Lambda表達(dá)式
```java
/*
推導(dǎo)Lambda表達(dá)式
*/
public class ClassLambda {
//3.靜態(tài)內(nèi)部類
static class Like2 implements ILike {
@Override
public void lambda() {
System.out.println("I like lambda2!");
}
}
public static void main(String[] args) {
ILike like = new Like();
like.lambda();
like = new Like2();
like.lambda();
//4.局部?jī)?nèi)部類
class Like3 implements ILike{
@Override
public void lambda() {
System.out.println("I like lambda3!");
}
}
like = new Like3();
like.lambda();
//5.匿名內(nèi)部類,沒(méi)有類的名稱,必須借助接口或者父類
like = new ILike() {
@Override
public void lambda() {
System.out.println("I like lambda4!");
}
};
like.lambda();
//6.用lambda簡(jiǎn)化
like = ()->{
System.out.println("I like lambda5!");
};
like.lambda();
}
}
//1.定義一個(gè)函數(shù)式接口
interface ILike{
void lambda();
}
//2.實(shí)現(xiàn)類
class Like implements ILike {
@Override
public void lambda() {
System.out.println("I like lambda!");
}
}
```
**簡(jiǎn)化Lambda**
```java
public class TestLambda {
public static void main(String[] args) {
//lambda表示簡(jiǎn)化
Ilove ilove = (int a) -> {
System.out.println("I love you!" + a);
};
//簡(jiǎn)化1.參數(shù)類型
ilove = (a) -> {
System.out.println("1.I love you!" + a);
};
//簡(jiǎn)化2.簡(jiǎn)化括號(hào)
ilove = a -> {
System.out.println("2.I love you!" + a);
};
//簡(jiǎn)化3.簡(jiǎn)化花括號(hào)
ilove = a -> System.out.println("3.I love you!" + a);
ilove.love(520);
}
}
interface Ilove{
void love(int a);
}
```
總結(jié):
- lambda表達(dá)式只能有一行代碼的情況下才能簡(jiǎn)化成為一行,如果有多行,那么就用代碼塊包裹。
- 前提是接口時(shí)函數(shù)式接口。
- 多個(gè)參數(shù)也可以去掉參數(shù)類型,要去掉都去掉,必須加上括號(hào)。
## Stop方法:線程停止
```java
//測(cè)試stop
//1.建議線程正常停止--->利用次數(shù),不建議死循環(huán)
//2.建議使用標(biāo)志位--->設(shè)置一個(gè)標(biāo)志位
//3.不要使用stop或者destroy等過(guò)時(shí)或者JDK不建議使用的方法
public class TestStop implements Runnable {
//1.設(shè)置一個(gè)標(biāo)志位
private boolean flag = true;
@Override
public void run() {
int i = 0;
while (flag){
System.out.println("run Thread..." + i++);
}
}
//2.設(shè)置一個(gè)公開(kāi)的方法停止線程,轉(zhuǎn)換標(biāo)志位
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 < 100; i++) {
System.out.println("main" + i);
if (i == 90){
//調(diào)用stop方法切換標(biāo)志位,讓線程停止
testStop.stop();
System.out.println("線程該停止了。");
}
}
}
}
```
## Sleep方法:線程休眠
```java
//計(jì)時(shí)器
public class TimeDown {
public static void main(String[] args) {
try {
Down();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void Down() throws InterruptedException {
int number = 10;
while (true) {
Thread.sleep(1000);
System.out.println(number--);
if (number <= 0)
break;
}
}
}
```
```java
import java.text.SimpleDateFormat;
import java.util.Date;
//打印當(dāng)前系統(tǒng)時(shí)間
public class SystemTime {
public static void main(String[] args) {
Date startTime = new Date(System.currentTimeMillis()); //獲取當(dāng)前時(shí)間
while (true){
try {
Thread.sleep(1000);
System.out.println(new SimpleDateFormat("YYYY/MM/DD\nHH:mm:ss\n").format(startTime));
startTime = new Date(System.currentTimeMillis()); //更新當(dāng)前時(shí)間
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
```
每個(gè)對(duì)象都有一把鎖,sleep方法不會(huì)釋放鎖。
## Yield方法:線程禮讓
- 禮讓線程,讓當(dāng)前正在執(zhí)行的線程暫停,但不阻塞
- 將線程從運(yùn)行態(tài)轉(zhuǎn)化為就緒態(tài)
- 讓CPU重新調(diào)度,禮讓不一定成功,看CPU心情
```java
public class TestYield {
public static void main(String[] args) {
MyYield myYield = new MyYield();
new Thread(myYield,"a").start();
new Thread(myYield,"b").start();
}
}
class MyYield implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "-->start");
Thread.yield();
System.out.println(Thread.currentThread().getName() + "-->end");
}
}
```
## Join方法:線程強(qiáng)制執(zhí)行
- Join合并線程,待線程執(zhí)行完成后,在執(zhí)行其他線程,其他線程阻塞
- 不建議使用,容易導(dǎo)致進(jìn)程阻塞
```java
public class TestJoin implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("VIP插隊(duì)" + i);
}
}
public static void main(String[] args) throws InterruptedException {
//啟動(dòng)我們的線程
TestJoin testJoin = new TestJoin();
Thread thread = new Thread(testJoin);
thread.start();
//主線程
for (int i = 0; i < 100; i++) {
if (i == 10){
thread.join();
}
System.out.println("main" + i);
}
}
}
```
## 線程的狀態(tài)
線程可以處于以下?tīng)顟B(tài)之一:
- [`NEW`](../../java/lang/Thread.State.html#NEW)
尚未啟動(dòng)的線程處于此狀態(tài)。
- [`RUNNABLE`](../../java/lang/Thread.State.html#RUNNABLE)
在Java虛擬機(jī)中執(zhí)行的線程處于此狀態(tài)。
- [`BLOCKED`](../../java/lang/Thread.State.html#BLOCKED)
被阻塞等待監(jiān)視器鎖定的線程處于此狀態(tài)。
- [`WAITING`](../../java/lang/Thread.State.html#WAITING)
正在等待另一個(gè)線程執(zhí)行特定動(dòng)作的線程處于此狀態(tài)。
- [`TIMED_WAITING`](../../java/lang/Thread.State.html#TIMED_WAITING)
正在等待另一個(gè)線程執(zhí)行動(dòng)作達(dá)到指定等待時(shí)間的線程處于此狀態(tài)。
- [`TERMINATED`](../../java/lang/Thread.State.html#TERMINATED)
已退出的線程處于此狀態(tài)。
**線程狀態(tài)觀測(cè)**
```java
//觀測(cè)線程的狀態(tài)
public class TestState {
public static void main(String[] args) {
Thread thread = new Thread(()->{
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("結(jié)束");
});
//觀測(cè)狀態(tài)
Thread.State state = thread.getState();
System.out.println(state); //NEW
//觀察重啟后
thread.start();
state = thread.getState();
System.out.println(state);//RUNNABLE
while (state != Thread.State.TERMINATED){//只要線程不終止,就一直輸出狀態(tài)
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
state = thread.getState(); //更新線程狀態(tài)
System.out.println(state); //輸出狀態(tài)
}
}
}
```
## 守護(hù)優(yōu)先級(jí)
```java
public class TestPriority {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName() +
"-->" +
Thread.currentThread().getPriority());
MyPriority myPriority = new MyPriority();
Thread t1 = new Thread(myPriority);
Thread t2 = new Thread(myPriority);
Thread t3 = new Thread(myPriority);
Thread t4 = new Thread(myPriority);
t1.start();
t2.setPriority(2);
t2.start();
t3.setPriority(6);
t3.start();
t4.setPriority(Thread.MAX_PRIORITY);
t4.start();
}
}
class MyPriority implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName() +
"-->" +
Thread.currentThread().getPriority());
}
}
```
## synchronized鎖機(jī)制
- 由于我們可以通過(guò) private 關(guān)鍵字來(lái)保證數(shù)據(jù)對(duì)象只能被方法訪問(wèn),所以我們只需要針對(duì)方法提出一套機(jī)制,這套機(jī)制就是synchronized關(guān)鍵字,它包括兩種用法:
- synchronized方法
- synchronized塊
```java
//同步方法:
public synchronized void method(int args){} //synchronized默認(rèn)鎖的是this.
```
- synchronized方法控制對(duì)“對(duì)象”的訪問(wèn),每個(gè)對(duì)象對(duì)應(yīng)一把鎖,每個(gè)synchronized方法都必須獲得調(diào)用該方法的對(duì)象的鎖才能執(zhí)行,否則線程會(huì)阻塞,方法一旦執(zhí)行,就獨(dú)占該鎖,直到該方法返回才釋放鎖,后面被阻塞的線程才能獲得這個(gè)鎖,繼續(xù)執(zhí)行
**同步塊**
```java
synchronized (Obj){}
```
- Obj 稱之為同步監(jiān)視器
- Obj 可以是任何對(duì)象(增刪改的對(duì)象),但是推薦使用共享資源作為同步監(jiān)視器
- 同步方法中無(wú)需指定同步監(jiān)視器,因?yàn)橥椒椒ǖ耐奖O(jiān)視器就是this ,就是
這個(gè)對(duì)象本身,或者是class [反射中講解]
- 同步監(jiān)視器的執(zhí)行過(guò)程
1. 第一個(gè)線程訪問(wèn),鎖定同步監(jiān)視器,執(zhí)行其中代碼.
2. 第二個(gè)線程訪問(wèn),發(fā)現(xiàn)同步監(jiān)視器被鎖定, 無(wú)法訪問(wèn).
3. 第一個(gè)線程訪問(wèn)完畢,解鎖同步監(jiān)視器.
4. 第二個(gè)線程訪問(wèn), 發(fā)現(xiàn)同步監(jiān)視器沒(méi)有鎖,然后鎖定并訪問(wèn)
```java
public class Bank {
public static void main(String[] args) {
Account account = new Account(100, "存錢");
GetMoney You = new GetMoney(account, 50, "我");
GetMoney Wife = new GetMoney(account, 100, "老婆");
Wife.start();
You.start();
}
}
//賬戶
class Account {
int SumMoney;
String name;
public Account(int sumMoney, String name) {
this.SumMoney = sumMoney;
this.name = name;
}
}
//取錢
class GetMoney extends Thread {
Account account; //賬戶
int TakeMoney; //取走了多少錢
int nowMoney; //手里有多少錢
public GetMoney(Account account, int TakeMoney, String name){
super(name);
this.account = account;
this.TakeMoney = TakeMoney;
}
//取錢
@Override
public synchronized void run(){
//鎖的對(duì)象是賬戶
synchronized (account) {
//判斷有沒(méi)有錢
if (account.SumMoney - TakeMoney < 0) {
System.out.println(Thread.currentThread().getName() + "錢不夠,沒(méi)有取到錢");
return;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//卡內(nèi)余額
account.SumMoney = account.SumMoney - TakeMoney;
//你手里的錢
nowMoney = nowMoney + TakeMoney;
System.out.println(this.getName() + "手里的錢:" + nowMoney);
System.out.println(account.name + "余額的錢還有" + account.SumMoney);
}
}
}
```
## 死鎖
- 從JDK 5.0開(kāi)始, Java提供了更強(qiáng)大的線程同步機(jī)制一通過(guò)顯式定 義同步鎖對(duì)象來(lái)實(shí)現(xiàn)同步。同步鎖使用Lock對(duì)象充當(dāng)
- java.til.concurrent.locks.Lock接C是控制多個(gè)線程對(duì)共享資源進(jìn)行訪問(wèn)的工具。鎖提供了對(duì)共享資源的獨(dú)占訪問(wèn),每次只能有-個(gè)線程對(duì)Lock對(duì)象加鎖,線程開(kāi)始訪問(wèn)共享資源之前應(yīng)先獲得L ock對(duì)象
- ReentrantLock類實(shí)現(xiàn)了Lock ,它擁有與synchronized相同的并發(fā)性和內(nèi)存語(yǔ)義,在實(shí)現(xiàn)線程安全的控制中,比較常用的是ReentrantLock,可以顯式加鎖、釋放鎖。
```java
import java.util.concurrent.locks.ReentrantLock;
public class TestLock {
public static void main(String[] args) {
wantTicket buy = new wantTicket();
new Thread(buy,"a").start();
new Thread(buy,"b").start();
new Thread(buy,"c").start();
}
}
class wantTicket implements Runnable{
int TicketNum = 10;
private final ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true){
try {//上鎖
lock.lock();
if (TicketNum > 0){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "-->拿到了第" + TicketNum-- + "張票");
}else {
break;
}
}finally {
lock.unlock();
}
}
}
}
```
- Lock是顯式鎖(手動(dòng)開(kāi)啟和關(guān)閉鎖,別忘記關(guān)閉鎖) synchronized是隱式鎖, 出了作用域自動(dòng)釋放
- Lock只有代碼塊鎖,synchronized有代碼塊鎖和方法鎖
- 使用Lock鎖,JVM將花費(fèi)較少的時(shí)間來(lái)調(diào)度線程,性能更好。并且具有更好的擴(kuò)展性(提供更多的子類)
- 優(yōu)先使用順序:
- Lock >同步代碼塊(已經(jīng)進(jìn)入了方法體,分配了相應(yīng)資源) >同步方法(在方法體之外)
### 生產(chǎn)者消費(fèi)者問(wèn)題
| 方法名 | 作用 |
| --- | --- |
| wait() | 表示線程一直等待 ,直到其他線程通知,與sleep不同,會(huì)釋放鎖 |
| wait(long timeout) | 指定等待的毫秒數(shù) |
| notify | 喚醒一個(gè)處于等待狀態(tài)的線程 |
| notifyAll | 喚醒同一個(gè)對(duì)象上所有調(diào)用wait()方法的線程,優(yōu)先級(jí)別高的線程優(yōu)先調(diào)度 |
```java
//測(cè)試:生產(chǎn)者消費(fèi)者模型-->利用緩沖區(qū):管程法
//生產(chǎn)者,消費(fèi)者,產(chǎn)品,緩沖區(qū)
public class TestPC {
public static void main(String[] args) {
SynContainer container = new SynContainer();
new Productor(container).start();
new Consumer(container).start();
}
}
//生產(chǎn)者
class Productor extends Thread{
SynContainer container;
public Productor(SynContainer container){
this.container = container;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
container.push(new Chicken(i));
System.out.println("生產(chǎn)了" +i +"只雞");
}
}
}
//消費(fèi)者
class Consumer extends Thread{
SynContainer container;
public Consumer(SynContainer container){
this.container = container;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("消費(fèi)了-->" + container.pop().id + "只雞");
}
}
}
//產(chǎn)品
class Chicken{
int id;
public Chicken(int id) {
this.id = id;
}
}
//緩沖區(qū)
class SynContainer{
//需要一個(gè)容器大小
Chicken[] chickens= new Chicken[10];
//容器計(jì)數(shù)器
int count = 0;
//生產(chǎn)者生產(chǎn)產(chǎn)品
public synchronized void push(Chicken chicken){
//如果容器滿了,我們就需要等待消費(fèi)
if (count == chickens.length){
//通知消費(fèi)者消費(fèi),生產(chǎn)等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
//如果沒(méi)有滿,我們就需要丟入產(chǎn)品
chickens[count] = chicken;
count++;
//可以通知消費(fèi)者消費(fèi)了
this.notifyAll();
}
}
//消費(fèi)者消費(fèi)產(chǎn)品
public synchronized Chicken pop(){
//判斷能否消費(fèi)
if (count == 0){
//等待生產(chǎn)者生產(chǎn),消費(fèi)者等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果可以消費(fèi)
count--;
Chicken chicken = chickens[count];
//吃完了,通知生產(chǎn)者生產(chǎn)
this.notifyAll();
return chicken;
}
}
```
```java
//測(cè)試生產(chǎn)者消費(fèi)者問(wèn)題2-->信號(hào)燈法,標(biāo)志位解決
public class TestPC2 {
public static void main(String[] args) {
TV tv = new TV();
new Player(tv).start();
new Watcher(tv).start();
}
}
//生產(chǎn)者-->演員
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("河南衛(wèi)視");
}else {
this.tv.play("春節(jié)聯(lián)歡晚會(huì)");
}
}
}
}
//消費(fèi)者-->觀眾
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();
}
}
}
//產(chǎn)品-->節(jié)目
class TV {
//演員表演,觀眾等待 T
//觀眾觀看,演員等待 F
String voice; //表演的節(jié)目
boolean flag = true;
//表演
public synchronized void play(String voice){
if (!flag){
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){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("觀看了:" + voice);
//通知演員表演
this.notifyAll();
this.flag = !this.flag;
}
}
```
|
|