同步锁ReentrantLock
生活随笔
收集整理的這篇文章主要介紹了
同步锁ReentrantLock
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
http://www.cnblogs.com/hoojo/archive/2011/05/05/2038101.html
Java Thread 多線程同步、鎖、通信
線程同步、同步鎖、死鎖
線程通信
線程組和未處理異常
Callable和Future
12、線程同步 當多個線程訪問同一個數據時,非常容易出現線程安全問題。這時候就需要用線程同步 Case:銀行取錢問題,有以下步驟: A、用戶輸入賬戶、密碼,系統判斷是否登錄成功 B、用戶輸入取款金額 C、系統判斷取款金額是否大于現有金額 D、如果金額大于取款金額,就成功,否則提示小于余額 ? 現在模擬2個人同時對一個賬戶取款,多線程操作就會出現問題。這時候需要同步才行; 同步代碼塊: synchronized (object) { //同步代碼 } Java多線程支持方法同步,方法同步只需用用synchronized來修飾方法即可,那么這個方法就是同步方法了。 對于同步方法而言,無需顯示指定同步監視器,同步方法監視器就是本身this 同步方法: public synchronized void editByThread() { //doSomething } ? 需要用同步方法的類具有以下特征: A、該類的對象可以被多個線程訪問 B、每個線程調用對象的任意都可以正常的結束,返回正常結果 C、每個線程調用對象的任意方法后,該對象狀態保持合理狀態 不可變類總是線程安全的,因為它的對象狀態是不可改變的,但可變類對象需要額外的方法來保證線程安全。 例如Account就是一個可變類,它的money就是可變的,當2個線程同時修改money時,程序就會出現異常或錯誤。 所以要對Account設置為線程安全的,那么就需要用到同步synchronized關鍵字。 下面的方法用synchronized同步關鍵字修飾,那么這個方法就是一個同步的方法。這樣就只能有一個線程可以訪問這個方法, 在當前線程調用這個方法時,此方法是被鎖狀態,同步監視器是this。只有當此方法修改完畢后其他線程才能調用此方法。 這樣就可以保證線程的安全,處理多線程并發取錢的的安全問題。 public synchronized void drawMoney(double money) { //取錢操作 } 注意:synchronized可以修飾方法、代碼塊,但不能修飾屬性、構造方法 可變類的線程安全是以降低程序的運行效率為代價,為了減少線程安全所帶來的負面影響,可以采用以下策略: A、不要對線程安全類的所有方法都采用同步模式,只對那些會改變競爭資源(共享資源)的方法進行同步。 B、如果可變類有2中運行環境:單線程環境和多線程環境,則應該為該可變提供2種版本;線程安全的和非線程安全的版本。 在單線程下采用非線程安全的提高運行效率保證性能,在多線程環境下采用線程安全的控制安全性問題。 釋放同步監視器的鎖定 任何線程進入同步代碼塊、同步方法之前,必須先獲得對同步監視器的鎖定,那么何時會釋放對同步監視器鎖定? 程序無法顯示的釋放對同步監視器的鎖定,線程可以通過以下方式釋放鎖定: A、當線程的同步方法、同步代碼庫執行結束,就可以釋放同步監視器 B、當線程在同步代碼庫、方法中遇到break、return終止代碼的運行,也可釋放 C、當線程在同步代碼庫、同步方法中遇到未處理的Error、Exception,導致該代碼結束也可釋放同步監視器 D、當線程在同步代碼庫、同步方法中,程序執行了同步監視器對象的wait方法,導致方法暫停,釋放同步監視器 ? 下面情況不會釋放同步監視器: A、當線程在執行同步代碼庫、同步方法時,程序調用了Thread.sleep()/Thread.yield()方法來暫停當前程序,當前程序不會釋放同步監視器 B、當線程在執行同步代碼庫、同步方法時,其他線程調用了該線程的suspend方法將該線程掛起,該線程不會釋放同步監視器。注意盡量避免使用suspend、resume 同步鎖(Lock) 通常認為:Lock提供了比synchronized方法和synchronized代碼塊更廣泛的鎖定操作,Lock更靈活的結構,有很大的差別,并且可以支持多個Condition對象 Lock是控制多個線程對共享資源進行訪問的工具。通常,鎖提供了對共享資源的獨占訪問,每次只能有一個線程對Lock對象加鎖, 線程開始訪問共享資源之前應先獲得Lock對象。不過某些鎖支持共享資源的并發訪問,如:ReadWriteLock(讀寫鎖),在線程安全控制中, 通常使用ReentrantLock(可重入鎖)。使用該Lock對象可以顯示加鎖、釋放鎖。 class C { //鎖對象 private final ReentrantLock lock = new ReentrantLock(); ...... //保證線程安全方法 public void method() { //上鎖 lock.lock(); try { //保證線程安全操作代碼 } catch() { } finally { lock.unlock();//釋放鎖 } } } 使用Lock對象進行同步時,鎖定和釋放鎖時注意把釋放鎖放在finally中保證一定能夠執行。 使用鎖和使用同步很類似,只是使用Lock時顯示的調用lock方法來同步。而使用同步方法synchronized時系統會隱式使用當前對象作為同步監視器, 同樣都是“加鎖->訪問->釋放鎖”的操作模式,都可以保證只能有一個線程操作資源。 同步方法和同步代碼塊使用與競爭資源相關的、隱式的同步監視器,并且強制要求加鎖和釋放鎖要出現在一個塊結構中,而且獲得多個鎖時, 它們必須以相反的順序釋放,且必須在與所有鎖被獲取時相同的范圍內釋放所有資源。 Lock提供了同步方法和同步代碼庫沒有的其他功能,包括用于非塊結構的tryLock方法,已經試圖獲取可中斷鎖lockInterruptibly()方法, 還有獲取超時失效鎖的tryLock(long, timeUnit)方法。 ReentrantLock具有重入性,也就是說線程可以對它已經加鎖的ReentrantLock再次加鎖,ReentrantLock對象會維持一個計數器來追蹤lock方法的嵌套調用, 線程在每次調用lock()加鎖后,必須顯示的調用unlock()來釋放鎖,所以一段被保護的代碼可以調用另一個被相同鎖保護的方法。 死鎖 當2個線程相互等待對方是否同步監視器時就會發生死鎖,JVM沒有采取處理死鎖的措施,這需要我們自己處理或避免死鎖。 一旦死鎖,整個程序既不會出現異常,也不會出現錯誤和提示,只是線程將處于阻塞狀態,無法繼續。 主線程保持對Foo的鎖定,等待對Bar對象加鎖,而副線程卻對Bar對象保持鎖定,等待對Foo加鎖2條線程相互等待對方先釋放鎖,進入死鎖狀態。 由于Thread類的suspend也很容易導致死鎖,所以Java不推薦使用此方法暫停線程。 13、線程通信 (1)、線程的協調運行 場景:用2個線程,這2個線程分別代表存款和取款。——現在系統要求存款者和取款者不斷重復的存款和取款的動作, 而且每當存款者將錢存入賬戶后,取款者立即取出這筆錢。不允許2次連續存款、2次連續取款。 實現上述場景需要用到Object類,提供的wait、notify和notifyAll三個方法,這3個方法并不屬于Thread類。但這3個方法必須由同步監視器調用,可分為2種情況: A、對于使用synchronized修飾的同步方法,因為該類的默認實例this就是同步監視器,所以可以在同步中直接調用這3個方法。 B、對于使用synchronized修改的同步代碼塊,同步監視器是synchronized后可括號中的對象,所以必須使用括號中的對象調用這3個方法 方法概述: 一、wait方法:導致當前線程進入等待,直到其他線程調用該同步監視器的notify方法或notifyAll方法來喚醒該線程。 wait方法有3中形式:無參數的wait方法,會一直等待,直到其他線程通知;帶毫秒參數的wait和微妙參數的wait, 這2種形式都是等待時間到達后蘇醒。調用wait方法的當前線程會釋放對該對象同步監視器的鎖定。 二、notify:喚醒在此同步監視器上等待的單個線程。如果所有線程都在此同步監視器上等待,則會隨機選擇喚醒其中一個線程。 只有當前線程放棄對該同步監視器的鎖定后(用wait方法),才可以執行被喚醒的線程。 三、notifyAll:喚醒在此同步監視器上等待的所有線程。只有當前線程放棄對該同步監視器的鎖定后,才能執行喚醒的線程。 (2)、條件變量控制協調 如果程序不使用synchronized關鍵字來保證同步,而是直接使用Lock對象來保證同步,則系統中不存在隱式的同步監視器對象, 也不能使用wait、notify、notifyAll方法來協調進程的運行。 當使用Lock對象同步,Java提供一個Condition類來保持協調,使用Condition可以讓那些已經得到Lock對象卻無法組合使用, 為每個對象提供了多個等待集(wait-set),這種情況下,Lock替代了同步方法和同步代碼塊,Condition替代同步監視器的功能。 Condition實例實質上被綁定在一個Lock對象上,要獲得特定的Lock實例的Condition實例,調用Lock對象的newCondition即可。 Condition類方法介紹: 一、await:類似于隱式同步監視器上的wait方法,導致當前程序等待,直到其他線程調用Condition的signal方法和signalAll方法來喚醒該線程。 該await方法有跟多獲取變體:long awaitNanos(long nanosTimeout),void awaitUninterruptibly()、awaitUntil(Date daadline) 二、signal:喚醒在此Lock對象上等待的單個線程,如果所有的線程都在該Lock對象上等待,則會選擇隨機喚醒其中一個線程。 只有當前線程放棄對該Lock對象的鎖定后,使用await方法,才可以喚醒在執行的線程。 三、signalAll:喚醒在此Lock對象上等待的所有線程。只有當前線程放棄對該Lock對象的鎖定后,才可以執行被喚醒的線程。 (3)、使用管道流 線程通信使用管道流,管道流有3種形式: PipedInputStream、PipedOutputStream、PipedReader和PipedWriter以及Pipe.SinkChannel和Pipe.SourceChannel, 它們分別是管道流的字節流、管道字符流和新IO的管道Channel。 管道流通信基本步驟: A、使用new操作法來創建管道輸入、輸出流 B、使用管道輸入流、輸出流的connect方法把2個輸入、輸出流連接起來 C、將管道輸入、輸出流分別傳入2個線程 D、2個線程可以分別依賴各自的管道輸入流、管道輸出流進行通信 14、線程組和未處理異常 ThreadGroup表示線程組,它可以表示一批線程進行分類管理,Java允許程序對 Java允許直接對線程組控制,對線程組控制相對于同時控制這批線程。用戶創建的所有線程都屬于指定的線程組。 如果程序沒有值得線程屬于哪個組,那這個線程就屬于默認線程組。在默認情況下,子線程和創建它父線程屬于同一組。 一旦某個線程加入了指定線程組之后,該線程將屬于該線程組,直到該線程死亡,線程運行中途不能改變它所屬的線程組。 Thread類提供一些構造設置線程所屬的哪個組,具有以下方法: A、Thread(ThreadGroup group, Runnable target):target的run方法作為線程執行體創建新線程,屬于group線程組 B、Thread(ThreadGroup group, Runnalbe target, String name):target的run方法作為線程執行體創建的新線程,該線程屬于group線程組,且線程名為name C、Thread(ThreadGroup group, String name):創建新線程,新線程名為name,屬于group組 ? 因為中途不能改變線程所屬的組,所以Thread提供ThreadGroup的setter方法,但提供了getThreadGroup方法來返回該線程所屬的線程組, getThreadGroup方法的返回值是ThreadGroup對象的表示,表示一個線程組。 ThreadGroup有2個構造形式: A、ThreadGroup(String name):name線程組的名稱 B、ThreadGroup(ThreadGroup parent, String name):指定名稱、指定父線程組創建的一個新線程組 ? 上面的構造都指定線程名稱,也就是線程組都必須有自己的一個名稱,可以通過調用ThreadGroup的getName方法得到, 但不允許中途改變名稱。ThreadGroup有以下常用的方法: A、activeCount:返回線程組活動線程數目 B、interrupt:中斷此線程組中的所有線程 C、isDeamon:判斷該線程是否在后臺運行 D、setDeamon:把該線程組設置為后臺線程組,后臺線程具有一個特征,當后臺線程的最后一個線程執行結束或最后一個線程被銷毀,后臺線程組自動銷毀。 E、setMaxPriority:設置線程組最高優先級 uncaughtException(Thread t, Throwable e)該方法可以處理該線程組內的線程所拋出的未處理的異常, Thread.UncaughtExceptionHandler是Thread類的一個內部公共靜態接口, 該接口內只有一個方法:void uncaughtException(Thread t, Throwable e) 該方法中的t代表出現異常的線程,而e代表該線程拋出的異常 Thread類中提供2個方法來設置異常處理器: A、staticsetDefaultUnaughtExceptionHandler(Thread.UncaughtExceptionHandler eh):為該線程類的所有線程實例設置默認的異常處理器 B、setUncaughtExceptionHandler(Thread.UncaughtExceptionHander eh):為指導線程實例設置異常處理器 ? ThreadGroup實現了Thread.UncaughtExceptionHandler接口,所以每個線程所屬的線程組將會作為默認的異常處理器。當一個線程拋出未處理異常時, JVM會首先查找該異常對應的異常處理器,(setUncaughtExceptionHandler設置異常處理器),如果找到該異常處理器,將調用該異常處理器處理異常。 否則,JVM將會調用該線程的所屬線程組的uncaughtException處理異常,線程組處理異常流程如下: A、如果該線程有父線程組,則調用父線程組的uncaughtException方法來處理異常 B、如果該線程實例所屬的線程類有默認的異常處理器(setDefaultUnaughtExceptionHandler方法設置異常處理器),那就調用該異常處理器來處理異常信息 C、將異常調用棧的信息打印到System.err錯誤輸出流,并結束該線程 ? 15、Callable和Future Callable接口定義了一個call方法可以作為線程的執行體,但call方法比run方法更強大: A、call方法可以有返回值 B、call方法可以申明拋出異常 ? Callable接口是JDK5后新增的接口,而且不是Runnable的子接口,所以Callable對象不能直接作為Thread的target。而且call方法還有一個返回值, call方法不能直接調用,它作為線程的執行體被調用。那么如何接收call方法的返回值? JDK1.5提供了Future接口來代表Callable接口里的call方法的返回值,并為Future接口提供了一個FutureTask實現類,該實現類實現Future接口, 并實現了Runnable接口—可以作為Thread的target。 ? Future接口里定義了如下幾個公共方法控制他關聯的Callable任務: A、boolean cancel(Boolean mayInterruptlfRunning):試圖取消該Future里關聯的Callable任務 B、V get():返回Callable任務里的call方法的返回值,調用該方法將導致線程阻塞,必須等到子線程結束才得到返回值 C、V get(long timeout, TimeUnit unit):返回Callable任務里的call方法的返回值,該方法讓程序最多阻塞timeout和unit指定的時間。 如果經過指定時間后Callable任務依然沒有返回值,將會拋出TimeoutException。 D、boolean isCancelled:如果在Callable任務正常完成前被取消,則返回true。 E、boolean isDone:如果Callable任務已經完成,則返回true ? 創建、并啟動有返回值的線程的步驟如下: 一、創建Callable接口的實現類,并實現call方法,該call方法的返回值,并作為線程的執行體。 二、創建Callable實現類的實例,使用FutureTask類來包裝Callable對象,該FutureTask對象封裝了該Callable對象的call方法的返回值 三、使用FutureTask對象作為Thread對象的target創建、并啟動新線程 四、調用FutureTask對象的方法來獲得子線程執行結束后的返回值?
總結
以上是生活随笔為你收集整理的同步锁ReentrantLock的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java Annotation详解
- 下一篇: java调用outlook