今天在做一個簡單的小test時,發現了ThreadPoolExecutor的一個問題,先列出代碼:主要功能是往一個鏈接中插入、刪除數據
鏈表的節點:
public class BoundedNode {
public Object value;
public BoundedNode next;
public BoundedNode(Object x){
value=x;
next=null;
}
}
增加、刪除節點的操作:
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class BoundedQueue {
ReentrantLock enqlock,deqlock;
Condition notempty,notfull;
AtomicInteger size;
BoundedNode head,tail;//隊列頭哨兵和尾哨兵
int capacity;
public BoundedQueue(int capacity) throws InterruptedException{
this.capacity=capacity;
head=new BoundedNode(null);
tail=head;
size=new AtomicInteger(0);
enqlock=new ReentrantLock();
deqlock=new ReentrantLock();
notfull=enqlock.newCondition();
notempty=deqlock.newCondition();
}
public void enq(Object x) throws InterruptedException{
boolean weakdeq=false;
//入隊者首先獲取入隊鎖
enqlock.lock();
try{
//判斷隊列是否爲滿,通過循環判斷,結合上面的加鎖,因此此方法也稱爲自旋//加鎖,優勢效率較高,缺點造成CPU消耗較大
while(size.get()==capacity){
//如果隊列滿,則在“不滿”條件上等待,直到隊列不滿的條件發生,等待時會//暫時釋放入隊鎖
notfull.await();
}
//如果“不滿”條件滿足,則構建新的隊列元素,並將新的隊列元素掛接到隊列//尾部
BoundedNode e=new BoundedNode(x);
tail.next=tail=e;
System.out.println("add:"+size);
//獲取元素入隊前隊列容量,並在獲取後將入隊前隊列容量增加1
if(size.getAndIncrement()==0){
//如果入隊前隊列容量等於0,則說明有出隊線程正在等待出隊條件notempty
//發生,因此要將相關標誌置爲true
weakdeq=true;
}
}finally{
//入隊者釋放入隊鎖
enqlock.unlock();
}
//判斷出隊等待標誌
if(weakdeq){
//入隊線程獲取出隊鎖
deqlock.lock();
try{
//觸發出隊條件,即隊列“不空”條件,使等待出隊的線程能夠繼續執行
notempty.signalAll();
}finally{
//入隊線程釋放出隊鎖
deqlock.unlock();
}
}
}
public Object deq() throws InterruptedException{
Object result=null;
boolean weakenq=false;
//出隊者首先獲取出隊鎖
deqlock.lock();
try{
//判斷隊列是否爲空,通過循環判斷,結合上面的加鎖,因此此方法也稱爲自旋//加鎖,優勢效率較高,缺點造成CPU消耗較大
while(size.get()==0){
//如果隊列空,則在“不空”條件上等待,直到隊列不空的條件發生,等待時會//暫時釋放出隊鎖
notempty.await();
}
//如果“不空”條件滿足,則通過隊列頭部哨兵獲取首節點,並獲取隊列元素值
result=head.next.value;
head=head.next;
//獲取元素出隊前隊列容量,並在獲取後將出隊前隊列容量減少1
System.out.println("delete:"+size);
if(size.getAndDecrement()==capacity){
//如果出隊前隊列容量等於隊列限額,則說明有入隊線程正在等待入隊條件//notfull發生,因此要將相關標誌置爲true
weakenq=true;
}
}finally{
//出隊者釋放出隊鎖
deqlock.unlock();
}
//判斷入隊等待標誌
if(weakenq){
//出隊線程獲取入隊鎖
enqlock.lock();
try{
//觸發入隊條件,即隊列“不滿”條件,使等待入隊的線程能夠繼續執行
notfull.signalAll();
}finally{
//出隊線程釋放入隊鎖
enqlock.unlock();
}
}
return result;
}
}
兩個線程,一個增加、一個刪除
import java.util.concurrent.Callable;
public class ThreadAdd implements Callable<String>{
public BoundedQueue boundedQueue;
public ThreadAdd(BoundedQueue boundedQueue){
this.boundedQueue = boundedQueue;
}
@Override
public String call() {
try {
boundedQueue.enq("x");
} catch (InterruptedException e) {
e.printStackTrace();
}
return "";
}
}
import java.util.concurrent.Callable;
public class ThreadDelete implements Callable<String>{
public BoundedQueue boundedQueue;
public ThreadDelete(BoundedQueue boundedQueue){
this.boundedQueue = boundedQueue;
}
@Override
public String call() {
try {
boundedQueue.deq();
} catch (InterruptedException e) {
e.printStackTrace();
}
return "";
}
}
測試案例:
package pool;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class mainTest {
public static void main(String[] args) throws InterruptedException{
BoundedQueue boundedQueue = new BoundedQueue(10);
ThreadPoolExecutor x = new ThreadPoolExecutor(1,1,1000,TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
List<Future<String>> list = new ArrayList<Future<String>>();
for(int i=0;i<3;i++){
Future<String> n = x.submit(new ThreadAdd(boundedQueue)); //增加
Future<String> m = x.submit(new ThreadDelete(boundedQueue)); //刪除
Future<String> l = x.submit(new ThreadDelete(boundedQueue)); //刪除
list.add(n);
list.add(m);
list.add(l);
}
Thread.sleep(3000);
for(Future<String> future : list){
System.out.println(future.isDone());
}
x.shutdown();
}
}
運行結果:
add:0
delete:1
true
true
false
false
false
false
false
false
false
發現只有前面兩個線程操作成功,其它的都陷入等待狀態;
分析原因發現可能是線程池設置過小有關,調整如下:
ThreadPoolExecutor x = new ThreadPoolExecutor(10,10,1000,TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
此時開了10個線程的線程池,按理來說足夠用了,運行下:
add:0
delete:1
add:0
delete:1
add:0
delete:1
true
true
true
true
true
false
true
false
false
發現還是有三個線程未能操作成功,分析原因可知,是由於隊列爲空,刪除操作的線程陷入了等待的狀態。總體分析原因:
1、線程池大小爲1時,由於刪除操作過多,導致某次刪除線程A的操作掛起,等待能刪的時候繼續執行;A一直等某個新增線程操作後才能繼續刪除
而由於線程池大小設計爲1,剩餘的線程都被放入隊列進行等待A執行完後繼續執行,但是A被掛起,一直不能執行,導致整個操作都相當於死鎖了~~~
2、線程池大小爲10時,刪除的操作比新增的操作多,就會產生刪除線程一直等待這個問題;