Conidition中的SinglAll和await誤用
使用Condition的SinalAll和await()方法寫了一個生產者和消費者程序,功能很簡單,只是爲了理解singall和await方法:生產者線程生產message,只要broker中有消息,就會通知消費者去消費;消費者消費完message,通知生產者生產消息;
沒想到出現的問題每次不一樣,仔細分析了一下,自己沒有深刻理解這兩種方法原理,造成程序編寫有問題。記錄一下,爲了以後自己不再犯類似錯誤。
Condition主要是負責線程間通信與Object中方法類似,但是比起Object類中的方法要靈活很多,接口中定義的方法主要如下:
1. await();放棄當前獲得鎖,當前線程進入沉睡狀態
2. await(long,TimeUnit);放棄當前鎖,在規定時間內進入沉睡狀態
3. singal():喚起一個線程
4. singalAll():喚起所有線程
Condition主要是通過ReentrantLock中的newCondition()方法創建(具體內容暫不討論)。即Condition必須要與ReentrantLock結合起來使用,一個ReentrantLock可以創建多個Condition對象。
生產者和消費者
通過Condition中的await()方法和sinalAll方法模擬生產者和消費者。其中producer將消息放入broker中(List)。只要有消息,就會通知consumer去消費。
程序如下:
/**
* producer將消息發送至storeService,
* consumer從storeService中consume消息
* @author tonny
* 單例模式,確保只有一個實例
*/
public class MessageBrokerService {
private static MessageBrokerService instance = null;
private static List<String> oneMsgPool = new ArrayList<String>();
private MessageBrokerService(){}
public void putOneMessage(String msg){
if(!oneMsgPool.isEmpty()){
throw new IllegalArgumentException("隊列中消息個數不是一個");
}
oneMsgPool.add(msg);
}
public String getMessage(){
return oneMsgPool.remove(0);
}
public void putMessage(String msg){
oneMsgPool.add(msg);
}
/**
* 單例模式
* @return
*/
public static MessageBrokerService newInstance(){
if(null == instance){
synchronized(MessageBrokerService.class){
if(null == instance){
instance = new MessageBrokerService();
}
}
}
return instance;
}
}
生產者程序
/**
* 生產者服務,可以有多個生產者線程調用生產者服務
* 去生產消息
* @author tonny
* 生產者produce消息時,消費者通知生產者,消息已經被消費 了。
*/
public abstract class ProducerService {
/**
* 生產消息的服務
*/
public void putMessage(String msg){
Lock lock = MessageBlockMechanism.getLock();
Condition condition = MessageBlockMechanism.getLockCondition();
try {
//獲取對象鎖
lock.lock();
if(!MessageBlockMechanism.isProduceConsumeFlag()){
// 放棄當前鎖
condition.await();
}
if(MessageBlockMechanism.isProduceConsumeFlag()){
producerMessage(condition,msg);
}
} catch (Exception e) {
e.printStackTrace();
}finally{
lock.unlock();
}
}
public abstract void producerMessage(Condition condition,String msg);
}
具體子類代碼
public class MultiProducerService extends ProducerService {
@Override
public void producerMessage(Condition condition ,String msg){
MessageBrokerService.newInstance().putMessage(msg);
// 設置consumer獲取消息標識
MessageBlockMechanism.setProduceConsumeFlag(false);
condition.signalAll();
}
}
消費者代碼
/**
* 消費者服務,主要是消費者線程調用該服務去消費生產者生產的消息,
* (或者從broker中獲取消息)
* @author tonny
*
*/
public class ConsumerService {
public String consumeMessage(){
Lock lock = MessageBlockMechanism.getLock();
Condition condition = MessageBlockMechanism.getLockCondition();
String message = null;
try {
//獲取對象鎖
lock.lock();
if(MessageBlockMechanism.isProduceConsumeFlag()){
condition.await();
}
if(!MessageBlockMechanism.isProduceConsumeFlag()){
message = MessageBrokerService.newInstance().getMessage();
System.out.println("consumer當前線程["+Thread.currentThread().getName()+"] 消費消息 :"+message);
// 設置consumer獲取消息標識
MessageBlockMechanism.setProduceConsumeFlag(true);
condition.signalAll();
}
} catch (Exception e) {
e.printStackTrace();
}finally{
lock.unlock();
}
return message;
}
}
生產者消費者線程
public class ProducerServiceThread implements Runnable {
private ProducerService service;
public ProducerServiceThread(ProducerService service) {
super();
this.service = service;
}
public void run() {
service.putMessage("hello");
}
}
---
public class ConsumerServiceThread implements Runnable {
private ConsumerService service;
public ConsumerServiceThread(ConsumerService service) {
super();
this.service = service;
}
public void run() {
service.consumeMessage();
}
}
客戶端代碼
public class ProducerConsumerMultiTest {
public static void main(String[] args) {
ProducerService producer = new MultiProducerService();
ConsumerService consumer = new ConsumerService();
for(int i=0;i<10;i++){
Thread t1 = new Thread(new ProducerServiceThread(producer));
Thread t2 = new Thread(new ConsumerServiceThread(consumer));
t1.start();
t2.start();
}
}
正確結果應該是生產者生產10個消息,消費者消費10個消息,但是運行幾次結果如下:
第一種結果:
consumer當前線程[Thread-3] 消費消息 :hello
consumer當前線程[Thread-7] 消費消息 :hello
consumer當前線程[Thread-11] 消費消息 :hello
consumer當前線程[Thread-15] 消費消息 :hello
consumer當前線程[Thread-19] 消費消息 :hello
consumer當前線程[Thread-5] 消費消息 :hello
consumer當前線程[Thread-9] 消費消息 :hello
consumer當前線程[Thread-13] 消費消息 :hello
consumer當前線程[Thread-17] 消費消息 :hello
consumer當前線程[Thread-1] 消費消息 :hello
第二種結果
consumer當前線程[Thread-1] 消費消息 :hello
consumer當前線程[Thread-5] 消費消息 :hello
consumer當前線程[Thread-13] 消費消息 :hello
consumer當前線程[Thread-15] 消費消息 :hello
consumer當前線程[Thread-17] 消費消息 :hello
consumer當前線程[Thread-9] 消費消息 :hello
consumer當前線程[Thread-7] 消費消息 :hello
consumer當前線程[Thread-19] 消費消息 :hello
第三種結果
consumer當前線程[Thread-5] 消費消息 :hello
consumer當前線程[Thread-13] 消費消息 :hello
consumer當前線程[Thread-9] 消費消息 :hello
consumer當前線程[Thread-17] 消費消息 :hello
consumer當前線程[Thread-7] 消費消息 :hello
consumer當前線程[Thread-11] 消費消息 :hello
原因分析如下
所有線程啓動時,生產者消費者標識produceConsumeFlag爲true。同時啓動10個生產者10個消費者。生產者和消費者採用同一個鎖。即每次只能有一個線程獲得執行鎖權限。
1. 生產者先獲得鎖,則生產者produce一條message,將標誌設置爲false。執行singalAll(不釋放鎖)喚起所有線程,unlock(釋放鎖)。此時如果是一個consumer得到競爭鎖,則消費完消息。執行singalAll和unlock(還剩下18個線程)。這種情況下,消費者競爭鎖,標誌已經爲true,則消費者執行await方法(釋放鎖),等待被喚醒。此時還是其他消費者獲得鎖,繼續等待。直到其中一個producer獲得鎖,則再次生產消息;設置標誌,釋放鎖。如果還是另外生產者獲得鎖,則同樣會執行await等待。這就會出現在某一個時刻多個producer和consumer都由於執行了await方法,出現等待狀態。在這種情況下,如果consumer執行singalAll方法,unlock後。緊接着後面都是被其他consumer獲得鎖,每個consumer發現produceConsumeFlag爲true,不去消費消息,直接釋放鎖。最後一個consumer釋放鎖之後(消費的消息不足10條),其中一個producer獲得鎖資格,生產消息後,設置標誌爲false。此時獲得鎖的只能是producer,最終都會結束。造成上述其中一種情況。
2. 場景很多,想要把他描述清楚,不是很容易。只能靠自己去理解。關鍵是程序編寫有問題。
3. 再次記錄一下,只是爲了讓自己以後不要再犯同樣的錯誤!