併發問題不再是一個只有高級程序員才能接觸的問題了,在使用多線程編程的時候,咱們更多的將目光放在追求系統的高併發和高吞吐,而這一切的前提是確保程序的正確性。在多線程編程中容易產生的問題有不少,好比線程安全問題、死鎖、飢餓等。下面的例子將從線程安全開始,逐步開啓併發編程的大門。 java
@NotThreadSafe public class UnsafeSequence { private int value; /** Returns a unique value. */ public int getNext() { return value++; } }
這個例子來源於《Java Concurrency In Practice》的第一章,一個最最簡單,卻容易引發線程安全問題的片斷。書中給出了以下解釋:The problem with UnsafeSequence is that with some unlucky timing, two threads could call getNext and receive the same value. Figure 1.1 shows how this can happen. The increment notation, nextValue++, may appear to be a single operation, but is in fact three separate operations: read the value, add one to it, and write out the new value. Since operations in multiple threads may be arbitrarily interleaved by the runtime, it is possible for two threads to read the value at the same time, both see the same value, and then both add one to it. The result is that the same sequence number is returned from multiple calls in different threads. 程序員
Figure 1.1的確很明瞭的告訴咱們線程推動的過程,這也給了咱們一個尋找線程不安全的方法,經過這樣圖示法來分析問題。 編程
固然,這裏引出了第一個可能會引發線程不安全的因素:程序中有變量的讀取、寫入或判斷操做。 安全
/**例如上述變量的自增*/ public int getNext() { return value++; } /**例如單例模式中隊變量的判斷操做*/ Public Object getInstance(){ If(obj==null){ return new Object(); } return obj; }
寫一個簡單的程序驗證一下上述問題,其實線程的學習最好的辦法就是舉例證實線程的不安全性,而後再想出解決方案,固然這也多是這部分學習最難所在: 多線程
package com.a2.concurrency.chapter1; /** * 線程安全第一種因素:程序中有變量的讀取、寫入或判斷操做 * @author ChenHui * */ public class UnsafeSequence { private int value; public int getValue() { return value++; } public static void main(String[] args) throws InterruptedException { final UnsafeSequence us = new UnsafeSequence(); Thread th1 = new Thread("th1") { @Override public void run() { System.out.println( us.getValue()+" "+super.getName()); } }; Thread th2 = new Thread("th2") { @Override public void run() { System.out.println(us.getValue()+" "+super.getName()); } }; th1.start(); /** * 若是不執行Thread.sleep(1000); * 偶爾結果爲: * 0 th2 * 0 th1 * 若是執行Thread.sleep(1000); * 結果爲: * 0 th1 * 1 th2 */ //Thread.sleep(1000); th2.start(); } }
對於這種因素產生的問題,咱們先給出一種經常使用解決方案,就是使用同步機制。這裏咱們先給出最簡單,你們也最容易想到的方案,對操做加synchronized關鍵字: 併發
private volatile int value; public synchronized int getNext() { return value++; }
在這裏使用了synchronized的狀況下,是否使用volatile關鍵字並非主要的。 app
這一節的最後例舉一下通常會遇到線程安全問題的地方,引用自併發編程書中第一章: ide
l Timer 函數
l Servlet/JSP 高併發
l RMI
l Swing
l ……
注:synchronized 方法控制對類成員變量的訪問: 每一個類實例對應一把鎖,每一個 synchronized 方法都必須得到調用該方法的類實例的鎖方能執行,不然所屬 線程阻塞 ,方法一旦執行,就獨佔該鎖,直到從該方法返回時纔將鎖釋放,此後被阻塞的線程方能得到該鎖,從新進入可 執行狀態。這種機制確保了同一時刻對於每個類實例,其全部聲明爲 synchronized 的成員函數中至多隻有一個處於可執行狀態(由於至多隻有一個可以得到該類實例對應的鎖),從而有效避免了類成員變量的訪問衝突(只要全部可能訪問類成員變量的方法均被聲明爲 synchronized)。