1. 程式人生 > >談談對多執行緒的看法——隨著瞭解的加深而不斷補充和修正

談談對多執行緒的看法——隨著瞭解的加深而不斷補充和修正

1、多執行緒的建立一共有三種方式,一是繼承 Thread 類,二是實現 Runnable 介面,然後藉助 Thread (Target) 構造方法進行多執行緒的例項化,都需要覆蓋重寫內部的 run() 方法,使用start()方法呼叫,這兩種方式都是沒有返回結果的。還有第三種,實現Callable<T> 介面,覆蓋重寫 call()方法,然後需要配合FutureTask<T>使用,這裡的泛型T就是返回的型別。

2、 對於某個類被public static 修飾的變數,或者非私有的變數,多個執行緒之間會共享這個變數,不管這個類被例項化多少次,這個變數都只會初始化一次,之後要看是否有執行緒對其進行修改。

3、多執行緒測試可以在main方法或者在Junit測試中進行,需要注意的是,需要使用 run as java application 的模式跑執行緒池相關的測試

4、多執行緒有兩類,一類是同時執行多個執行緒,執行緒之間沒有通訊,這種情況下要注意記憶體不要溢位,另一類是多執行緒且執行緒之間有通訊,比如共享變數或物件,有先後順序之分,這種情況下不單要注意記憶體不要溢位,還要注意執行緒安全。

5、wait() 和 notify ()是 Object 的屬性,不是Thread 的屬性,Object 是靜態變數時,可以完成執行緒之間的通訊。具體來說,併發同步的情況下,Object wait 本執行緒(執行緒1)阻塞,同級執行緒(執行緒2)開始執行佔有資源,執行緒2呼叫notify 並執行完畢執行緒2其餘程式碼釋放資源,被阻塞的執行緒1開始執行,需要配合  synchronized (object)使用。wait 阻塞時,本執行緒和其餘執行緒無差異,呼叫wait的執行緒在再次獲得臨界區上並沒有特殊權重。wait不會使本執行緒退出,notify不會使本執行緒立即退出,前者等待其他執行緒呼叫 notify 釋放臨界區後繼續執行(在阻塞執行緒中等待),後者則在剩餘程式碼執行結束後才正式釋放臨界區並結束執行緒。Object.wait()不可以多次呼叫,否則會造成死鎖,這一點是區分重入鎖的。
 

6、join() 會使指定執行緒執行完畢之後再執行之後的程式碼,這就好比這個多執行緒不再是非同步了,而是和之後的程式碼成了序列關係,join是加入的意思,子執行緒呼叫 join() 方法,子執行緒加入到父執行緒中,同級執行緒不會影響。必須先start再join,而且線上程池中join是無效的。

7、yield() 會讓執行緒主動讓出資源,但是資源仍舊可能被重新給這個執行緒。

8、synchronized 是 Java 語言中一個重量級操作,因為 Java 的執行緒是對映到作業系統的原生執行緒紙上的,如果要阻塞或者喚醒一個執行緒,都需要作業系統從使用者態轉為核心態,而狀態轉換需要耗費很多的處理器時間。synchronized對例項物件,例項方法,靜態方法加鎖。其中例項物件不可以為不變物件,比如Integer就是不變物件。

9、stop()被廢棄了,因為太野蠻了。可以用 Thread.currentThread().interrupt()-設定中斷、Thread.currentThread().isInterrupted()-僅查詢是否中斷、Thread.interrupted()-查詢是否中斷且消除中斷標誌,設定中斷並不會造成執行緒立即退出,而是需要有效的邏輯支援,interrupt 是執行緒中斷通訊的一種支援方式,一般而言執行緒究竟能否中斷在於是否走出迴圈的程式碼片段,但是有些情況下,可以規定執行緒是否響應中斷,這對於等待中的執行緒來說是一種“福音”。請參考本文第20條。

10、Thread.sleep(int ms) 和 Thread.currentThread.sleep(int ms)作用和效果是一樣的。

11、suspend() 掛起 和 resume() 繼續執行被廢棄,可能會導致死鎖,因為掛起的時候並沒有釋放臨界區資源。但是可以使用阻塞工具類,LockSupport,.park()和.unpark()。

12、volatile 修飾靜態變數,這樣虛擬機器會採取一些措施,保證這個變數的可見性等特點。(有序、可見、原子)。volatile 保持簡單操作的原子性,複合操作則無法取代鎖的作用(比如i++),再者,可以在虛擬機器server模式下確保資料的可見性和有序性(虛擬機器server模式優化後本會忽視變數的變化,加上volatile後子執行緒又可以看到主執行緒對變數的修改)。volatile只能確保一個執行緒修改後其他執行緒能夠看到這個改動,但當兩個執行緒同時修改一個數據時,卻依然會產生衝突。

底層實現來說,一般的執行緒都會有一個執行緒副本,這個副本來源於主存,使用volatile修飾的變數,不會有執行緒副本這一說,從而是各個執行緒共享了一個主存變數,確保了可見性。對於使用synchronized修飾的獲取變數的方法而言,本地副本還是存在的,區別是,修改本地執行緒副本後,主存的變數會更新,即二者是同步的,其他執行緒呼叫本地副本前,會先同步主存中的變數。

13、synchronized 和 ReentrantLock 都用於解決執行緒同步的問題,synchronized 用於修飾一個物件、一個方法,ReentrantLock 修飾一個程式碼塊,前者支援執行緒之間的通訊,使用 wait和notify方法,後者也支援需要Condition 的支援。關鍵字 synchronized 的用法有多種,分為:指定物件加鎖,直接作用於例項方法-相當於對當前例項加鎖,直接作用於靜態方法-對當前類枷鎖。被synchronized 修飾的多個執行緒之間是序列關係。

14、ThreadLocal並不能替代同步機制,兩者面向的問題領域不同。同步機制是為了同步多個執行緒對相同資源的併發訪問,是為了多個執行緒之間進行通訊的有效方式;而ThreadLocal是隔離多個執行緒的資料共享,從根本上就不在多個執行緒之間共享資源(變數),這樣當然不需要對多個執行緒進行同步了。所以,如果你需要進行多個執行緒之間進行通訊,則使用同步機制;如果需要隔離多個執行緒之間的共享衝突,可以使用ThreadLocal,這將極大地簡化你的程式,使程式更加易讀、簡潔。

15、多執行緒中,執行緒池.shutdown()會下發執行緒池關閉命令,執行緒池.isShutdown會立即為true,執行緒池.isTerminated 會線上程池中所有任務結束之後返回true,否則為false。

即後面二者返回true.執行shutdown命令是前提。

16、ThreadGroup 為執行緒組,可以打包管理多個執行緒,執行緒組可以包含執行緒組,已經不推薦使用。

17、守護執行緒是一種特殊的執行緒,比如垃圾回收執行緒、JIT(動態編譯,Just-In-Time,http://blog.csdn.net/hsuxu/article/details/9320699)執行緒,與守護執行緒對應的是使用者執行緒,使用者執行緒可以認為是系統的工作執行緒,它會完成這個程式應該要完成的業務操作。 當用戶執行緒全部執行結束,守護執行緒才會停止。使用者執行緒通過 setDaemon(true) 可以手動設定守護執行緒。守護執行緒區別於使用者執行緒,使用者執行緒可以指定為守護執行緒,需在start前設定,setDaemin(true),當用戶執行緒結束後,守護執行緒會自動退出,儘管守護執行緒一般是死迴圈。

18、執行緒優先順序級別高的優先執行,setPriority(int)。

19、ArrayList 是執行緒不安全的,需要採取同步措施。可以使用執行緒安全的 Vector 代替或者 Collections.synchronizasList(new LinkedList<Object>);

ArrayList與Vector都有一個初始的容量大小,當儲存進它們裡面的元素的個數超過了容量時,就需要增加ArrayList與Vector的儲存空間,Vector預設增長為原來兩倍,而ArrayList的增長策略為增長為原來的1.5倍,預設為10。ArrayList與Vector都可以設定初始的空間大小,Vector還可以設定增長的空間大小,而ArrayList沒有提供設定增長空間的方法。

LinkedList執行緒不安全,HashMap執行緒不安全,HashSet執行緒不安全,HashTable執行緒安全。

20、ReentrantLock有中斷lockInterruptibky響應中斷,Condition有awaitUnintwrruptibly,不響應中斷,Semaphore 有acquireUninterruptibky 不響應中斷,即wait,ReentrantLock預設不響應中斷,Condition,Semaphore預設響應中斷。ReentrantLock又叫重入鎖,可以重複加鎖,但是Object.wait()是不可以重複的,只可以一次,多次的話會造成死鎖。

 Condition來自ReentrantLock.newCondition,Condition的各項呼叫需要先獲得ReentrantLock.lock,使用完畢後需要unlock。ReentrantLock(true)表示鎖是公平的,公平鎖會降低效率 。Condition是鎖內的鎖。

21、訊號量結合線程池使用,類似於,執行緒池是個飯店,訊號量是某道菜,客人可以很多,但是每道菜就是那種上菜節奏。

22、執行緒池並非越大越好,因為即使是執行緒之間的切換也是需要開銷的。

23、在讀多寫少的場景,CopyOnWriteArrayList效能好於Vector。可以使用Collections.synchronizedList(new LinkedList<String>)封裝執行緒不安全的得到執行緒安全的。ConcurrentLinkedQueue是高併發環境中效能最好的佇列。

24、執行緒池:Executor 框架提供的各種型別的執行緒池。new*ThreadPool,newSingleThread*Executor。Fixed固定,Cached無限量,schedule可以延遲時間。返回ExecutorService或ScheduledExecutorService。ThreadPoolExecutor是底層實現的支援。這幾種都有耗盡資源的風險,一是建立無限多程序,二是任務佇列無限制。newCachedThreadPool多於執行緒空閒60秒後會被回收。

ExecutorService DIYThreadPool = new ThreadPoolExecutor(0,2,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>());

25、倒計時器,CountDownLatch,它可以讓某一個執行緒等待直到倒計時結束,才開始執行。計數減少是countdownlatch.countDown完成的,countdiwnlatch.await會檢查計數是否減少為零,此外會讓主執行緒等待子執行緒,有join()的效果。迴圈柵欄有同樣的效果,其中CyclicBarrier.await有檢查的意思。呼叫同一個迴圈柵欄的await方法會再執行緒數滿足總條件後繼續執行,一個執行緒呼叫兩次,就會進行兩次計數,這兩次是分在兩個批次中的。當所有的程式執行結束之後,CyclicBarrier會執行一個方法,也是以執行緒方式作為入參在構造方法中初始化的。單獨宣告,間接呼叫計數。

26、讀寫鎖,ReentrantReadWriteLock,獲取讀鎖:Lock lock=readWriteLock.readLock();獲取寫鎖:readWriteLick.writeLock();操作,lock.lock(),lock.unlock

27、內部鎖synchronized和重入鎖ReentrantLock一次都只允許一個執行緒訪問一個資源。而訊號量(Semaphore)可以指定多個執行緒,同時訪問某一個資源。通過訊號量獲得鎖和重入鎖獲得鎖沒有本質的差別。Thread像是容器,執行Runnable,多個Thread被傳入同一個Runnable物件,將共享這個Runnable內部的鎖約束,對於執行緒池來說也是一樣的。