解決問題:為多線程婴谱、多任務(wù)間的協(xié)作和數(shù)據(jù)共享提供并發(fā)控制。常用方法:內(nèi)部鎖、重入鎖勘究、讀寫鎖、信號量等斟冕。
一口糕、volatile
由于每個線程有自己的私有內(nèi)存空間(計數(shù)器、本地方法棧磕蛇、虛擬機棧)景描,同時還保存了主內(nèi)存中的共享變量的值的拷貝。因此如果線程想改變自己工作內(nèi)存的數(shù)據(jù)時秀撇,對其他線程是不可見的超棺。為此可以使用volatile關(guān)鍵字迫使所有線程讀寫主內(nèi)存中的對應(yīng)變量,從而使得其在多線程間可見呵燕。
聲明為volatile的變量解決了線程間數(shù)據(jù)共享問題棠绘,可以保證:
1.其他線程修改了變量,可以立即反饋到當(dāng)前線程
2.確保當(dāng)前線程修改了變量再扭,即使會寫到主內(nèi)存氧苍,對其他線程可見
3.編譯期可以保證其變量有序
主內(nèi)存和工作內(nèi)存?泛范?
volatile特別適合于在不同線程中進行控制狀態(tài)標(biāo)記量
volatile boolean inited = false; // 線程1
context = loadContext();
inited = true; // 線程2
while(!inited) {
sleep();
}
doSomethingWithConfig(context);
分析:
上面的代碼利用volatile修飾的inited保證上下文的初始化是否完成让虐,線程1初始化,線程2必須保證線程1初始化完成才能繼續(xù)進行下去罢荡。加入volatile可以保證變量inited的改變及時刷到主存中赡突,可以被其他線程及時看到。
二区赵、synchronized
采用同步方式
1.使用:
一種是鎖定一個對象的方法惭缰,如 public synchronized void method(),當(dāng)method方法調(diào)用時,調(diào)用線程首先獲得當(dāng)前對象的鎖笼才,若當(dāng)前對象鎖被其他線程持有从媚,則調(diào)用線程會等待,方法結(jié)束后患整,釋放對象鎖拜效,以上方法等價于
public void method(){ synchronized(this){ } }
一種是鎖定代碼塊,可以精確的控制并發(fā)代碼各谚,
public void method(Object so ){
some code here ;
synchronized(so){
...
}
other code here ;
}
一種是加在static方法上紧憾,鎖的是當(dāng)前Class對象,因此所有對該方法的調(diào)用昌渤,都要獲取該Class對象的鎖赴穗。
以上實際上鎖的是對象(所有同步代碼段用的同一個對象獨占鎖),只能防止多個線程同時執(zhí)行同一個對象的同步代代碼碼段,即使不同的同步代碼段也是不能同時訪問的般眉。
2.notify和wait
通常情況下了赵,雖然實現(xiàn)了同步效果,但是對于復(fù)雜的業(yè)務(wù)邏輯甸赃,常常配合Object對象的wait和notify方法柿汛。
wait過程中,釋放對象鎖埠对,典型用法:
synchronized (obj )
{
while (condition)
obj.wait();
//收到通知后络断,繼續(xù)執(zhí)行
}
首先在wait前獲得對象獨占鎖,循環(huán)進行狀態(tài)判斷项玛,跳出后貌笨,wait方法執(zhí)行后當(dāng)前線程會釋放對象鎖,供其他線程使用
當(dāng)?shù)却趏bj對象上的線程被notify后襟沮,會獲得當(dāng)前當(dāng)前對象的獨占鎖锥惋,并繼續(xù)運行。如果多個線程在等待开伏,那么notify則隨機選擇一個
如下示例實現(xiàn)一個阻塞隊列:
public class BlockQueue {
private List arrayList=new ArrayList();
public synchronized Object pop() throws InterruptedException {
while(arrayList.size()==0)
this.wait();
if(arrayList.size()>0)
{
return arrayList.remove(0);
}
else return null;
}
public synchronized void add(Object obj)
{
arrayList.add(obj);
this.notify();
}
}
同時Object還有wait(long timeout)和notifyAll()將喚醒所有等待在當(dāng)前對象上的所有線程
三净刮、ReentrantLock重入鎖
1.特性
(1).高并發(fā)下性能好,可中斷硅则、可定時
(2).公平鎖和非公平鎖:公平鎖能保證線程間公平競爭鎖對象淹父,對鎖的獲取是有序的,非公平則無序怎虫,可能會出現(xiàn)插隊現(xiàn)象
(3).必須手動調(diào)用unlock,否則會一直鎖定程序暑认,而synchronized隨著jvm自動釋放
2.重要方法
(1)lock:獲得鎖,如果鎖被占用大审,就一直等待
(2)lockInerruptibly : 獲得鎖蘸际,但優(yōu)先響應(yīng)中斷
(3)tryLock:嘗試獲得鎖,成功返回true徒扶;失敗返回false粮彤。該方法不等待,立即返回姜骡。
(4)tryLock(long timeout,TimeUnit unit)导坟,在給定時間內(nèi)獲得鎖
(5)unlock:釋放鎖
注:lock方法會一直等待,直到獲得鎖圈澈,同時在等待鎖的過程中惫周,線程不會響應(yīng)中斷;而lockInterruptibly在線程等待鎖過程中康栈,可以優(yōu)先響應(yīng)中斷递递。
總結(jié):ReentrantLock重入鎖提供了豐富的鎖控制喷橙,如無等待的tryLock,還有優(yōu)先響應(yīng)中斷的lockInterruptibly的鎖登舞。在鎖競爭激烈的情況贰逾,這些靈活的鎖可以提供更優(yōu)的方案,從而提升系統(tǒng)性能
四菠秒、ReadWriteLock讀寫鎖
jdk5提供的讀寫分離鎖疙剑,可以有效減少鎖競爭,提升系統(tǒng)性能稽煤。在讀多的情況下,可以做到多個線程并行讀操作囚戚,在寫的時候還是使用鎖酵熙。
適合于讀多寫少的場景
五、condition對象
具體參考:http://www.reibang.com/p/be2dc7c878dc
六驰坊、Semaphone信號量
信號量為多線程協(xié)作提供了更強大的控制方法匾二,是對鎖的擴展。無論是的內(nèi)部鎖synchronized還是重入鎖ReentrantLock都限制了只有一個線程訪問一個資源拳芙,而信號量支持多個線程同時訪問一個資源察藐。
方法:
1.構(gòu)造函數(shù) :需要指定信號量的準(zhǔn)入數(shù),即多少個線程同時訪問某個資源舟扎。
信號量對鎖進行了擴展分飞,可以限定對某個資源的最大可訪問線程數(shù)。
七睹限、ThreadLocal 線程局部變量
能保證不同線程中的局部變量獨立性譬猫,但是不屬于多線程的數(shù)據(jù)共享。
機制:不提供鎖羡疗,而是使用以空間換時間的方式染服,為每個線程提供變量的獨立副本,以保證線程安全叨恨,因此它不屬于多線程間數(shù)據(jù)共享的方案
public class MyThread implements Runnable {
public static final ThreadLocal<Date> localvar=new ThreadLocal<>();
private long time;
public MyThread(long time )
{
this.time=time;
}
@Override
public void run()
{
Date d=new Date(time);
for (int i=0;i<50000;i++)
{
localvar.set(d);
if(localvar.get().getTime()!=time)
System.out.println("id="+time+"locallvar="+localvar.get().getTime());
}
}
}