《Java並發編程實戰》讀書筆記__演算法

來源:互聯網
上載者:User

Subsections 
安全執行緒(Thread safety)
鎖(lock)
共用對象
對象組合
基礎構建模組
任務執行
取消和關閉
線程池的使用
效能與延展性
並發程式的測試
顯示鎖
原子變數和非阻塞同步機制

一、安全執行緒(Thread safety)
無論何時,只要多於一個線程訪問給定的狀態變數。而且其中某個線程會寫入該變數,此時必須使用同步來協助線程對該變數的訪問。 安全執行緒是指多個線程在訪問一個類時,如果不需要額外的同步,這個類的行為仍然是正確的。 安全執行緒的執行個體: (1)、一個無狀態的類是安全執行緒的。      無狀態類是指不包含任何域或也沒有引用其它類的域。一次特定計算的瞬間狀態,會唯一存在本地變數中。 (2)、原子操作是安全執行緒的。      自增操作時一個離散操作的簡寫形式,擷取當前值,加一,寫回新值。這是一個讀-改-寫操作,不具備原子性。 (3)、競爭條件是不安全的。      當計算的正確性依賴於運行時相關的時序或者多線程的交替時,會產生競爭條件。最常見的一種是檢查再運行(check-then-act)。 eg:

public class Instance() {     private Instance in = null;     public Instance getInstance() {          if(in == null) {              in= new Instance();          }          return in;     }}
這個例子意圖是想得到一個單例的對象,但是如果兩個線程同時執行到getInstance()這個地方,此時此刻,in是否為null,這依賴於時序。這是無法預測到的。 解決檢查再運行和讀-改-寫操作導致的線程不安全方法,就是必須保證操作的原子性。而java 內建的原子性機制-鎖可以解決這些個問題。
二、鎖(lock) (1)、內部鎖,java提供了強制原子性的內建鎖機制:synchronized 塊。 操作共用狀態的複合操作必須是原子的,以避免競爭條件,比如讀-改-寫操作和檢查再運行操作。複合操作會在完整的運行期佔有鎖,以確保其行為為原子的。
三、共用對象 要編寫正確的程式,關鍵問題在於:在訪問共用的可變狀態時需要進行正確的管理。 (1)、記憶體可見度 我們不僅希望防止某個線程在訪問某個狀態,而另外一個線程同時在修改這個狀態。同時希望確保一個線程修改了對象狀態以後,其它線程能夠看到發生的狀態變化。 在沒有同步的情況下,編譯器,處理器對操作的執行順序進行一些意想不到的調整。在缺少同步多線程程式中,要想對記憶體操作的執行順序進行判斷,幾乎無法得到正確的結論。
(2)、失效資料 在缺少同步的程式中產生錯誤的結果的一種情況就是,失效資料。
(3)、加鎖與可見度 鎖可以確保某個線程以一種可以預測的方式來查看另一個線程的執行結果,比如A執行某個同步代碼塊時,線程B隨後進入同一鎖保護的代碼塊。在B執行鎖保護的同步代碼塊時,可以看到線程A之前在同一代碼塊中所有的操作結果。 為什麼在訪問某個共用且可變的共用變數時,要求線程在同一個鎖上同步,是為了確保某個線程寫入該變數的值對於其它線程來說是可見的。否則,如果一個線程在未持有正確鎖的情況下讀取某個變數,那麼讀到的可能是一個失效值。
(4)、發布和逸出
(5)、線程封閉 當訪問共用的可變資料時,通常需要同步。一種避免使用同步的方式就是不共用資料。如果僅在單線程內訪問資料,就不需要同步。這種技術稱為線程封閉。它是安全執行緒最簡單方式之一。 eg:JDBC 的Connection對象,servlet請求大部分都是單線程同步方式處理,並且在Connection對象返回之前,串連池不會再將它分配給其它線程。 維持線程封閉更規範的方法是使用ThreadLocal 其設計的初衷是為瞭解決多線程編程中的資源共用問題。提起這個,大家一般會想到synchronized,synchronized採取的是“以時間換空間”的策略,本質上是對關鍵資源上鎖,讓大家排隊操作。而ThreadLocal採取的是“以空間換時間”的思路,為每個使用該變數的線程提供獨立的變數副本,在本線程內部,它相當於一個“全域變數”,可以保證本線程任何時間操縱的都是同一個對象。 原理:每個啟動並執行線程都會有一個類型為ThreadLocal.ThreadLocalMap的map,這個map就是用來儲存與這個線程綁定的變數,map的key就是ThreadLocal對象,value就是線程正在執行的任務中的某個變數的封裝類Entry.
ThreadLocal有四個方法:
//返回此線程局部變數的當前線程副本中的值
public T get() {        Thread t = Thread.currentThread();        ThreadLocalMap map = getMap(t);        if (map != null) {            ThreadLocalMap.Entry e = map.getEntry(this);            if (e != null)                return (T)e.value;        }        return setInitialValue();    }

//如果是第一次調用,需要初始化。

private T setInitialValue() {        T value = initialValue();        Thread t = Thread.currentThread();        ThreadLocalMap map = getMap(t);        if (map != null)            map.set(this, value);        else            createMap(t, value);        return value;    }
//將此線程局部變數的當前線程副本中的值設定為指定值

public void set(T value) {        Thread t = Thread.currentThread();        ThreadLocalMap map = getMap(t);        if (map != null)            map.set(this, value);        else            createMap(t, value);    }

//移除此線程局部變數的值。

public void remove() {         ThreadLocalMap m = getMap(Thread.currentThread());         if (m != null)             m.remove(this);     }
資料庫連接管理類,轉載:http://blog.csdn.net/ghsau/article/details/15732053

public class ConnectionManager {     /** 線程內共用Connection,ThreadLocal通常是全域的,支援泛型 */     private static ThreadLocal<Connection> threadLocal = new ThreadLocal<Connection>();          public static Connection getCurrConnection() {          // 擷取當前線程內共用的Connection          Connection conn = threadLocal.get();          try {               // 判斷串連是否可用               if(conn == null || conn.isClosed()) {                    // 建立新的Connection賦值給conn(略)                    // 儲存Connection                    threadLocal.set(conn);               }          } catch (SQLException e) {               // 異常處理          }          return conn;     }          /**      * 關閉當前資料庫連接      */     public static void close() {          // 擷取當前線程內共用的Connection          Connection conn = threadLocal.get();          try {               // 判斷是否已經關閉               if(conn != null && !conn.isClosed()) {                    // 關閉資源                    conn.close();                    // 移除Connection                    threadLocal.remove();                    conn = null;               }          } catch (SQLException e) {               // 異常處理          }     }}

(6)、 不變性 滿足同步需求的另外一種方法是使用不可變對象。不可變對象一定是安全執行緒的。 不可變對象的定義:對象建立出來後(通過構造方法)無論如何對此對象進行非暴力操作(不用反射),此對象的狀態(執行個體域的值)都不會發生變化,那麼此對象就是不可變的,相應類就是不可變類,跟是否用 final 修飾沒關係。 在並發程式中使用和共用對象時,可以使用一下策略: 線程封閉。線程封閉的對象只能由一個線程擁有。 唯讀共用。在沒有額外同步情況下,共用的唯讀對象可以由多個線程並發訪問。 線程共用安全。安全執行緒的對象在其內部實現同步。因此多個線程可以通過對象的公有介面來訪問而不需要進一步的同步。
四、對象組合 (1)、如何設計安全執行緒的類。      設計安全類需要注意一下三要素:      找出構造對象狀態的所有變數。      約束狀態變數的不變性條件。      建立對象狀態的並發訪問管理原則。 (2)、執行個體封閉      如果某對象不是安全執行緒的,我們可以通過多種技術使其在多線程中能安全的使用。確保該對象只能由單個線程訪問。

 public class PersonSet{          private final Set<Person> mySet = new HashSet<Person>();                    public sychronized void addPersion(Person p) {               mySet.add(p)          }               public sychronized boolean containsPerson(Person p) {               return mySet.contains(p);          }     }
雖然HashSet 並非安全執行緒的。但是mySet是私人的不會逸出。唯一能訪問mySet的代碼是addPerson(),和 containsPerson()。在執行上他們都要獲的PersonSet 上的鎖。PersonSet的狀態完全又它的內建鎖保護。所以 PersonSet是一個安全執行緒的類。 java 平台的類庫有很多 執行個體封閉的例子。比如一些基本的容器並非安全執行緒的,如ArrayList,HashMap。類庫提供的封裝方法, Collections.synchronizedList( list)、 Collections.synchronizedMap( m)使得非安全執行緒的類可以在多線程中使用。 (3)、java 監視器模式      把對象的所有可變狀態都封裝起來,並由對象自己的內部鎖來保護。
 public class privateLock {          private final Object myLock = new Object();               private int weight;                    void someMethod() {               synchronized(myLock) {                    //訪問weight               }          }     }
使用私人鎖對象比使用對象的內建鎖有許多優點。私人鎖可以將鎖封裝起來,客戶代碼無法得到鎖。但客戶可以通過公有方法來訪問鎖。以便參與到同步策略中去。
監視器好比一做建築,它有一個很特別的房間,房間裡有一些資料,而且在同一時間只能被一個線程佔據,進入這個建築叫做"進入監視器",進入建築中的那個特別的房間叫做"獲得監視器",佔據房間叫做"持有監視器",離開房間叫做"釋放監視器",離開建築叫做"退出監視器".
如上圖所示,一個線程通過1號門進入Entry Set(入口區),如果在入口區沒有線程等待,那麼這個線程就會擷取監視器成為監視器的owner,然後執行監視地區的代碼。如果在入口區中有其它線程在 等待,那麼新來的線程也會和這些線程一起等待。線程在持有監視器的過程中,有兩個選擇,一個是正常執行監視器地區的代碼,釋放監視器,通過5號門退出監視 器;還有可能等待某個條件的出現,於是它會通過3號門到Wait Set(等待區)休息,直到相應的條件滿足後再通過4號門進入重新擷取監視器再執行。
注意:當一個線程釋放監視器時,在入口區和等待區的等待線程都會去競爭監視器,如果入口區的線程贏了,會從2號門進入;如果等待區的線程贏了會從4 號門進入。只有通過3號門才能進入等待區,在等待區中的線程只有通過4號門才能退出等待區,也就是說一個線程只有在持有監視器時才能執行wait操作,處於等待的線程只有再次獲得監視器才能退出等待狀態。
五、基礎構建模組 (1)、同步容器類。包括Vector和Hashtable。同步的封裝容器類由Collections.sychronizedXXXFactory 方法建立。      eg:synchronizedList,synchronizedMap(m)、synchronizedSet(s) (2)、同步工具類。      阻塞隊列(BlockingQueue(LinkedBlockingQueue,ArrayBlockingQueue,PriorityBlockingQueue,SynchronousQueue))不僅可以儲存對象的容器,而且還可以協調生產者和消費者之間的控制流程。      訊號量(Semaphore):用來控制同時訪問某個特定資源的運算元量。通過 acquire() 擷取一個許可,如果沒有就等待,而 release() 釋放一個許可。Semaphore允許線程擷取許可, 未獲得許可的線程需要等待.這樣防止了在同一時間有太多的線程執行。Semaphore實現的功能就類似廁所有5個坑,假如有10個人要上廁所,那麼同時只能有多少個人去上廁所呢。同時只能有5個人能夠佔用,當5個人中 的任何一個人讓開後,其中等待的另外5個人中又有一個人可以佔用了。另外等待的5個人中可以是隨機獲得優先機會,也可以是按照先來後到的順序獲得機會,這取決於構造Semaphore對象時傳入的參數選項。單個訊號量的Semaphore對象可以實現互斥鎖的功能,並且可以是由一個線程獲得了“鎖”,再由另一個線程釋放“鎖”,這可應用於死結恢複的一些場合。  eg:類比30輛車去泊車,而車位有10個的情境. 當車位滿時,出來一輛車,才能有一輛車進入停車. 轉載http://mouselearnjava.iteye.com/blog/1921468
package my.concurrent.semaphore;import java.util.concurrent.Semaphore;public class Car implements Runnable {     private final Semaphore parkingSlot;     private int carNo;     /**     * @param parkingSlot     * @param carName     */     public Car(Semaphore parkingSlot, int carNo) {          this.parkingSlot = parkingSlot;          this.carNo = carNo;     }     public void run() {          try {               parkingSlot.acquire();               parking();               sleep(300);               parkingSlot.release();               leaving();          } catch (InterruptedException e) {               // TODO Auto-generated catch block               e.printStackTrace();          }     }     private void parking() {          System.out.println(String.format("%d號車泊車", carNo));     }     private void leaving() {          System.out.println(String.format("%d號車離開車位", carNo));     }     private static void sleep(long millis) {          try {               Thread.sleep(millis);          } catch (InterruptedException e) {               // TODO Auto-generated catch block               e.printStackTrace();          }     }}package my.concurrent.semaphore;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.Semaphore;public class ParkingCars {     private static final int NUMBER_OF_CARS = 30;     private static final int NUMBER_OF_PARKING_SLOT = 10;     public static void main(String[] args) {          /*          * 採用FIFO, 設定true          */          Semaphore parkingSlot = new Semaphore(NUMBER_OF_PARKING_SLOT, true);          ExecutorService service = Executors.newCachedThreadPool();          for (int carNo = 1; carNo <= NUMBER_OF_CARS; carNo++) {               service.execute(new Car(parkingSlot, carNo));          }          sleep(3000);          service.shutdown();          /*          * 輸出還有幾個可以用的資源數          */          System.out.println(parkingSlot.availablePermits() + " 個停車位可以用!");     }     private static void sleep(long millis) {          try {               Thread.sleep(millis);          } catch (InterruptedException e) {               // TODO Auto-generated catch block               e.printStackTrace();          }     }}

運行結果: 1號車泊車 
4號車泊車 
9號車泊車 
2號車泊車 
8號車泊車 
10號車泊車 
3號車泊車 
12號車泊車 
14號車泊車 
6號車泊車 
2號車離開車位 
4號車離開車位 
6號車離開車位 
1號車離開車位 
9號車離開車位 
3號車離開車位 
5號車泊車 
8號車離開車位 
10號車離開車位 
11號車泊車 
7號車泊車 
12號車離開車位 
13號車泊車 
14號車離開車位 
16號車泊車 
17號車泊車 
20號車泊車 
19號車泊車 
18號車泊車 
15號車泊車 
5號車離開車位 
20號車離開車位 
18號車離開車位 
22號車泊車 
11號車離開車位 
7號車離開車位 
13號車離開車位 
15號車離開車位 
21號車泊車 
26號車泊車 
23號車泊車 
28號車泊車 
25號車泊車 
16號車離開車位 
27號車泊車 
17號車離開車位 
30號車泊車 
24號車泊車 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.