sleep()和wait()的區別屬於老生常談了,大部分Java面試或者筆試都會問到。標準的答案是:java
其上第一點,線程阻塞二者都會釋放cpu資源,這一點很重要。想一想如下執行的代碼會有區別嗎?面試
// 代碼段1
while (true) {
System.out.println("Hello World");
}
// 代碼段2
while (true) {
System.out.println("Hello World");
Thread.sleep(1);
}
複製代碼
上面的代碼段1,是一個死循環,執行該代碼電腦風扇就呼呼響了,cpu佔用也提高。而代碼段二,也是使用了while(true)包裹,但cpu佔用卻不會明顯提高。緩存
從上面咱們知道,若是while(true)中使用了能使得線程阻塞的代碼,那麼程序將一直運行但cpu不會高佔用。JDK源碼中大量使用了這個設計。好比,延遲線程池ScheduledThreadPoolExecutor
,其使用了DelayedWorkQueue隊列,將task按執行時間排序,排在隊頭的第一個執行。安全
同時,使用for(;;)死循環,比較目標時間和當前時間的毫秒差值delay,若是delay小於等於0則執行該task,不然awaitNanos(delay)阻塞線程。這樣就避免了for(;;)佔用cpu佔滿cpu。源碼以下:數據結構
public RunnableScheduledFuture<?> take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
for (;;) {
RunnableScheduledFuture<?> first = queue[0];
if (first == null)
available.await();
else {
long delay = first.getDelay(NANOSECONDS);
if (delay <= 0)
return finishPoll(first);
first = null; // don't retain ref while waiting
if (leader != null)
available.await();
else {
Thread thisThread = Thread.currentThread();
leader = thisThread;
try {
available.awaitNanos(delay);
} finally {
if (leader == thisThread)
leader = null;
}
}
}
}
} finally {
if (leader == null && queue[0] != null)
available.signal();
lock.unlock();
}
}
複製代碼
synchronized關鍵字咱們再熟悉不過了,其做用有兩點:多線程
使用synchronized同步塊或者同步方法時,只有一個線程能進入該區域,叫作線程同步。synchronized配置wait()和notify()使用,能夠在兩個或多個線程之間協調資源,叫作線程協做,這也是爲何wait()和notify()須要在synchronized中使用的緣由。this
那synchronized的原理是什麼呢?--監視器monitoratom
synchronized後通常帶有鎖對象,即synchronized(this)或者默認的鎖對象。synchronized底層由Java對象頭和monitor監視器實現。spa
監視器指令有ACC_SYNCHRONIZED和monitorenter、monitorexit。線程
而每一個Object對象在對象頭都有指向ObjectMonitor數據結構的指針,以下圖,ObjectMonitor中包含owner保存當前執行的線程,EntryList保存執行到synchronized的線程,waitSet保存執行了wait()方法的線程,線程是以ObjectWaiter形式封裝保存到隊列的,count表示重入次數,所以synchronized是可重入鎖。
總結以上的內容,就是synchronized圍繞對象鎖,對象鎖維護了EntryList用於線程同步,WaitSet用於線程協做。
synchronized用起來比較繁瑣難用。別擔憂,JDK給咱們提供了JUC包中的ReentrantLock,是synchronized的完美代替品。具體的內容能夠看相關資料,這裏不展開討論。
其實多線程的問題,涉及到了JMM內存模式,多線程會發生如下幾個問題:
解決了上面的三個問題,你的程序就是線程安全的了。而volatile能夠保證有序性和可見性。
volatile底層由內存屏障實現,內存屏障是cpu的指令,其禁止在內存屏障先後的指令中插入其餘指令,保證了有序性,同時內存屏障強制刷緩存,保證了可見性。單例的DCL(Double Check Lock)是典型的使用volatile的場景,由於new一個實例須要三步:分配內存、初始化、指針指向,這三步不能保證有序性,可使用volatile解決。
JUC包簡直是多線程開發的神器,JUC主要的概念是CAS,AQS,基本全部類都是構建在其上。JUC包基本能夠說是用來替代volatile和synchronized關鍵字的,其中atomic包用來替代volatile,lock包用來替代synchronized,lock中的condition實現了await(),signal()用來替代synchronized的wait(),notify。JUC包中的BlockingQueue是使用Condition來實現的。線程池則是構建在BlockingQueue隊列上。