Part 1 關(guān)于final變量在多線程的使用
我們?nèi)缃褚呀?jīng)了解到,除非使用鎖或volatile修飾符,否則無法從多個(gè)線程安全地讀取一個(gè)域。
但是還有一種情況可以安全的訪問一個(gè)共享域,即這個(gè)域聲明為final時(shí)。
final Map《String,Double》 accounts = new HashMap();
這樣子,其他線程會(huì)在構(gòu)造函數(shù)完成構(gòu)造之后才看到這個(gè)accounts變量。
如果不使用final,就不能保證其他線程看到的是accounts更新后的值,它們可能都只是看到null,而不是新構(gòu)造的HashMap。
當(dāng)然,對(duì)這個(gè)映射表的訪問是安全的,但是并不意味對(duì)他的操作是安全的!如果多個(gè)線程在讀寫這個(gè)映射表,仍然要進(jìn)行同步。
Part 2 原子性
假設(shè)對(duì)共享變量除了賦值之外并不完成其他操作,那么可以將這些共享變量聲明為volatile(他就是一個(gè)簡(jiǎn)單賦值,而不是讀取 增加某個(gè)數(shù) 再賦值 ,所以他是原子操作! 是 x = 1 而不是 x = x +1)。
但是我們還有更好的原子操作的表示方法,請(qǐng)看:
在java.util.concurrent.atomic包中有很多類使用了高效的機(jī)器指令(而不是鎖)來保證其他操作的原子性。
例如:AtommicInteger類提供了方法incrementAndGet 和 decrementAndGet,它們分別以原子方式將一個(gè)整數(shù)自增或自減。例如,可以安全地生成一個(gè)數(shù)值序列,如下所示:
public static AtomicLong nextNumber = new AtomicLong();
// In some thread.。.
long id = nextNumber.incrementAndGet();
incrementAndGet方法以原子方式將將AtomicLong自增,并返回自增后的值??梢员WC,即使多個(gè)線程訪問一個(gè)實(shí)例,也會(huì)計(jì)算并返回正確的值。
有很多方法可以以原子方式設(shè)置和增減值,不過,如果希望完成更復(fù)雜的更新,就必須使用compareAndSet方法。例如,假設(shè)希望跟蹤不同線程觀察最大值。下面的代碼應(yīng)該是這樣的:
do{
oldValue = largest.get();
newValue = Math.max(oldValue,observed);
}while(!largest.compareAndSet(oldValue,newValue))
這樣子的話,如果另一個(gè)線程也在更新largest,就可能阻止這個(gè)線程更新。這樣一來,compareAndSet就會(huì)返回false,而不是設(shè)置新值。這種情況下,循環(huán)會(huì)再次嘗試,讀取更新后的值,并嘗試修改。最終,他會(huì)成功地用新值替換原來的值。
accumulateAndGet方法利用一個(gè)二元操作符來合并原子值和所提供的參數(shù)。
還有g(shù)etAndUpate和getAndAccumulate。
如果有大量的線程要訪問相同的原子值,性能會(huì)大大下降,因?yàn)闃酚^更新需要太多次重試。Java 8 針對(duì)這點(diǎn)提供了LongAdder 和 LongAccumulator類來解決這個(gè)問題。LongAdder包括多個(gè)變量(加數(shù)),其綜合為當(dāng)前值??梢杂卸鄠€(gè)線程更新不同的加數(shù),線程個(gè)數(shù)增加時(shí)會(huì)自動(dòng)提供新的加數(shù)。代碼如下:
final LongAdder adder = new LongAdder();
for(...)
pool.submit(()-》{
while(...){
...
if(...)adder.increment();
}
});
...
long total = adder.sum());
LongAccumulator將這種思想推廣到任意的累加操作中。在構(gòu)造器中,可以提供這個(gè)操作以及它的零元素。要加入新的值,可以調(diào)用accumulate。調(diào)用get來獲得當(dāng)前值。
Part 3 死鎖以及Java關(guān)于死鎖的應(yīng)對(duì)(鎖測(cè)試和超時(shí)):
鎖和條件不能解決多線程中的所有問題比如,死鎖(考慮下哲學(xué)家問題)。
遺憾的是java中并沒有能完全避免死鎖的方法,但是我們可以通過自己的設(shè)計(jì)和良好的習(xí)慣來避免死鎖。
這里就可能需要用到測(cè)試鎖了:線程在調(diào)用lock方法獲得另一個(gè)線程持有的鎖的時(shí)候,很可能發(fā)生阻塞,甚至發(fā)生死鎖。trylock方法試圖去申請(qǐng)一個(gè)鎖,在成功獲得鎖后會(huì)返回一個(gè)true,否則,立即返回false,而且線程可以立即離開去做其他事情。
if(mylock.trylock()){
// 現(xiàn)在已經(jīng)上鎖
try{...}
finally{ mylock.unlock();}
}
else
// do something else
可以調(diào)用tryLock時(shí),使用超時(shí)參數(shù),像這樣:if(mylock.tryLock(100,TimeUnit.MILLISECONDS))
TimUnit是個(gè)枚舉類,可取的值包括SECONDS,MILLISECONDS,MICROSECONDS和NANOSECONDS。
有趣的地方在這里,如果一個(gè)線程調(diào)用帶有超時(shí)參數(shù)的tryLock,同時(shí)調(diào)用后如果線程在等待期間被中斷,將拋出一個(gè)InterruptedException異常。這是一個(gè)非常有用的特性,因?yàn)樵试S程序打破死鎖!
而調(diào)用lockInterruptibly方法,就相當(dāng)于一個(gè)超時(shí)設(shè)為無限的tryLock方法。
在等待條件對(duì)象時(shí)候也可以提供一個(gè)超時(shí):
myCondition.await(100,TimeUnit.MILLISECONDS)
Part 4 線程局部變量:
前面幾節(jié)中,我們討論了在線程間共享變量的風(fēng)險(xiǎn)。有時(shí)可能要避免共享變量,使用ThreadLocal輔助類為各個(gè)線程提供各自的實(shí)例。例如,SimpleDateFormat類不是線程安全的。假設(shè)有一個(gè)靜態(tài)變量:
public static final SimpleDateFormat dataFormat = new SimpleDateFormat(“yyyy-MM-dd”);
如果兩個(gè)線程都執(zhí)行以下操作:
String dateStamp = dateFormat.format(new Date());
結(jié)果很可能會(huì)混亂,因?yàn)閐ateFormat使用內(nèi)部數(shù)據(jù)結(jié)構(gòu)可能會(huì)被并發(fā)的訪問破壞。當(dāng)然可以使用同步,但開銷很大;或者也可以在需要時(shí)構(gòu)造一個(gè)局部SimoleDateFormat對(duì)象,不過這也太浪費(fèi)了。
這時(shí)候,我們就可以為每一個(gè)線程構(gòu)造一個(gè)實(shí)例,如下:
public static final ThreadLocal《SimpleDateFormat》 dateFormat =
ThreadLocal.withInitial(()-》new SimpleDateFormat(“yyyy-MM-dd”));
要是說具體的格式化方法,可以調(diào)用:
String dateStamp = dateFormat.get().format(new Date());
在一個(gè)給定線程中首次調(diào)用get時(shí),會(huì)調(diào)用initialValue方法。在此之后,get方法會(huì)返回屬于當(dāng)前線程的那個(gè)實(shí)例。
Part 讀寫鎖:
java.until.concurrent.locks包定義了兩個(gè)鎖類,我們已經(jīng)討論的ReentrantLock類和ReentranReandWriteLock類。如果很多線程從一個(gè)數(shù)據(jù)結(jié)構(gòu)讀取數(shù)據(jù)而很少線程修改其中數(shù)據(jù)的話,后者是十分有用的。在這種情況下,允許讀者線程共享訪問是合適的,當(dāng)然,寫者線程依然必須是互斥訪問。
下面是讀寫鎖的必要步驟:
(1)構(gòu)造一個(gè)ReentranReandWriteLock對(duì)象;
private ReetrantReadWriteLock rwl = new ReentrantReanWriteLock();
?。?)抽取讀鎖和寫鎖;
private Lock readLock = rwl.readLock();
private Lock writeLock = rwl.writeLock();
?。?)多所有的獲取方法加讀鎖;
public double getTotalBalance()
{
readLock.lock();
try{...}
finally{readLock.unlock();}
}
?。?)對(duì)所有的修改方法加寫鎖;
public void transfer(...)
{
writeLock.lock();
try{...}
finally{writeLock.unLock();}
}
評(píng)論