一糟秘、隊列Queue類型
JUC包中隊列Queue是用于存儲線程任務(wù),常見的Queue類型有ArrayBlockingQueue球散、LinkedBlockingQueue尿赚、PriorityBlockingQueue和DelayQueue。
ArrayBlockingQueue
由數(shù)組組成的有界隊列蕉堰,隊列基于數(shù)組實現(xiàn),容量大小在創(chuàng)建ArrayBlockingQueue對象時已定義好吼畏,不可擴容嘁灯。
LinkedBlockingQueue
由鏈接節(jié)點組成的可選有界隊列泻蚊,隊列基于數(shù)組實現(xiàn),容量大小在創(chuàng)建LinkedBlockingQueue對象時已定義好,不可擴容丑婿。
PriorityBlockingQueue
由優(yōu)先級堆組成的無界優(yōu)先級隊列性雄,內(nèi)部線程是阻塞的,使用必須實現(xiàn)compareTo方法羹奉,這里的無界是理論上的秒旋。
DelayQueue
由優(yōu)先級堆支持的、基于時間的調(diào)度隊列诀拭,由優(yōu)先級堆支持的迁筛、基于時間的調(diào)度隊列,內(nèi)部基于無界隊列PriorityQueue實現(xiàn)耕挨,而無界隊列基于數(shù)組的擴容實現(xiàn)细卧。入隊的對象必須要實現(xiàn)Delayed接口。
public static void main(String[] args) {
DelayQueue<MovieTiket> delayQueue = new DelayQueue<MovieTiket>();
MovieTiket tiket = new MovieTiket("電影票0",10000);
delayQueue.put(tiket);
MovieTiket tiket1 = new MovieTiket("電影票1",5000);
delayQueue.put(tiket1);
MovieTiket tiket2 = new MovieTiket("電影票2",8000);
delayQueue.put(tiket2);
System.out.println("message:--->入隊完畢");
while( delayQueue.size() > 0 ){
try {
tiket = delayQueue.take();
System.out.println("電影票出隊:"+tiket.getMsg());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class MovieTiket implements Delayed {
//延遲時間
private final long delay;
//到期時間
private final long expire;
//數(shù)據(jù)
private final String msg;
//創(chuàng)建時間
private final long now;
public MovieTiket(String msg , long delay) {
this.delay = delay;
this.msg = msg;
expire = System.currentTimeMillis() + delay; //到期時間 = 當前時間+延遲時間
now = System.currentTimeMillis();
}
/**
* 用于延遲隊列內(nèi)部比較排序 當前時間的延遲時間 - 比較對象的延遲時間
* 越早過期的時間在隊列中越靠前
* @param delayed
* @return
*/
public int compareTo(Delayed delayed) {
return (int) (this.getDelay(TimeUnit.MILLISECONDS)
- delayed.getDelay(TimeUnit.MILLISECONDS));
}
}
二筒占、Condition隊列
BlockingQueue底層都是基于ReentrantLock 與 Condition隊列實現(xiàn)的贪庙,這也是Condition隊列只能在獨占模式下使用的原因。
多線程下的BlockingQueue是怎樣操作的翰苫?
若BlockingQueue初始容量為1止邮,T1 T2 同時操作BlockingQueue这橙,T1不斷往BlockingQueue放,T2不斷從BlockingQueue 取导披。T1 在放完第一次后屈扎,BlockingQueue已滿,無法繼續(xù)放了撩匕,T1阻塞助隧,T2喚醒,直到BlockingQueue容量為0滑沧;T2從BlockingQueue取出并村,取完第一次后,BlockingQueue為空滓技,無法繼續(xù)取了哩牍,T2阻塞,T1喚醒令漂,直到BlockingQueue容量為1膝昆。
條件關(guān)鍵:BlockingQueue的容量。
BlockingQueue的創(chuàng)建會創(chuàng)建一個ReentrantLock叠必、兩個隊列(NotFull與NotEmpty)荚孵。
public ArrayBlockingQueue(int capacity, boolean fair) {
if (capacity <= 0)
throw new IllegalArgumentException();
this.items = new Object[capacity];
lock = new ReentrantLock(fair);
notEmpty = lock.newCondition();
notFull = lock.newCondition();
}
ReentrantLock中用于多線程的同步操作,NotFull與NotEmpty用于存儲不同條件的線程任務(wù)纬朝。ArrayBlockingQueue的put 和 take操作都需要配合ReentrantLock的使用收叶,只有線程獲取到鎖的線程才能執(zhí)行put 和 take操作。
注意1:BlockingQueue put操作是通過獲取ReentrantLock鎖共苛,進入條件隊列而不是CLH同步隊列判没,同時線程會調(diào)用await方法進入阻塞狀態(tài)。
注意2:條件隊列中阻塞線程不會被喚醒隅茎,只有把條件隊列中的線程移到CLH同步隊列中才會被喚醒澄峰。
三、HashMap線程不安全
死鎖
Java 1.7 HashMap會產(chǎn)生死鎖辟犀,其數(shù)據(jù)結(jié)構(gòu)為數(shù)組+鏈表俏竞。在多線程場景下,擴容期間存在節(jié)點位置互換指針引用的問題有可能導致閉環(huán)堂竟,主要原因還是鏈表中節(jié)點在擴容的時候位置發(fā)生了變化魂毁。
Java 1.8 HashMap后不會產(chǎn)生死鎖,其數(shù)據(jù)結(jié)構(gòu)為數(shù)組+紅黑樹跃捣。
數(shù)據(jù)丟失
HashMap的put操作在多線程下有可能產(chǎn)生相同的hashcode漱牵,從而造成數(shù)據(jù)覆蓋夺蛇,進而造成數(shù)據(jù)丟失疚漆。Java 1.7 1.8 HashMap都會產(chǎn)生這樣的問題。