所謂的併發,通常是指基於多處理器硬件環境的,容許系統在同一時刻執行多件不一樣的任務邏輯,在單處理器硬件環境下,通常是按照時間片輪轉的調度方式,實現宏觀意義上的併發,而事實上,在同一個時間點上,仍然只有一件任務在運行,我習慣把這種併發當作「僞併發」,如下所講的併發臨界資源管理,是基於多CPU硬件環境的。即在同一時刻,正在運行的不一樣CPU可能會訪問一些共用的資源,而臨界資源管理須要作的就是保證,這些資源的讀寫操做不能陷入死鎖以及,各線程得到的資源都是最新的。 java
在提到併發以前,我得提到一個基礎問題,那就是,程序在運行時,爲了加快系統的運算速度,臨時數據通常是放在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();//獲取線程池 dom
for(int i=0;i<5;i++){ ide
es.execute(new Accessor(i)); //執行線程 工具
}this
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();
}
}