線程間通信
爲什麼要進行線程間通信?有一個很經典的例子就是生產者-消費者案例(簡化後):必須是生產者生產一個,消費者消費一個,沒有生產不能消費,沒有消費不能生產。生產者和消費者分別是兩個不同的線程,那這兩個線程在執行的時候必須相互通信才能按照要求執行下去:生產者生產前先看看消費者消費完了沒?消費完了再生產,否則不生產,生產好了之後,告訴消費者你來消費;同樣的,消費者消費前先看看你生產了沒?生產了就消費,否則不消費,消費好了之後,告訴生產者你來生產。這就是典型的線程間通信問題。
首先要明白的是:線程通信的基礎是線程同步,如果線程間連同步都不用的話,就更沒有通信的必要了。線程同步是對互斥資源(說互斥不太嚴謹,是需要控制訪問的資源,不一定是互斥)的同步訪問,而線程間通信是線程間運行進度的相互影響。
線程間通信即線程間相互影響,相互影響無非是操作資源影響和運行進度影響,資源影響由同步和進度影響共同造成,進度影響即是對線程進行手動的狀態間轉換。
Java中線程間通信是使用wait()、notify()、notifyAll()這三個方法完成的(除了這基本的線程間通信的方式,後續博文還會探討其他線程間通信的方式)。
wait()方法是Object類的不可重寫方法,那也就是說每個對象都有這個方法哦。調用wait方法後,持有當前對象監視器的線程交出監視器,並且掛起,等待被喚醒(當然也有可能你沒有喚醒它,它卻由於假喚醒醒了)
notify()方法是Object類的不可重寫方法,也是每個類都有這個方法。調用notify方法後,喚醒一個等待持有該對象監視器的任意(隨機的哦)一個線程
notifyAll()方法是Object類的不可重寫方法,每個類都有。調用這個方法後,喚醒所有等待持有該對象監視器的線程
未使用線程間通信的生產者-消費者:
public class ProducerCustomerWithoutCommunication {
public static void main(String[] args) {
Queue q = new Queue();
q.n = 0;
Thread t1 = new Thread(new Producer(q));
t1.start();
Thread t2 = new Thread(new Customer(q));
t2.start();
/*
* 想要實現的效果是:生產一個拿走一個,生產一個拿走一個,即生產完一個如果沒有拿走的話,不再生產,如果生產者還沒生產出來,則不拿
*/
//使用線程間通訊之前
// Set: 1
// Set: 2
// Set: 3
// Get: 3
// Get: 4
// Get: 4
// Get: 4
// Get: 4
// Get: 4
// Get: 4
// Set: 4
// Set: 5
// Get: 4
/*
* 沒有達到效果,因爲連續生產了三次後纔開始消費,然後連續消費了
* 更過分的是生產者還沒有生產完(顯然已經生產了4)4,消費者已經拿到4了
*/
}
}
class Queue{
int n;
/*
* 這裏加同步是爲了保證每次要麼只能生產要麼只能消費,不能這裏生產沒完呢就開始消費也不能消費沒完成就開始生產
* 但是,根據輸出:4還沒生產完就被消費了,並不是這裏的問題,而是其他地方的問題?發現了嗎?
*/
public synchronized void get(){
System.out.println("Get: "+n);
}
public synchronized void set(int n){
this.n = n;
System.out.println("Set: "+n);
}
}
class Producer implements Runnable{
Queue q;
Producer(Queue q){
this.q = q;
}
@Override
public void run(){
/*
* 線程不能被殺死,只能通過設置標誌位的方式使其終結,即讓run方法返回後這個線程就終結了
*/
while(q.n<5&&q.n>-5){
int n = q.n;
n++;
q.set(n);
}
}
}
class Customer implements Runnable{
Queue q;
public Customer(Queue q){
this.q = q;
}
@Override
public void run(){
while(q.n<5&&q.n>-5){
q.get();
}
}
}
使用了線程間通信的生產者-消費者:
public class ProducerCustomerWithCommunication {
public static void main(String[] args) {
Queue2 q = new Queue2();
Thread t1 = new Thread(new Producer2(q));
Thread t2 = new Thread(new Customer2(q));
t1.start();
t2.start();
// 運行結果:
// Set..1
// Get..1
// Set..2
// Get..2
// Set..3
// Get..3
// Set..4
// Get..4
// Set..5
// Get..5
// Set..6
// Get..6
// Set..7
// Get..7
// Set..8
// Get..8
// Set..9
// Get..9
// Set..10
// Get..10
}
}
class Queue2{
int n = 0;
boolean isProduced = false;
public synchronized void set(){
if(isProduced)
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
n++;
System.out.println("Set.."+n);
isProduced = true;
notify();
}
public synchronized void get(){
if(!isProduced)
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Get.."+n);
isProduced = false;
notify();
}
}
class Producer2 implements Runnable{
Queue2 q;
public Producer2(Queue2 q){
this.q = q;
}
@Override
public void run(){
while(q.n<10)
q.set();
}
}
class Customer2 implements Runnable{
Queue2 q;
public Customer2(Queue2 q){
this.q = q;
}
@Override
public void run(){
while(q.n<10)
q.get();
}
}