亚洲免费在线-亚洲免费在线播放-亚洲免费在线观看-亚洲免费在线观看视频-亚洲免费在线看-亚洲免费在线视频

Effective Java (并發(fā))

系統(tǒng) 2042 0

六十六、同步訪問共享的可變數(shù)據(jù):

?? ?? 在Java中很多時候都是通過synchronized關(guān)鍵字來實現(xiàn)共享對象之間的同步的。事實上,對象同步并不僅限于當多個線程操作同一可變對象時,仍然能夠保證該共享對象的狀態(tài)始終保持一致。與此同時,他還可以保證進入同步方法或者同步代碼塊的每個線程,都看到由同一個鎖保護的之前所有的修改效果。
?? ?? Java的語言規(guī)范保證了讀寫一個變量是原子的,除非這個變量的類型為long或double。換句話說,讀取一個非long或double類型的變量,可以保證返回的值是某個線程保存在該變量中的,即時多個線程在沒有同步的情況下并發(fā)地修改這個變量也是如此。然而需要特別指出的是,這樣的做法是非常危險的。即便這樣做不會帶來數(shù)據(jù)同步修改的問題,但是他會導(dǎo)致另外一個更為隱匿的錯誤發(fā)生。見如下代碼:

1 ???? public class StopThread {
2 ???????? private static boolean stopRequested = false ;
3 ???????? public static void main(String[] args) throw InterruptedException {
4 ???????????? Thread bgThread = new Thread( new Runnable() {
5 ???????????????? public void run() {
6 ???????????????????? int i = 0;
7 ???????????????????? while (!stopRequested)
8 ???????????????????????? i++;
9 ???????????????? }
10 ???????????? });
11 ???????????? bgThread.start();
12 ???????????? TimeUnit.SECONDS.sleep(1);
13 ???????????? stopRequested = true ;
14 ???????? }
15 ???? }
?

?? ?? 對于上面的代碼片段,有些人會認為在主函數(shù)sleep一秒后,工作者線程的循環(huán)狀態(tài)標志(stopRequested)就會被修改,從而致使工作者線程正常退出。然而事實卻并非如此,因為Java的規(guī)范中并沒有保證在非同步狀態(tài)下,一個線程修改的變量,在另一個線程中就會立即可見。事實上,這也是Java針對內(nèi)存模型進行優(yōu)化的一個技巧。為了把事情描述清楚,我們可以將上面代碼中run方法的代碼模擬為優(yōu)化后的代碼,見如下修改后的run方法:

1 public void run() {
2 ???????? int i = 0;
3 ???????? if (!stopRequested) {
4 ???????????? while ( true )
5 ???????????????? i++;
6 ???????? }
7 ???? }
?

?? ?? 這種優(yōu)化被稱為提升,正是HotSpot Server VM的工作。
?? ?? 要解決這個問題并不難,只需在讀取和寫入stopRequested的時候加入synchronized關(guān)鍵字即可,見如下代碼:

復(fù)制代碼
1 ???? public class StopThread {
2 ???????? private static boolean stopRequested = false ;
3 ???????? private static synchronized void requestStop() {
4 ???????????? stopRequested = true ;
5 ???????? }
6 ???????? private static synchronized boolean stopRequested() {
7 ???????????? return stopRequested;
8 ???????? }
9 ???????? public static void main(String[] args) throw InterruptedException {
10 ???????????? Thread bgThread = new Thread( new Runnable() {
11 ???????????????? public void run() {
12 ???????????????????? int i = 0;
13 ???????????????????? while (!stopRequested())
14 ???????????????????????? i++;
15 ???????????????? }
16 ???????????? });
17 ???????????? bgThread.start();
18 ???????????? TimeUnit.SECONDS.sleep(1);
19 ???????????? requestStop();
20 ???????? }
21 ???? }
?

????? 在上面的修改代碼中,讀寫該變量的函數(shù)均被加以同步。
?? ?? 事實上,Java中還提供了另外一種方式用于處理該類問題,即volatile關(guān)鍵字。該單詞的直譯為“易變的”,引申到這里就是告訴cpu該變量是容易被改變的變量,不能每次都從當前線程的內(nèi)存模型中獲取該變量的值,而是必須從主存中獲取,這種做法所帶來的唯一負面影響就是效率的折損,但是相比于synchronized關(guān)鍵字,其效率優(yōu)勢還是非常明顯的。見如下代碼:

1 ???? public class StopThread {
2 ???????? private static volatile boolean stopRequested = false ;
3 ???????? public static void main(String[] args) throw InterruptedException {
4 ???????????? Thread bgThread = new Thread( new Runnable() {
5 ???????????????? public void run() {
6 ???????????????????? int i = 0;
7 ???????????????????? while (!stopRequested)
8 ???????????????????????? i++;
9 ???????????????? }
10 ???????????? });
11 ???????????? bgThread.start();
12 ???????????? TimeUnit.SECONDS.sleep(1);
13 ???????????? stopRequested = true ;
14 ???????? }
15 ???? }
?

????? 和第一個代碼片段相比,這里只是在stopRequested域變量聲明之前加上volatile關(guān)鍵字,從而保證該變量為易變變量。然而需要說明的是,該關(guān)鍵字并不能完全取代synchronized同步方式,見如下代碼:

        
          1 
        
        
          public
        
        
          class
        
         Test {


        
          2
        
        
          private
        
        
          static
        
        
          volatile
        
        
          int
        
         nextID = 0;


        
          3
        
        
          public
        
        
          static
        
        
          int
        
         generateNextID() {

        
          
4
        
        
          return
        
         nextID++;

        
          
5
        
                 }


        
          6
        
             }
      

?? ?? generateNextID方法的用意為每次都給調(diào)用者生成不同的ID值,遺憾的是,最終結(jié)果并不是我們期望的那樣,當多個線程調(diào)用該方法時,極有可能出現(xiàn)重復(fù)的ID值。這是因為++運算符并不是原子操作,而是由兩個指令構(gòu)成,首先是讀取該值,加一之后再重新賦值。由此可見,這兩個指令之間的時間窗口極有可能造成數(shù)據(jù)的不一致。如果要修復(fù)該問題,我們可以使用JDK(1.5 later)中java.util.concurrent.atomic包提供的AtomicLong類,使用該類性能要明顯好于synchronized的同步方式,見如下修復(fù)后的代碼:

復(fù)制代碼
1 ???? public class Test {
2 ???????? private static final AtomicLong nextID = new AtomicLong();
3 ???????? public static long generateNextID() {
4 ???????????? return nextID.getAndIncrement();
5 ???????? }
6 ???? }

??? ?
六十七、避免過度同步:

?? ?? 過度同步所導(dǎo)致的最明顯問題就是性能下降,特別是在如今的多核時代,再有就是可能引發(fā)的死鎖和一系列不確定性的問題。當同步函數(shù)或同步代碼塊內(nèi)調(diào)用了外來方法,如可被子類覆蓋的方法,或外部類的接口方法等。由于這些方法的行為存在一定的未知性,如果在同步塊內(nèi)調(diào)用了類似的方法,將極有可能給當前的同步帶來未知的破壞性。見如下代碼:

1 ???? public class ObservableSet<E> extends ForwardingSet<E> {
2 ???????? public ObservableSet(Set<E> set) {
3 ???????????? super (set);
4 ???????? }
5 ???????? private final List<SetObserver<E>> observers = new ArrayList<SetObserver<E>>();
6 ???????? public void addObserver(SetObserver<E> observer) {
7 ???????????? synchronized (observers) {
8 ???????????????? observers.add(observer);
9 ???????????? }
10 ???????? }
11 ???????? public boolean removeObserver(SetObserver<E> observer) {
12 ???????????? synchronized (observers) {
13 ???????????????? return observers.remover(observer);
14 ???????????? }
15 ???????? }
16 ???????? private void notifyElementAdded(E element) {
17 ???????????? synchronized (observers) {
18 ???????????????? for (SetObserver<E> observer : observers)
19 ???????????????????? observer.added( this ,element);
20 ???????????? }
21 ???????? }
22 ???????? @Override public boolean add(E element) {
23 ???????????? boolean added = super .add(element);
24 ???????????? if (added)
25 ???????????????? notifyElementAdded(element);
26 ???????????? return added;
27 ???????? }
28 ???????? @Override public boolean addAll(Collection<? extends E> c) {
29 ???????????? boolean result = false ;
30 ???????????? for (E element : c)
31 ???????????????? result |= add(element);
32 ???????????? return result;
33 ???????? }
34 ???? }
?

?? ?? 下面的代碼片段是回調(diào)接口和測試調(diào)用:

1 ???? public interface SetObserver<E> {
2 ???????? void added(ObservableSet<E> set,E element);
3 ???? }
4 ????
5 ???? public static void main(String[] args) {
6 ???????? ObservableSet<Integer> set = new ObservableSet<Integer>( new HashSet<Integer>());
7 ???????? set.addObserver( new SetObserver<Integer>() {
8 ???????????? public void added(ObservableSet<Integer> s, Integer e) {
9 ???????????????? System.out.println(e);
10 ???????????? }
11 ???????? });
12 ???????? for ( int i = 0; i < 100; i++)
13 ???????????? set.add(i);
14 ???? }
?

????? 對于這個測試用例,他完全沒有問題,可以保證得到正確的輸出,即打印出0-99的數(shù)字。
?? ?? 現(xiàn)在我們換一個觀察者接口的實現(xiàn)方式,見如下代碼片段:

1 ???? set.addObserver( new SetObserver<Integer>() {
2 ???????? public void added(ObservableSet<Integer> s,Integer e) {
3 ???????????? System.out.println(e);
4 ???????????? if (e == 23)
5 ???????????????? s.removeObserver( this );
6 ???????? }
7 ???? });
?

?? ?? 對于以上代碼,當執(zhí)行s.removeObserver(this)的時候,將會拋出ConcurrentModificationException異常,因為在notifyElementAdded方法中正在遍歷該集合。對于該段代碼,我只能說我們是幸運的,錯誤被及時拋出并迅速定位,這是因為我們的調(diào)用是在同一個線程內(nèi)完成的,而Java中synchronized關(guān)鍵字構(gòu)成的鎖是可重入的,或者說是可遞歸的,即在同一個線程內(nèi)可多次調(diào)用且不會被阻塞。如果恰恰相反,我們的沖突調(diào)用來自于多個線程,那么將會形成死鎖。在多線程的應(yīng)用程序中,死鎖是一種比較難以重現(xiàn)和定位的錯誤。為了解決上述問題,我們需要做的一是將調(diào)用外部代碼的部分移出同步代碼塊,再有就是針對該遍歷,我們需要提前copy出來一份,并基于該對象進行遍歷,從而避免了上面的并發(fā)訪問沖突,如:

復(fù)制代碼
1 ???? private void notifyElementAdded(E element) {
2 ???????? List<SetObserver<E>> snapshot = null ;
3 ???????? synchronized (observers) {
4 ???????????? snapshot = new ArrayList<SetObserver<E>>(observers);
5 ???????? }
6 ???????? for (SetObserver<E> Observer : snapshot)
7 ???????????? Observer.added( this ,element);
8 ???? }
?

????? 減少不必要的代碼同步還可以大大提高程序的并發(fā)執(zhí)行效率,一個非常明顯的例子就是StringBuffer,該類在JDK的早期版本中即以出現(xiàn),是數(shù)據(jù)操作同步類,即時我們是以單線程方式調(diào)用該類的方法,也不得不承受塊同步帶來的額外開銷。Java在1.5中提供了非同步版本的StringBuilder類,這樣在單線程應(yīng)用中可以消除因同步而帶來的額外開銷,對于多線程程序,可以繼續(xù)選擇StringBuffer,或者在自己認為需要同步的代碼部分加同步塊。
?? ?
六十八、executor和task優(yōu)先于線程:

?? ?? 在Java 1.5 中提供了java.util.concurrent包,在這個包中包含了Executor Framework框架,這是一個很靈活的基于接口的任務(wù)執(zhí)行工具。該框架提供了非常方便的調(diào)用方式和強大的功能,如:
?? ?? ExecutorService executor = Executors.newSingleThreadExecutor();? //創(chuàng)建一個單線程執(zhí)行器對象。
?? ?? executor.execute(runnable);? //提交一個待執(zhí)行的任務(wù)。
?? ?? executor.shutdown();? //使執(zhí)行器優(yōu)雅的終止。
?? ?? 事實上,Executors對象還提供了更多的工廠方法,如適用于小型服務(wù)器的Executors.newCachedThreadPool()工廠方法,該方法創(chuàng)建的執(zhí)行器實現(xiàn)類對于小型服務(wù)器來說還是比較有優(yōu)勢的,因為在其內(nèi)部實現(xiàn)中并沒有提供任務(wù)隊列,而是直接將任務(wù)提交給當前可用的線程,如果此時沒有可用的線程了,則創(chuàng)建一個新線程來執(zhí)行該任務(wù)。因此在任務(wù)數(shù)量較多的大型服務(wù)器上,由于該機制創(chuàng)建了大量的工作者線程,這將會導(dǎo)致系統(tǒng)的整體運行效率下降。對于該種情況,Executors提供了另外一個工廠方法Executors.newFixedThreadPool(),該方法創(chuàng)建的執(zhí)行器實現(xiàn)類的內(nèi)部提供了任務(wù)隊列,用于任務(wù)緩沖。
?? ?? 相比于java.util.Timer,該框架也提供了一個更為高效的執(zhí)行器實現(xiàn)類,通過工廠方法Executors.ScheduledThreadPool()可以創(chuàng)建該類。它提供了更多的內(nèi)部執(zhí)行線程,這樣在執(zhí)行耗時任務(wù)是,其定時精度要優(yōu)于Timer類。


六十九、并發(fā)工具優(yōu)先于wait和notify:

?? ?? java.util.concurrent中更高級的工具分成三類:Executor Framework、并發(fā)集合(Concurrent Collection)以及同步器(Synchronizer)。相比于java.util中提供的集合類,java.util.concurrent中提供的并發(fā)集合就有更好的并發(fā)性,其性能通常數(shù)倍于普通集合,如ConcurrentHashMap等。換句話說,除非有極其特殊的原因存在,否則在并發(fā)的情況下,一定要優(yōu)先選擇ConcurrentHashMap,而不是Collections.syschronizedmap或者Hashtable。
????? java.util.concurrent包中還提供了阻塞隊列,該隊列極大的簡化了生產(chǎn)者線程和消費者線程模型的編碼工作。
? ? ? 對于同步器,concurrent包中給出了四種主要的同步器對象:CountDownLatch、Semaphore、CyclicBarrier和Exchanger。這里前兩種比較常用。在該條目中我們只是簡單介紹一個CountDownLatch的優(yōu)勢,該類允許一個或者多個線程等待一個或者多個線程來做某些事情。CountDownLatch的唯一構(gòu)造函數(shù)帶有一個int類型的參數(shù) ,這個int參數(shù)是指允許所有在等待的線程被處理之前,必須在鎖存器上調(diào)用countDown方法的次數(shù)。
?? ?? 現(xiàn)在我們給出一個簡單應(yīng)用場景,然后再給出用CountDownLatch實現(xiàn)該場景的實際代碼。場景描述如下:
? ? ? 假設(shè)想要構(gòu)建一個簡單的框架,用來給一個動作的并發(fā)執(zhí)行定時。這個框架中包含單個方法,這個方法帶有一個執(zhí)行該動作的executor,一個并發(fā)級別(表示要并發(fā)執(zhí)行該動作的次數(shù)),以及表示該動作的runnable。所有的工作線程自身都準備好,要在timer線程啟動時鐘之前運行該動作。當最后一個工作線程準備好運行該動作時,timer線程就開始執(zhí)行,同時允許工作線程執(zhí)行該動作。一旦最后一個工作線程執(zhí)行完該動作,timer線程就立即停止計時。直接在wait和notify之上實現(xiàn)這個邏輯至少來說會很混亂,而在CountDownLatch之上實現(xiàn)則相當簡單。見如下示例代碼:

1 ???? public static long time(Executor executor, int concurrency, final Runnable action) {
2 ???????? final CountDownLatch ready = new CountDownLatch(concurrency);
3 ???????? final CountDownLatch start = new CountDownLatch(1);
4 ???????? final CountDownLatch done = new CountDownLatch(concurrency);
5 ???????? for ( int i = 0; i < concurrency; i++) {
6 ???????????? executor.execute( new Runnable() {
7 ???????????????? public void run() {
8 ???????????????????? ready.countDown();
9 ???????????????????? try {
10 ???????????????????????? start.await();
11 ???????????????????????? action.run();
12 ???????????????????? } catch (InterruptedException e) {
13 ???????????????????????? Thread.currentThread().interrupt();
14 ???????????????????? } finally {
15 ???????????????????????? done.countDown();
16 ???????????????????? }
17 ???????????????? }
18 ???????????? });
19 ???????????? // 等待工作者線程準備可以執(zhí)行,即所有的工作線程均調(diào)用ready.countDown()方法。
20 ???????????? ready.await();
21 ???????????? // 這里使用nanoTime,是因為其精確度高于System.currentTimeMills()。
22 ???????????? long startNanos = System.nanoTime();
23 ???????????? // 該語句執(zhí)行后,工作者線程中的start.await()均將被喚醒。
24 ???????????? start.countDown();
25 ???????????? // 下面的等待,只有在所有的工作者線程均調(diào)用done.countDown()之后才會被喚醒。
26 ???????????? done.await();
27 ???????????? return System.nanoTime() - startNanos;
28 ???????? }
29 ???? }
?


七十一、慎用延遲初始化:

?? ?? 延遲初始化作為一種性能優(yōu)化的技巧,它要求類的域成員在第一次訪問時才執(zhí)行必要的初始化動作,而不是在類構(gòu)造的時候完成該域字段的初始化。和大多數(shù)優(yōu)化一樣,對于延遲初始化,最好的建議"除非絕對必要,否則就不要這么做"。延遲初始化如同一把雙刃劍,它確實降低了實例對象創(chuàng)建的開銷,卻增加了訪問被延遲初始化的域的開銷,這一點在多線程訪問該域時表現(xiàn)的更為明顯。見如下代碼:

1 ???? public class TestClass {
2 ???????? private final FieldType field;
3 ???????? synchronized FieldType getField() {
4 ???????????? if (field == null )
5 ???????????????? field = computeFieldValue();
6 ???????????? return field;
7 ???????? }
8 ???? }
?

????? 從上面的代碼可以看出,在每次訪問該域字段時,均需要承擔同步的開銷。如果在真實的應(yīng)用中,在多線程環(huán)境下,我們確實需要為一個實例化開銷很大的對象實行延遲初始化,又該如何做呢?該條目提供了3中技巧:
? ? ? 1. 對于靜態(tài)域字段,可以考慮使用延遲初始化Holder class模式:

1 ???? public class TestClass {
2 ???????? private static class FieldHolder {
3 ???????????? static final FieldType field = computeFieldValue();
4 ???????? }
5 ???????? static FieldType getField() {
6 ???????????? return FieldHolder.field;
7 ???????? }
8 ???? }
?

?? ?? 當getField()方法第一次被調(diào)用時,它第一次讀取FieldHolder.field,導(dǎo)致FieldHolder類得到初始化。這種模式的魅力在于,getField方法沒有被同步,并且只執(zhí)行一個域訪問,因此延遲初始化實際上并沒有增加任何訪問成本。現(xiàn)在的VM將在初始化該類的時候,同步域的訪問。一旦這個類被初始化,VM將修補代碼,以便后續(xù)對該域的訪問不會導(dǎo)致任何測試或者同步。
? ? ? 2. 對于實例域字段,可使用雙重檢查模式:

1 ???? public class TestClass {
2 ???????? private volatile FieldType f;
3 ???????? FieldType getField() {
4 ???????????? FieldType result = f;
5 ???????????? if (result == null ) {
6 ???????????????? synchronized ( this ) {
7 ???????????????????? result = f;
8 ???????????????????? if (result == null )
9 ???????????????????????? f = result = computeFieldValue();
10 ???????????????? }
11 ???????????? }
12 ???????????? return result;
13 ???????? }
14 ???? }
?

????? 注意在上面的代碼中,首先將域字段f聲明為volatile變量,其語義在之前的條目中已經(jīng)給出解釋,這里將不再贅述。再者就是在進入同步塊之前,先針對該字段進行驗證,如果不是null,即已經(jīng)初始化,就直接返回該域字段,從而避免了不必要的同步開銷。然而需要明確的是,在同步塊內(nèi)部的判斷極其重要,因為在第一次判斷之后和進入同步代碼塊之前存在一個時間窗口,而這一窗口則很有可能造成不同步的錯誤發(fā)生,因此第二次驗證才是決定性的。
? ? ? 在該示例代碼中,使用局部變量result代替volatile的域字段,可以避免在后面的訪問中每次都從主存中獲取數(shù)據(jù),從而提高函數(shù)的運行性能。事實上,這只是一種代碼優(yōu)化的技巧而已。
?? ?? 針對該技巧,最后需要補充的是,在很多并發(fā)程序中,對某一狀態(tài)的測試,也可以使用該技巧。
?? ?? 3. 對于可以接受重復(fù)初始化實例域字段,可使用單重檢查模式:

1 ???? public class TestClass {
2 ???????? private volatile FieldType f;
3 ???????? FieldType getField() {
4 ???????????? FieldType result = f;
5 ???????????? if (result == null )
6 ???????????????? f = result = computeFieldValue();
7 ???????????? return result;
8 ???????? }
9 ???? }?

Effective Java (并發(fā))


更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主

微信掃碼或搜索:z360901061

微信掃一掃加我為好友

QQ號聯(lián)系: 360901061

您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。

【本文對您有幫助就好】

您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描上面二維碼支持博主2元、5元、10元、自定義金額等您想捐的金額吧,站長會非常 感謝您的哦!!!

發(fā)表我的評論
最新評論 總共0條評論
主站蜘蛛池模板: 国产高清久久99 | 99久久99热精品免费观看国产 | 成人精品免费视频 | 天天看片日日夜夜 | 五月天亚洲婷婷 | 一级成人a免费视频 | 999久久久国产 | 91视频免费入口 | 精品视频在线免费观看 | 香蕉视频在线看 | 狠狠色丁香婷婷综合欧美 | 亚洲激情网 | 免费的毛片视频 | 毛片免费观看日本中文 | 欧美中文字幕 | 国产精品成人一区二区1 | 伊人免费视频 | 免费一级毛片在线播放视频 | 国产一区二区三区久久精品 | 亚洲欧美激情精品一区二区 | 色激情综合网 | 国产美女a做受大片在线观看 | 日韩精品首页 | 日韩视频在线观看一区二区 | 亚洲国产精品久久久久网站 | 99久久www免费人成精品 | 天天摸天天操天天爽 | 高清亚洲| 日本欧美强乱视频在线 | 久揄揄鲁一二三四区高清在线 | 亚洲欧美在线观看91偷拍 | 国产一区精品在线 | 亚1洲二区三区四区免费 | 久草视频资源在线 | 中文字幕在线精品视频入口一区 | 精品午夜国产在线观看不卡 | 看全色黄大色大片免费久久久 | 国产草草 | 99久久精品免费看国产一区二区 | 亚洲 中文 欧美 日韩 在线人 | 国产精品自拍视频 |