一区二区三区日韩精品-日韩经典一区二区三区-五月激情综合丁香婷婷-欧美精品中文字幕专区

分享

Java Threads 多線程10分鐘參考手冊(cè)

 昵稱7905119 2011-10-12

Java Threads 多線程10分鐘參考手冊(cè)

分類: Java 1320人閱讀 評(píng)論(6) 收藏 舉報(bào)

1         同步

                如何同步多個(gè)線程對(duì)共享資源的訪問是多線程編程中最基本的問題之一。當(dāng)多個(gè)線程并發(fā)訪問共享數(shù)據(jù)時(shí)會(huì)出現(xiàn)數(shù)據(jù)處于計(jì)算中間狀態(tài)或者不一致的問題,從而影響到程序的正確運(yùn)行。我們通常把這種情況叫做競(jìng)爭(zhēng)條件(race condition),把并發(fā)訪問共享數(shù)據(jù)的代碼叫做關(guān)鍵區(qū)域(critical section)。同步就是使得多個(gè)線程順序進(jìn)入關(guān)鍵區(qū)域從而避免競(jìng)爭(zhēng)條件的發(fā)生。

1.1       Synchronized關(guān)鍵字

               Synchronized是Java多線程編程中最常用的關(guān)鍵字。所有的Java 對(duì)象都有自己唯一的隱式同步鎖。該鎖只能同時(shí)被一個(gè)線程獲得,其他試圖獲得該鎖的線程都會(huì)被阻塞在對(duì)象的等待隊(duì)列中直到獲得該鎖的線程釋放鎖才能繼續(xù)工作。Synchronized關(guān)鍵字通常有兩種用法。當(dāng)Synchronized關(guān)鍵字用于類方法定義中時(shí),表示所有調(diào)用該方法的線程都必須獲得當(dāng)前對(duì)象的鎖。這種方式比較簡(jiǎn)單,但是同步的粒度比較大,當(dāng)一個(gè)線程要執(zhí)行某個(gè)對(duì)象的同步方法的時(shí)候,必須同時(shí)沒有任何其他線程在執(zhí)行該對(duì)象的任一同步方法。此外,同步方法中的所有代碼均在同步塊中,獲得鎖的線程必須在執(zhí)行完所有的代碼離開該方法后才會(huì)釋放鎖,這些代碼中可能只有一部分涉及到對(duì)共享資源(例如成員變量)的訪問需要同步,其余則不需要,那么這樣粗粒度的同步顯然增加了其他線程的等待時(shí)間。Synchronized的另一種 用法允許作用在某個(gè)對(duì)象上,并且只同步一段代碼而不是整個(gè)方法。

 

synchronized (object)  {

 // 需要同步的代碼

}

 

這里synchronized所作用的對(duì)象可以是類的某個(gè)成員變量,也可以是這個(gè)類對(duì)象(用this表示)。這種用法使得程序員可以根據(jù)需要同步不同的成員變量,而不總是當(dāng)前類對(duì)象,提高了靈活性。

 值得一提的是,并不是只有對(duì)象才有鎖,類本身也有自己的鎖,這使得static方法同樣可以用synchronized來修飾。訪問同步static方法的線程需要獲得類的同步鎖才能繼續(xù)執(zhí)行。

1.2       Volatile關(guān)鍵字

                在Java內(nèi)存模型中每個(gè)線程擁有自己的本地存儲(chǔ)(例如寄存器),并且允許線程擁有變量值的拷貝。這使得本來不需要同步的一些原子操作,例如boolean成員變量存儲(chǔ)和讀取也變得不安全。設(shè)想我們有個(gè)叫做done的boolean成員變量和一個(gè)當(dāng)done為true時(shí)才會(huì)停止的循環(huán),該循環(huán)由后臺(tái)線程執(zhí)行,另一個(gè)UI線程等待用戶輸入,用戶按下某個(gè)按鈕以后會(huì)把done設(shè)成true從而終止循環(huán)。由于UI線程自己本地?fù)碛衐one的拷貝,用戶在按下按鈕時(shí)只是把自己本地的done設(shè)成了true而沒有及時(shí)更新主內(nèi)存中的done,所以后臺(tái)線程由于看不到done的改變而不會(huì)終止。即使主內(nèi)存中的done變化了,后臺(tái)線程也會(huì)因?yàn)樽约罕镜氐淖兞恐禌]有及時(shí)更新而沒有察覺到done的變化。解決這一問題的方法之一是為done提供synchronized的setter和getter方法,這是因?yàn)楂@得同步鎖會(huì)迫使所有變量的值從臨時(shí)存儲(chǔ)(寄存器)寫會(huì)主內(nèi)存。除此之外,Java提供了一個(gè)解決這個(gè)問題更為優(yōu)雅的方法:Volatile關(guān)鍵字。每次使用volatile變量,JVM都會(huì)保證從主內(nèi)存中讀取它的值;同樣每次修改volatile變量,JVM都會(huì)把值寫回到主內(nèi)存中。

                Volatile適用的場(chǎng)景比較嚴(yán)格,必須很清楚地看到volatile只是告訴JVM對(duì)于該變量的讀寫必須每次都在主內(nèi)存中進(jìn)行而禁止使用臨時(shí)的拷貝來優(yōu)化,它只是出于JVM特殊的內(nèi)存模型的需要,并沒有同步的功能。因此只有對(duì)volatile變量進(jìn)行的原子操作(讀取和賦值)才是線程安全的,像自增++自減--這樣包含多個(gè)命令的操作仍然需要其它的同步措施。

                另一個(gè)需要注意的的地方是當(dāng)用volatile修飾數(shù)組的時(shí)候,它只是說數(shù)組的引用是volatile的,而數(shù)組中的元素還是和普通變量一樣,可能被JVM優(yōu)化,我們無法為數(shù)組中的元素加上volatile修飾。解決上述問題的方法是使用Atomic變量。作為使用volatile修飾數(shù)組的一個(gè)例子,可以參考java.util.concurrent.CopyOnWriteArrayList。它的add操作是通過復(fù)制原來的數(shù)組并把新元素添加到新數(shù)組末尾然后再把內(nèi)部數(shù)組引用變量指向新數(shù)組來實(shí)現(xiàn)的,因此數(shù)組變量經(jīng)常會(huì)被修改,需要使用volatile。

1.3       顯式鎖Lock

                盡管synchronized關(guān)鍵字可以解決大多數(shù)同步問題,J2SE5.0還是引入了Lock接口。相比使用synchronized關(guān)鍵字獲取對(duì)象隱式的同步鎖,我們稱Lock為顯式鎖。使用顯式鎖的一個(gè)顯而易見的好處是它不再屬于某個(gè)對(duì)象,從而可以在多個(gè)對(duì)象之間共享它。Lock接口有l(wèi)ock()和unlock()兩個(gè)方法,使用它們和使用synchronized關(guān)鍵字類似,在進(jìn)入需要同步的代碼之前調(diào)用lock,在離開同步代碼塊時(shí)調(diào)用unlock。通常unlock會(huì)被放在finally中以保證即使同步代碼塊中有異常發(fā)生,鎖仍然可以被釋放。

                和使用synchronized關(guān)鍵字和lock()方法總是把未能獲得鎖的線程阻塞不同,Lock接口還提供了非阻塞的tryLock()方法。調(diào)用tryLock方法的線程如果未能獲得鎖會(huì)立刻返回false,線程可以繼續(xù)執(zhí)行其他代碼而避免等待,這為程序員提供了更多自由。

                Lock接口還提供了一個(gè)newCondition () 方法,它返回一個(gè)Condition對(duì)象。Condition對(duì)象的作用和Object用于線程通知的wait-notify機(jī)制相同。

1.4       信號(hào)量Semaphore

                有時(shí)候我們有多個(gè)相同的共享資源可以同時(shí)被多個(gè)線程使用。我們希望在鎖的基礎(chǔ)上加上一個(gè)計(jì)數(shù)器,根據(jù)資源的個(gè)數(shù)來初始化這個(gè)計(jì)數(shù)器,每次成功的lock操作都會(huì)使計(jì)數(shù)器的值減去1,只要計(jì)數(shù)器的值不為零就表示還有資源可以使用,lock操作就能成功。每次unlock操作都會(huì)給這個(gè)計(jì)數(shù)器加1。只有當(dāng)計(jì)數(shù)器的值為0的時(shí)候lock操作才會(huì)阻塞當(dāng)前線程。這就是Java中的信號(hào)量Semaphore。

                Semaphore類提供的方法和Lock接口非常類似,當(dāng)把信號(hào)量的資源個(gè)數(shù)設(shè)置成1時(shí),信號(hào)量就退化為普通的鎖。

1.5       讀寫鎖ReadWriteLock

                對(duì)共享資源的訪問通??梢苑譃樽x取和寫入。在有些應(yīng)用場(chǎng)景中讀取可能需要花費(fèi)較長(zhǎng)時(shí)間,我們需要使用互斥鎖來阻止并發(fā)的寫入操作以保證數(shù)據(jù)的一致性。但是對(duì)于并發(fā)的讀取線程其實(shí)并不需要使用同步。事實(shí)上只有使數(shù)據(jù)發(fā)生變化的操作才需要同步,我們希望有一種方法可以把讀取和寫入?yún)^(qū)分開來,讀取和寫入的操作之間是互斥的,但是多個(gè)讀取操作可以同時(shí)進(jìn)行,這樣可以有效提高讀取密集型程序的性能。J2SE5.0提供了ReadWriteLock接口并提供了實(shí)現(xiàn)該接口的ReentrantReadWriteLock類:

 

public interface ReadWriteLock {

    Lock readLock();

    Lock writeLock();

}

 

從接口方法中不難看出讀寫鎖中包含讀鎖和寫鎖。實(shí)現(xiàn)類ReentrantReadWriteLock為我們提供了更多便捷的方法來使用讀寫鎖,例如isWriteLocked可以用來檢測(cè)是否被寫鎖定。

 

2         線程通知

                除了同步鎖,Java Object還有兩個(gè)可用于線程間通知的同步方法wait和notify。調(diào)用對(duì)象wait方法的線程會(huì)被阻塞在該對(duì)象的等待隊(duì)列中直到其他線程調(diào)用notify方法來喚醒它。每次notify調(diào)用只能喚醒一個(gè)在等待隊(duì)列中的線程,notifyAll方法可以喚醒所有在該對(duì)象等待隊(duì)列中的線程。

 

3         最小化同步

                線程同步通過讓線程順序進(jìn)入同步代碼塊解決了多個(gè)線程競(jìng)爭(zhēng)同一資源而引起的不確定性,但是犧牲了效率,因此為了取得更好地性能,我們需要盡可能少地使用同步。事實(shí)上并不是所有的競(jìng)爭(zhēng)條件都是需要避免的,只有當(dāng)競(jìng)爭(zhēng)條件出現(xiàn)在非線程安全的代碼段時(shí)才會(huì)引起問題。

3.1       Atomic 變量

           如果一個(gè)操作是原子操作,例如給一個(gè)boolean 變量賦值,我們就不需要同步。Java提供了一些Atomic類,使得一些本來不是原子操作(例如自增操作 ++,它包含了取值、加1、賦值三個(gè)原子操作)也能夠原子執(zhí)行,從而不需要使用同步。

                Java提供了4個(gè)基本的原子類,AtomicInteger, AtomicLong, AtomicBoolean和AtomicReference分別提供針對(duì)int,long,boolean,object的原子操作。有意思的是如果你打開JDK的源代碼想看看這些原子操作是如何實(shí)現(xiàn)的,你會(huì)失望地發(fā)現(xiàn)代碼里面沒有使用任何同步或其它技術(shù)。如果你在自己的程序中寫下同樣地代碼,那么它們并不是原子的。

3.2       Thread Local 變量

                如果每個(gè)線程都有自己私有的成員變量,那么我們也不需要同步。ThreadLocal就是線程的私有變量,每個(gè)使用ThreadLocal變量的線程都會(huì)有自己獨(dú)立的ThreadLocal對(duì)象,因此就不存在多個(gè)線程訪問同一個(gè)變量的問題。當(dāng)然由于ThreadLocal變量為線程私有,它也就不可以用于在多個(gè)線程間共享狀態(tài)。

                ThreadLocal類并不神秘,它的實(shí)現(xiàn)原理比較簡(jiǎn)單:每個(gè)Thread對(duì)象有自己用來存儲(chǔ)私有ThreadLocal對(duì)象的容器ThreadLocalMap,當(dāng)某個(gè)線程調(diào)用ThreadLocal對(duì)象的get()方法來 取值的時(shí)候,get方法首先會(huì)取得當(dāng)前線程對(duì)象,然后取出該線程的ThreadLocalMap,然后檢查自己是否已經(jīng)在map中,如果自己已經(jīng)存在,直接返回map中的value。如果不存在,把自己作key并初始化一個(gè)value加入到當(dāng)前線程的map中。

 

    public T get() {

        Thread t = Thread.currentThread();

        ThreadLocalMap map = getMap(t);

        if (map != null) {

            ThreadLocalMap.Entry e = map.getEntry(this);

            if (e != null)

                return (T)e.value;

        }

        return setInitialValue();

}

 

4         線程池Thread Pool

                線程雖然不像進(jìn)程需要那么多資源,但是它的創(chuàng)建也是有一定開銷的,頻繁地創(chuàng)建和銷毀線程會(huì)降低程序的性能;此外應(yīng)用程序可以創(chuàng)建線程的數(shù)量是受機(jī)器物理?xiàng)l件制約的,過多的線程會(huì)耗盡機(jī)器的資源,因此我們?cè)谠O(shè)計(jì)程序的時(shí)候需要限制并發(fā)線程的數(shù)量。解決這兩個(gè)問題的通常做法是使用線程池。線程池在啟動(dòng)的時(shí)候一次性初始化若干個(gè)線程(也可以根據(jù)負(fù)載按需啟動(dòng),也有閑置一定時(shí)間的線程會(huì)被銷毀的策略),然后程序把任務(wù)交給線程池去執(zhí)行而不是直接交給某個(gè)線程執(zhí)行,由線程池給這些任務(wù)分配線程。當(dāng)某個(gè)線程執(zhí)行完一個(gè)任務(wù)后,線程池會(huì)把它設(shè)成空閑狀態(tài)以備下一個(gè)任務(wù)重用而不是銷毀它。線程池在初始化的時(shí)候需要指定線程數(shù)量上限,當(dāng)并發(fā)任務(wù)數(shù)量超過線程數(shù)量的時(shí)候,線程池不會(huì)再創(chuàng)建新的線程而是讓新任務(wù)等待,這樣我們就不在需要擔(dān)心線程數(shù)量過多耗盡系統(tǒng)資源了。JDK1.5開始為我們提供了標(biāo)準(zhǔn)的線程池。

4.1       執(zhí)行器Executor

                Java的線程池實(shí)現(xiàn)了以下Executor接口:

 

public interface Executor {

    void execute(Runnable command);

}

               

                在多線程編程中,執(zhí)行器是一種常用的設(shè)計(jì)模式,它的好處在于提供了一種簡(jiǎn)單有效的編程模型,我們只需把需要并發(fā)處理的工作拆分成獨(dú)立的任務(wù),然后交給執(zhí)行器去執(zhí)行即可而不必關(guān)心線程的創(chuàng)建,分配和調(diào)度。J2SE5.0主要提供了兩種功能的執(zhí)行器:ThreadPoolExecutor和ScheduledThreadPoolExecutor。ThreadPoolExecutor是基本的線程池實(shí)現(xiàn),ScheduledThreadPoolExecutor在前者基礎(chǔ)上增加了任務(wù)調(diào)度的功能,在把任務(wù)交給它時(shí)我們可以指定任務(wù)的執(zhí)行時(shí)間,而不是立刻執(zhí)行。

                java.util.concurrent.Executors是用來創(chuàng)建線程池的工廠類,通過它提供的工廠方法,我們可以方便地創(chuàng)建不同特性的線程池。

4.2       Future接口

                Executor接口并沒有看起來那么理想,有時(shí)候我們執(zhí)行一個(gè)任務(wù)是要得到計(jì)算的結(jié)果,有時(shí)候我們需要對(duì)任務(wù)有更多控制,例如知道它是否完成,或者中途終止它。返回void的execute方法并不能滿足我們這些需求。當(dāng)然我們可以在傳入的Runnable類上下功夫來提供類似的功能,但是這樣做繁瑣且容易出錯(cuò)。既然J2SE為我們提供了線程池的標(biāo)準(zhǔn)實(shí)現(xiàn)把我們從多線程編程中解放出來,這些常見的需求當(dāng)然也會(huì)很好地滿足。事實(shí)上線程池實(shí)現(xiàn)了一個(gè)更為豐富的ExecutorService接口,它定義了執(zhí)行任務(wù)并返回代表該任務(wù)的Future對(duì)象的submit方法。

           通過Future接口,我們可以查看已經(jīng)被提交給線程池執(zhí)行的任務(wù)是否完成,獲取執(zhí)行的結(jié)果或者終止任務(wù)。

4.3       Runnable 和Callable 接口

                實(shí)現(xiàn)了Runnable或Callable接口的類都可以作為任務(wù)提交給線程池執(zhí)行,這兩個(gè)接口的主要區(qū)別在于Callable的call方法有結(jié)果返回并且可以拋出異常而Runnable的run方法返回void且不允許有可檢查的異常拋出(只能拋runtime exception)。因此如果我們的任務(wù)執(zhí)行后有結(jié)果返回,應(yīng)該使用Callable接口。

 

5         線程和集合類

5.1       線程安全的集合類

  • java.util.Vector
  • java.util.Stack
  • java.util.HashTable
  • java.util.concurrent.ConcurrentHashMap
  • java.util.concurrent.CopyOnWriteArrayList
  • java.util.concurrent.CopyOnWriteArraySet
  • java.util.concurrent.ConcurrentLinkedQueue

5.2       非線程安全集合類

  • java.util.BitSet
  • java.util.HashSet (LinkedHashSet)
  • java.util.TreeSet
  • java.util.HashMap (WeekHashMap, TreeMap, LinkedHashMap, IdentityHashMap)
  • java.util.ArrayList (LinkedList)
  • java.util.PriorityQueue

                這些非線程安全的集合可以通過java.util.Collections.SynchronizedList、SynchronizedMap、SynchronizedSet等方法包裝成線程安全的集合。包裝器類簡(jiǎn)單地給被包裝集合的各項(xiàng)操作加上了synchronized保護(hù)。值得注意的是在使用游標(biāo)遍歷這些包裝器集合的時(shí)候必須加上額外的synchronized保護(hù),否則會(huì)出現(xiàn)問題。

     List list = Collections.synchronizedList(new ArrayList());

         ...

     synchronized(list) {

         Iterator i = list.iterator(); // Must be in synchronized block

         while (i.hasNext())

             foo(i.next());

     }

5.3       線程通知集合類

  • java.util.concurrent.ArrayBlockingQueue
  • java.util.concurrent.LinkedBlockingQueue
  • java.util.concurrent.SynchronousQueue
  • java.util.concurrent.PriorityBlockingQueue
  • java.util.concurrent.DelayQueue

                這些集合類都實(shí)現(xiàn)了BlockingQueue接口。阻塞隊(duì)列的特點(diǎn)是當(dāng)從隊(duì)列中取出元素時(shí)如果隊(duì)列為空,線程會(huì)被阻塞直到隊(duì)列中有元素被插入。當(dāng)從隊(duì)列中插入元素時(shí)如果隊(duì)列已滿,線程會(huì)被阻塞直到隊(duì)列中有元素被取出出現(xiàn)空閑空間。阻塞隊(duì)列可以用來實(shí)現(xiàn)生產(chǎn)者消費(fèi)者模式(Producer/Consumer Pattern) 。

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多

    亚洲国产香蕉视频在线观看| 日韩特级黄片免费观看| 亚洲精品一区二区三区免 | 深夜视频在线观看免费你懂| 人妻少妇系列中文字幕| 91亚洲国产成人久久| 久久永久免费一区二区| 一本色道久久综合狠狠躁| av中文字幕一区二区三区在线 | 初尝人妻少妇中文字幕在线| 国产精品免费视频视频| 国产一区欧美一区日本道| 白白操白白在线免费观看| 草草草草在线观看视频| 久久综合日韩精品免费观看| 日本女优一区二区三区免费| 中文字幕人妻日本一区二区 | 亚洲精品一区三区三区| 亚洲品质一区二区三区| 精品丝袜一区二区三区性色| 国产老熟女乱子人伦视频| 国产av精品一区二区| 日韩在线精品视频观看| 午夜精品久久久免费视频| 国产内射在线激情一区| 麻豆果冻传媒一二三区| 日本黄色录像韩国黄色录像| 丰满熟女少妇一区二区三区 | 九九热精品视频免费在线播放| 91播色在线免费播放| 欧美一级内射一色桃子| 日本少妇中文字幕不卡视频| 欧美国产日韩变态另类在线看 | 亚洲国产中文字幕在线观看| 午夜视频免费观看成人| 国产极品粉嫩尤物一区二区| 欧美二区视频在线观看| 丝袜视频日本成人午夜视频| 日韩特级黄色大片在线观看| 69精品一区二区蜜桃视频| 日本久久精品在线观看|