這一組是 Object 類的方法 須要注意的是:這三個方法都必須在同步的範圍內調用html
wait 阻塞當前線程,直到 notify 或者 notifyAll 來喚醒java
wait有三種方式的調用
wait()
必要要由 notify 或者 notifyAll 來喚醒
wait(long timeout)
在指定時間內,若是沒有notify或notifAll方法的喚醒,也會自動喚醒。
wait(long timeout,long nanos)
本質上仍是調用一個參數的方法
public final void wait(long timeout, int nanos) throws InterruptedException {
if (timeout < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (nanos < 0 || nanos > 999999) {
throw new IllegalArgumentException(
"nanosecond timeout value out of range");
}
if (nanos > 0) {
timeout++;
}
wait(timeout);
}
複製代碼
這一組是 Thread 類的方法面試
sleep 讓當前線程暫停指定時間,只是讓出CPU的使用權,並不釋放鎖編程
yield 暫停當前線程的執行,也就是當前CPU的使用權,讓其餘線程有機會執行,不能指定時間。會讓當前線程從運行狀態轉變爲就緒狀態,此方法在生產環境中不多會使用到,官方在其註釋中也有相關的說明緩存
/**
* A hint to the scheduler that the current thread is willing to yield
* its current use of a processor. The scheduler is free to ignore this
* hint.
*
* <p> Yield is a heuristic attempt to improve relative progression
* between threads that would otherwise over-utilise a CPU. Its use
* should be combined with detailed profiling and benchmarking to
* ensure that it actually has the desired effect.
*
* <p> It is rarely appropriate to use this method. It may be useful
* for debugging or testing purposes, where it may help to reproduce
* bugs due to race conditions. It may also be useful when designing
* concurrency control constructs such as the ones in the
* {@link java.util.concurrent.locks} package.
*/
複製代碼
join 等待調用 join 方法的線程執行結束,才執行後面的代碼 其調用必定要在 start 方法以後(看源碼可知) 使用場景:當父線程須要等待子線程執行結束才執行後面內容或者須要某個子線程的執行結果會用到 join 方法多線程
java編程語言容許線程訪問共享變量,爲了確保共享變量能被準確和一致的更新,線程應該確保經過排他鎖單獨得到這個變量。Java語言提供了volatile,在某些狀況下比鎖更加方便。若是一個字段被聲明成volatile,java線程內存模型確保全部線程看到這個變量的值是一致的。併發
valitate是輕量級的synchronized,不會引發線程上下文的切換和調度,執行開銷更小。app
內存可見性 多線程操做的時候,一個線程修改了一個變量的值 ,其餘線程能當即看到修改後的值異步
防止重排序 即程序的執行順序按照代碼的順序執行(處理器爲了提升代碼的執行效率可能會對代碼進行重排序)編程語言
並不能保證操做的原子性(好比下面這段代碼的執行結果必定不是100000)
public class testValitate {
public volatile int inc = 0;
public void increase() {
inc = inc + 1;
}
public static void main(String[] args) {
final testValitate test = new testValitate();
for (int i = 0; i < 100; i++) {
new Thread() {
public void run() {
for (int j = 0; j < 1000; j++)
test.increase();
}
}.start();
}
while (Thread.activeCount() > 2) { //保證前面的線程都執行完
Thread.yield();
}
System.out.println(test.inc);
}
}
複製代碼
確保線程互斥的訪問同步代碼
synchronized 是JVM實現的一種鎖,其中鎖的獲取和釋放分別是 monitorenter 和 monitorexit 指令,該鎖在實現上分爲了偏向鎖、輕量級鎖和重量級鎖,其中偏向鎖在 java1.6 是默認開啓的,輕量級鎖在多線程競爭的狀況下會膨脹成重量級鎖,有關鎖的數據都保存在對象頭中
加了 synchronized 關鍵字的代碼段,生成的字節碼文件會多出 monitorenter 和 monitorexit 兩條指令(利用javap -verbose 字節碼文件可看到關,關於這兩條指令的文檔以下:
monitorenter Each object is associated with a monitor. A monitor is locked if and only if it has an owner. The thread that executes monitorenter attempts to gain ownership of the monitor associated with objectref, as follows: • If the entry count of the monitor associated with objectref is zero, the thread enters the monitor and sets its entry count to one. The thread is then the owner of the monitor. • If the thread already owns the monitor associated with objectref, it reenters the monitor, incrementing its entry count. • If another thread already owns the monitor associated with objectref, the thread blocks until the monitor's entry count is zero, then tries again to gain ownership.
monitorexit The thread that executes monitorexit must be the owner of the monitor associated with the instance referenced by objectref. The thread decrements the entry count of the monitor associated with objectref. If as a result the value of the entry count is zero, the thread exits the monitor and is no longer its owner. Other threads that are blocking to enter the monitor are allowed to attempt to do so.
加了 synchronized 關鍵字的方法,生成的字節碼文件中會多一個 ACC_SYNCHRONIZED 標誌位,當方法調用時,調用指令將會檢查方法的 ACC_SYNCHRONIZED 訪問標誌是否被設置,若是設置了,執行線程將先獲取monitor,獲取成功以後才能執行方法體,方法執行完後再釋放monitor。在方法執行期間,其餘任何線程都沒法再得到同一個monitor對象。 其實本質上沒有區別,只是方法的同步是一種隱式的方式來實現,無需經過字節碼來完成。
會讓沒有獲得鎖的資源進入Block狀態,爭奪到資源以後又轉爲Running狀態,這個過程涉及到操做系統用戶模式和內核模式的切換,代價比較高。Java1.6爲 synchronized 作了優化,增長了從偏向鎖到輕量級鎖再到重量級鎖的過分,可是在最終轉變爲重量級鎖以後,性能仍然較低。
AtomicBoolean,AtomicInteger,AtomicLong以及 Lock 相關類等底層就是用 CAS實現的,在必定程度上性能比 synchronized 更高。
CAS全稱是Compare And Swap,即比較替換,是實現併發應用到的一種技術。操做包含三個操做數 —— 內存位置(V)、預期原值(A)和新值(B)。 若是內存位置的值與預期原值相匹配,那麼處理器會自動將該位置值更新爲新值 。不然,處理器不作任何操做。
若是隻是用 synchronized 來保證同步會存在如下問題 synchronized 是一種悲觀鎖,在使用上會形成必定的性能問題。在多線程競爭下,加鎖、釋放鎖會致使比較多的上下文切換和調度延時,引發性能問題。一個線程持有鎖會致使其它全部須要此鎖的線程掛起。
Java不能直接的訪問操做系統底層,是經過native方法(JNI)來訪問。CAS底層經過Unsafe類實現原子性操做。
ABA問題
什麼是ABA問題? 好比有一個 int 類型的值 N 是 1。 此時有三個線程想要去改變它:
線程A :但願給 N 賦值爲 2
線程B: 但願給 N 賦值爲 2
線程C: 但願給 N 賦值爲 1
複製代碼
此時線程A和線程B同時獲取到N的值1,線程A率先獲得系統資源,將 N 賦值爲 2,線程B因爲某種緣由被阻塞住,線程C在線程A執行完後獲得 N 的當前值2。此時的線程狀態:
在這個過程當中線程B獲取到N的值是一箇舊值,雖然和當前N的值相等,可是實際上N的值已經經歷了一次 1到2到1的改變。
上面這個例子就是典型的ABA問題
怎樣去解決ABA問題
給變量加一個版本號便可,在比較的時候不只要比較當前變量的值 還須要比較當前變量的版本號。Java中AtomicStampedReference 就解決了這個問題
循環時間長開銷大 在併發量比較高的狀況下,若是許多線程反覆嘗試更新某一個變量,卻又一直更新不成功,循環往復,會給CPU帶來很大的壓力。
CAS只能保證一個共享變量的原子操做
AQS抽象的隊列式同步器,是一種基於狀態(state)的鏈表管理方式。state 是用CAS去修改的。它是 java.util.concurrent 包中最重要的基石,要學習想學習 java.util.concurrent 包裏的內容這個類是關鍵。 ReentrantLock、CountDownLatcher、Semaphore 實現的原理就是基於AQS。想知道他怎麼實現以及實現原理 能夠參看這篇文章https://www.cnblogs.com/waterystone/p/4920797.html
在併發編程咱們通常使用Runable去執行異步任務,然而這樣作咱們是不能拿到異步任務的返回值的,可是使用Future 就能夠。使用Future很簡單,只需把Runable換成FutureTask便可。使用上比較簡單,這裏很少作介紹。
若是咱們使用線程的時候就去建立一個線程,雖然簡單,可是存在很大的問題。若是併發的線程數量不少,而且每一個線程都是執行一個時間很短的任務就結束了,這樣頻繁建立線程就會大大下降系統的效率,由於頻繁建立線程和銷燬線程須要時間。線程池經過複用能夠大大減小線程頻繁建立與銷燬帶來的性能上的損耗。
Java中線程池的實現類 ThreadPoolExecutor,其構造函數的每個參數的含義在註釋上已經寫得很清楚了,這裏幾個關鍵參數能夠再簡單說一下
最後,本文主要對Java併發編程開發須要的知識點做了簡單的講解,這裏每個知識點均可以用一篇文章去講解,因爲篇幅緣由不能對每個知識點都詳細介紹,我相信經過本文你會對Java的併發編程會有更近一步的瞭解。若是您發現還有缺漏或者有錯誤的地方,能夠在評論區補充,謝謝。