java併發臨界資源管理

所謂的併發,一般是指基於多處理器硬件環境的,允許系統在同一時刻執行多件不同的任務邏輯,在單處理器硬件環境下,一般是按照時間片輪轉的調度方式,實現宏觀意義上的併發,而事實上,在同一個時間點上,仍然只有一件任務在運行,我習慣把這種併發看成“僞併發”,以下所講的併發臨界資源管理,是基於多CPU硬件環境的。即在同一時刻,正在運行的不同CPU可能會訪問一些共用的資源,而臨界資源管理需要做的就是保證,這些資源的讀寫操作不能陷入死鎖以及,各線程獲得的資源都是最新的。
在提到併發之前,我得提到一個基礎問題,那就是,程序在運行時,爲了加快系統的運算速度,臨時數據一般是放在CPU緩存(Cache)中的,而不同的線程佔有一段不同的Cache,所以這裏就涉及到一個問題,即對共享資源而言,如何保證所有線程讀寫的同步。這就是併發所要解決的臨界資源管理問題的來由。java併發解決這個問題都是基於以下原理:保證每個線程對共享資源的寫操作都是獨佔式的,且將發生改變的共享資源寫回到主存(memory)中,基於緩存一致性規則,系統所有Cache中的相同共享資源值將全部過期失效,其他線程的讀操作被迫從主存中去取值了。(硬件實現,就不細說了)
一. Java多線程的實現
Java中從語言上實現多線程是通過實例化Thread類,調用實例start()方法,執行其run()方法所定義的任務來實現的。
得到我們自己的Thread實例,一般有兩種方式,一是讓自己的任務類繼承Thread父類,並覆寫其run方法,最後實例化這個任務類,調用start()方法,啓動線程。
另一種方法是任務類繼承Runnable接口,並實現其run()方法,通過Thread myThread = new Thread(myRunnable),傳入該Runnable實例作爲構造器參數,從而獲得Thread實例,調用start()方法,啓動線程。這種方式較爲常用。
以上提到的兩種方式,都需要顯示獲得Thread類的實例,並針對每單個Thread進行操作與管理。但在所需構建的線程較多時,這種方式便顯得較爲繁瑣,因爲對系統資源的佔用與釋放問題都交給了我們的設計者,所以Java中也提供了類似線程池的管理方式來管理這些線程。這些管理工具在java.util.concurrent包中。通過ExecutorService實例,進一步封裝了Thread管理細節,通過調用其execute()方法,執行線程,並通過shutdown()方式,釋放掉其中所有的線程所佔用的資源。其通用代碼如下:(推薦)

   ExecutorService es = Executors.newCachedThreadPool();//獲取線程池
   for(int i=0;i<5;i++){
       es.execute(new Accessor(i));  //執行線程
   }
   TimeUnit.SECONDS.sleep(5);
   es.shutdown();  //釋放所有資源

其獲取線程池的方式有三種,分別是
Executors.newCachedThreadPool():創建的線程池大小等於實際線程數
Executors.newFixedThreadPool(int num):創建固定大小的線程池
Executors.newSingleThreadExecutor():固定線程池大小爲1
二. 臨界資源管理
這部分涉及到幾個常用的關鍵詞,一個是synchronized,一個是volatile,一個是Atomatic*一系列原子類,以及ThreadLocal(線程本地存儲)。
1. volatile
這個關鍵字用來修飾變量,其功能和影響只需要記住一句話,它保證的是,被其所修飾的變量值,在每次發生改變之後,必定即時將改動後的值刷新寫入主存中
其侷限性也在於此,他只能保證變量的同步,而不是功能性的同步。例如對被volatile修飾的變量i,執行操作i++;這個操作並不線程安全,因爲這個操作不是原子性的,它可以拆分成兩步,一步是讀i值,第二步是做加法;volatile只能保證第一步讀到的值一定是當時最新的,但在第二步之前,該線程可能被暫時掛起,進而去執行其他的線程,如果其他線程在此時修改了i的值,那麼第二步算出來的值就不是那麼合理了。所以功能性的同步,就是synchronized這個關鍵字的事情了。
2. synchronized
這是Java併發保證同步最常用到的一個關鍵字。利用該關鍵字來保證同步是通過加鎖機制實現的,也可以說是一種隱式加鎖方式,之所以這麼說,是因爲你可以使用java.util.concurrent.locks類庫中提供的Lock類顯示地對代碼塊進行加鎖以實現線程同步。
Java中的每一個對象都可以作爲鎖,這裏的鎖是針對加鎖和解鎖兩種操作來說的,即同一時刻至多隻有一個線程能夠訪問作爲鎖的對象。根據synchronized的用途:synchronized可以用於修飾普通方法、靜態方法以及代碼塊,鎖的表現形式有以下三種:
對於普通同步方法,鎖是當前實例對象;對於靜態同步方法,鎖是當前類的Class對象;對於同步方法塊,鎖是synchronized括號裏參數指明的對象。
關於synchronized需要說明的有兩點:
(1)對於某個特定對象來說,其所有synchronized方法共享同一個鎖,即當一個類中同時包含多個Synchronized修飾的成員方法時,該類的一個實例對象,同一時刻只能訪問其中一個成員方法,對同步代碼塊,這一規則同樣成立。
(2)synchronized關鍵字不屬於方法特徵簽名的組成部分,所以可以在覆蓋方法的時候加上去。
3. 原子類
需要首先說明的是,原子操作是指不能被線程調度機制中斷的操作。原子性可以用於除了long和double之外的所有基本類型之上的簡單操作,即對於讀取和寫入除long和double之外的基本類型變量的值的操作,是原子操作。但是由於long和double爲64bit數據,而JVM對64位的讀取和寫入是當做兩個分離的32位操作來執行,這就可能產生一個在讀取和寫入操作中間發生上下文切換的隱患,導致不正確結果的可能性,這也被稱爲字撕裂。所以Java SE5引入了諸如AtomicInteger、AtomicLong以及AtomicReference等特殊的原子性變量,並提供了其對應的讀寫方法,從而保證其讀寫操作的原子性。具體參考手冊,其類庫爲java.util.concurrent.atomic。
4. ThreadLocal
引用自《Java編程編程思想》——“防止任務在共享資源上產生衝突的第二種方式是根除對變量的共享。線程本地存儲是一種自動化機制,可以爲使用相同變量的每個不同的線程都創建不同的存儲。因此,如果你有5個線程都要使用變量X所表示的對象,那麼線程本地存儲會生成5個用於X的不同的存儲塊。”,而創建和管理線程本地存儲可以用java.lang.ThreadLocal類來實現。
其提供的示例代碼如下:

/**
 * Created by Song on 2016/10/15.
 */
public class ThreadLocalVariableHolder {
    private static ThreadLocal<Integer> values = new ThreadLocal<Integer>(){
      private Random rand = new Random(47);
      @Override
      protected synchronized Integer initialValue() {
          return rand.nextInt(10000);
      }
    };
    public static void increment(){
        values.set(values.get()+1);
    }
    public static int get(){return values.get();}

    public static void main(String [] args) throws InterruptedException{
        ExecutorService es = Executors.newSingleThreadExecutor();
        for(int i=0;i<5;i++){
            es.execute(new Accessor(i));
        }
        TimeUnit.SECONDS.sleep(5);
        es.shutdown();
    }
}

class Accessor implements Runnable{
    private final  int id;
    public Accessor(int id){this.id=id;}
    public void run() {
        while (!Thread.currentThread().isInterrupted()){
            ThreadLocalVariableHolder.increment();
            System.out.println(this);
            Thread.yield();
        }
    }
    public String toString(){
        return "#"+id+": "+ThreadLocalVariableHolder.get();
    }
}
發佈了40 篇原創文章 · 獲贊 90 · 訪問量 37萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章