我在寫這個例子的時候,也出了很多問題。
今天頭很痛,好長時間沒弄出來,心情異常的煩躁。
先說下概念吧:
多線程之前通訊,不要想複雜了。他就是一個生產者 與 消費者的概念。
比如說一個生活中的例子(這是基於我的理解自己想的一個例子):
在銀行辦業務,需要排隊。 業務員屬於一個線程,辦業務的人屬於一個線程。
那麼在程序裏就相當於:業務員剛坐下開始上班,就處於一個wait狀態(等待),然後業務員就對外面的人立個牌子,空閒中。這就相當於notify,就是告訴外面的人,我這裏可以辦業務。 然後來銀行存錢的人發現,業務員立了個空閒中的牌子,然後存錢的人就從wait(等待狀態變成了)開始去辦業務的狀態,當這個存錢的人業務辦完了,就告訴櫃檯的業務員我辦好了。那麼這個過程也就是 notify ,然後業務員知道了,準備接待下一位存錢的人,業務員又處於一個wait狀態了。 就這樣循環下去。
所以: wait、notify就是一個是等待,一個是通知的意思。
如果不用,wait和notify會出現什麼情況呢 ?
比如:一個人在存錢的時候,來了個不講理的人,人家的錢還沒存,剛要存的時候,這個不講理的人就說,先給我存錢。這時候,銀行業務員可能擡頭看見了,也可能沒看見,拿着錢就存了。然後第3個人要取錢,卡給了,密碼輸了,又來了一個人,把第3個人推出去了。說了一句,取他一個億。 這時候是不會就亂套了 ?同樣也適用於業務員,不然,來了一個很熱心的業務員,說你讓開,讓我來給這個美女辦業務,上來也不知道要取錢還是存錢,想着這個美女應該是來開戶的吧。所以業務就辦錯了。
所以通過上面的例子:就可以知道,很多時候需要使用到wait 和 notify。而且爲了不讓別人插隊,需要一把鎖,把進去辦業務的人 和 業務員都鎖起來,辦好了之後,再解鎖。
用代碼實現,沒有用 wait 和 notify 也沒加鎖的效果:
public class Person {
private String username;
private int age;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"username='" + username + '\'' +
", age=" + age +
'}';
}
}
public class WriteRunnableThread implements Runnable {
private Person person;
public WriteRunnableThread(Person person) {
this.person = person;
}
public void run() {
/**
* 定義一個局部變量。
* 當這個值爲偶數時,爲Person類賦值: 偶數 0
* 當這個數爲奇數時,爲Person類賦值: 奇數 1
*/
int data = 0;
while (true) {
if (data == 0) {
person.setUsername("偶數");
person.setAge(0);
} else {
person.setUsername("奇數");
person.setAge(1);
}
/**
* 依次改變data的值爲: 偶數、奇數
*/
data = (data + 1) % 2;
}
}
}
public class ReadRunnableThread implements Runnable {
private Person person;
public ReadRunnableThread(Person person) {
this.person = person;
}
public void run() {
while (true) {
System.out.println(person.getUsername() + "," + person.getAge());
}
}
}
public class Test {
/**
* 現在要用多線程做一件事情:
* <p>
* 創建兩個線程,一個線程向Person類寫數據,一個線程從Person類中讀數據。
*/
public static void main(String[] args) {
Person person = new Person();
WriteRunnableThread writeRunnableThread = new WriteRunnableThread(person);
ReadRunnableThread readRunnableThread = new ReadRunnableThread(person);
Thread write = new Thread(writeRunnableThread);
Thread read = new Thread(readRunnableThread);
write.start();
read.start();
/**
* 會出現的結果:
*
* 奇數,1
* 奇數,0
* 偶數,0
* 偶數,1
*
* 有沒有發現,這不是我們想要的結果 ?
* 而且也會發現,拿到的數據,並不是按順序來的。
*/
}
}
然後,再來看,使用了wait 和 notify 並且加了同一把鎖的效果:
public class Person {
public String username;
public int age;
/**
* true 生產者線程等待, false 消費者線程等待
*/
public boolean flag = false;
}
public class WriteThread extends Thread {
private Person person;
public WriteThread(Person person) {
this.person = person;
}
@Override
public void run() {
/**
* 定義一個局部變量。
* 當這個值爲偶數時,爲Person類賦值: 偶數 0
* 當這個數爲奇數時,爲Person類賦值: 奇數 1
*/
int data = 0;
while (true){
synchronized (person) {
/**
* true: 等待消費
*/
if (person.flag) {
try {
person.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (data == 0) {
person.username = "偶數";
person.age = 0;
} else {
person.username = "奇數";
person.age = 1;
}
/**
* 依次改變data的值爲: 偶數、奇數
*/
data = (data + 1) % 2;
person.flag = false;
person.notify();
}
}
}
}
public class ReadThread extends Thread {
public Person person;
public ReadThread(Person person) {
this.person = person;
}
@Override
public void run() {
while (true){
synchronized (person) {
if (!person.flag) {
try {
person.wait();
} catch (InterruptedException e) {
}
}
System.out.println(person.username + "," + person.age);
person.flag = true;
person.notify();
}
}
}
}
public class Test {
/**
* 現在要用多線程做一件事情:
* <p>
* 創建兩個線程,一個線程向Person類寫數據,一個線程從Person類中讀數據。
* <p>
* 寫這個例子的時候報了很多錯:
* 1. notify 沒有在synchronized代碼塊中報錯。
* 2. 沒有使用 synchronized 也報錯了。
* 3. 沒有使用對象鎖,也報錯了。
*
* 所以:
* 使用wait 和 notify,一定要在同步代碼塊中,同時存在。
* 因爲wait 和 notify 在Object內中,所以一定要使用同一個對象鎖。
*/
public static void main(String[] args) {
Person person = new Person();
WriteThread writeThread = new WriteThread(person);
ReadThread readThread = new ReadThread(person);
writeThread.start();
readThread.start();
}
}
上面就是使用 wait notify 加鎖的代碼實現。
重點:(想了解這個可以去看 多線程高級講解五: 多線程的Lock鎖,多線程同步、多線程併發的概念 )
wait 、notify只能在synchronized中使用,不能在jdk1.5的Lock鎖中使用。
在Lock鎖中,如果要線程之間通訊,需要使用Condition類中的await 和 signal 。
wait 與 sleep的區別是什麼 ?
他們都是做休眠的。
wait必須在synchronized同步代碼塊中使用,只要運行到wait的代碼,下面的代碼就不會執行(處於等待狀態),他會等待其他線程notify的喚醒才能從休眠狀態改爲運行狀態,通過上面的例子,還可以看出wait還可以釋放鎖的資源。
sleep在線程中只等待,不會釋放鎖的資源。
本來想把上面這個例子,也實現成 銀行業務員 和 客戶這種關係,但是今天實在不想再看電腦屏幕了。將就着看吧。