線程共用受限資源
1、基本上所有的併發模式在解決線程衝突的時候,都是採用
序列化訪問共用資源的方案,這一位著在給定時刻只允許一個任務訪問共用資源; 這通常是在代碼前加上一條鎖語句實現的,這種機制常常被稱為
互斥量(mutex);
2、隱式加鎖同步:
使用synchronized關鍵字
1)共用資源一般是以對象形式存在的
記憶體片段,但也可以是檔案、輸入輸出連接埠、印表機; 2)要控制共用資源的訪問,
首先要將其封裝進一個對象,
然後要將所有訪問該資源的方法(或代碼塊)標記為synchronized;
同時要將該共用域標記為private;
3)範例程式碼
1、對整個方法進行同步class demo{ private int shareResource; public synchronized void method1(){ shareResource ++; } public synchronized void method2(){ shareResource --; }}2、對方法中需要同步的地方進行同步class demo{ private int shareResource; public void method1(){ System.out.println("method1 start"); synchronized(this){ shareResource ++; } System.out.println("method1 end"); } public void method2(){ System.out.println("method2 start"); synchronized(this){ shareResource --; } System.out.println("method2 end"); }}
4)對於
某個特定的對象來說,其範圍內
所有的synchronized方法共用一個鎖;
5)每個訪問臨界共用資源的方法都必須被同步,否則其他方法會隨意忽視該鎖;
3、顯式加鎖同步:利用Lock進行加鎖
1)java SE5中的java.util.concurrent.Locks 可以顯式地定義互斥機制; 2)範例程式碼
class Demo{ private int shareResource; private Lock lock = new ReentrantLock(); public int method{ lock.lock(); try{ shareResource ++; return shareResource; }finallly{ lock.unlock(); } }//將同步塊放置在try-finally塊中,return必須在try塊中,以確保unlock不會過早發生,從而將資料暴露給第二個任務;}
3)使用synchronized隱式加鎖的代碼量更少,
通常在一些特殊情況下才會使用Lock對象,如: ①使用synchronized不能嘗試擷取鎖,且最終擷取鎖失敗時只會拋出一個異常,無法進行任何的清除工作; ②嘗試擷取鎖一段時間,然後釋放它;
class Demo{ private Reentrantock lock = new ReentrantLock(); public void method1(){ boolean result = lock.tryLock(); //僅在調用時,lock處於空閑狀態才擷取鎖; try{ System.out.println(result); }finally{ if(result) lock.unlock(); } } public void method2(){ boolean result = false; try{ result = lock.tryLock(2,TimeUnit.SECONDS); //在調用時,lock在2seconds內處於空閑狀態,且線程在這個時間段類沒有被打斷,才擷取鎖; }catch(InterruptedException e){ System.out.println(result); }finally{ if(result) lock.unlock(); } }}
4、通過限制1個許可訊號量來類比一個互斥的鎖(訊號量時用來限制訪問共用資源的線程數)
class Task{ Semaphore semaphore = new Semaphore(1); public void xMethod(){ try{ semaphore.acquire(); statement; }catch(InterruptedException ex){ }finally{ semaphore.release(); } } }
5※、利用原子類取代synchronized互斥同步
1)原子性:
原子操作不需要進行同步控制,原子操作時不能被線程調度中斷的操作,一旦操作開始,那麼它一定可以在可能發生的“環境切換”之前執行完畢,可以利用這一點的特定來編寫
無鎖的代碼; 2)原子性可以應用在除了long和double之外的所有基本類型之上的“簡單操作”,對於讀寫除了long和double之外的基本變數這樣的操作,可以保證他們會被當做不可分割的操作來操作記憶體;
3)原子類:Java SE5引入了如AtomicInteger,AtomincLong,AtomicReference等特殊的原子性變數,它們提供以下形式的原子性條件更新操作: 這些類被調整為使用在某些現代處理器上可獲得的,並且在機器層級上的原子性,一般應用在效能調優上;
boolean compareAndSet(expectedValue,updateValue); 當前值==預期值,則以原子方式將該值設定為給定的值
int getAndAdd(int delta); 以原子的形式將給定值與當前值相加
int getAndSet(int delta); 以原子形式將當前值設定為給定值
int get();
void set(int newValue); 如:AtomicInteger i = new AtomicInteger(1024); i.compareAndSet(1024,2048); //該操作是安全執行緒的
4)範例程式碼
import java.util.concurrent.atomic.*;class Demo{ private AtomicInteger count = new AtomicInteger(0); public int getValue(){ return count.get(); } public void method1(){ count.getAndAdd(20);} public void method2(){ count.getAndDerement(); //count++} public void method3(){ count.getAndIncrement(); //count--} //Test public static void main(){ Demo demo = new Deme(); ExecutorService exec = Executors.newCachedThreadPool(); exec.execute(new Runnable(){ public void run(){ while(true){ demo.method1(); System.out.println(demo.getValue()); } } }); exec.execute(new Runnable(){ public void run(){ while(true){ demo.method2(); System.out.println(demo.getValue()); } } }); exec.execute(new Runnable(){ public void run(){ while(true){ demo.method3(); System.out..println(demo.getValue()); } } }); }}//對資料進行原子性操作,不用使用synchronized就可以對操作進行同步;
6、臨界區
1)臨界區(critical section)/同步控制塊:有時候只是希望多個線程同時存取方法內部的部分代碼,而不是整個方法,這種方式分離出來的代碼塊就是臨界區;
synchronized(syncObject){
statements;
}
2)在進入該同步控制塊之前,必須擷取syncObject對象的鎖 ,如果其他線程已經擷取該鎖,那麼要等到該鎖釋放後,才能進入該臨界區; 3)使用同步控制塊取代對整個方法進行同步控制
,可以使多個任務訪問對象的時間效能得到顯著地提升;
4)synchronized塊必須給定一個在其上進行同步的對象(一般比較合理的使用時synchronized(this)即本對象); 有時必須在另一個對象上同步,此時必須保證所有相關的任務都是在同一個對象上同步的,
此時兩個同步控制塊是相互獨立的,他們不會因為對方而阻塞,樣本:
//解決限制:一個對象只能擷取一個同步鎖————使得兩個任務可以同時進入同一個對象class Demo{ private Object syncObject = new Object(); public synchronized void method1(){ while(true){ println("method1()"); Thread.yield(); } } public void method2(){ synchronized(syncObject){ while(true){ println("method2()"); Thread.yield(); } } } //Test public static void main(){ final Demo demo = new Demo(); new Thread(){ public void run(){ demo.method1(); } } demo.method2(); }}
7、執行緒區域儲存ThreadLocal 1)除了以上使用互斥量同步的方法外,放置任務子在共用資源上產生衝突的第二種反方式
:根除對變數的共用; 2)在java中的線程
本地儲存是一種自動化機制,
可以為使用相同變數的每一個線程建立不同的儲存,可以使用 java.lang.ThreadLocal 來實現;
3)執行個體代碼
class Demo{ private static ThreadLocal<Integer> value = new ThreadLocal<Integer>(); value.set(0); public static void mian(String[] args){ ExecutorService executor = Executors.newCachedThreadPool(); for(int i=0;i<5;i++){ executor.execute(new Runnable(){ public void run(){ value.set(value.get()+i); System.out.print(value.get()+","); } }); } TimeUnit.SECONDS.sleep(3); executor.shutdown(); }}/*output: 0,1,2,3,4*///只能使用set(),get()來修改和訪問TreadLocal的資料;