java 多線程總結(jié)篇3之——生命周期和線程同步

線程的生命周期全在一張圖中,理解此圖是基本:


線程生命狀態(tài)

一、新建和就緒狀態(tài)

當(dāng)程序使用new關(guān)鍵字創(chuàng)建了一個線程之后,該線程就處于新建狀態(tài),此時它和其他的Java對象一樣,僅僅由Java虛擬機(jī)為其分配內(nèi)存,并初始化其成員變量的值。此時的線程對象沒有表現(xiàn)出任何線程的動態(tài)特征,程序也不會執(zhí)行線程的線程執(zhí)行體。

當(dāng)線程對象調(diào)用了start()方法之后,該線程處于就緒狀態(tài)。Java虛擬機(jī)會為其創(chuàng)建方法調(diào)用棧和程序計數(shù)器,處于這個狀態(tài)中的線程并沒有開始運行,只是表示該線程可以運行了。至于該線程何時開始運行,取決于JVM里線程調(diào)度器的調(diào)度。

注意:啟動線程使用start()方法,而不是run()方法。永遠(yuǎn)不要調(diào)用線程對象的run()方法。調(diào)用start方法來啟動線程,系統(tǒng)會把該run()方法當(dāng)成線程執(zhí)行體來處理;但如果直按調(diào)用線程對象的run()方法,則run()方法立即就會被執(zhí)行,而且在run()方法返回之前其他線程無法并發(fā)執(zhí)行。也就是說,系統(tǒng)把線程對象當(dāng)成一個普通對象,而run()方法也是一個普通方法,而不是線程執(zhí)行體。需要指出的是,調(diào)用了線程的run()方法之后,該線程已經(jīng)不再處于新建狀態(tài),不要再次調(diào)用線程對象的start()方法。只能對處于新建狀態(tài)的線程調(diào)用start()方法,否則將引發(fā)IllegaIThreadStateExccption異常。
調(diào)用線程對象的start()方法之后,該線程立即進(jìn)入就緒狀態(tài)——就緒狀態(tài)相當(dāng)于"等待執(zhí)行",但該線程并未真正進(jìn)入運行狀態(tài)。如果希望調(diào)用子線程的start()方法后子線程立即開始執(zhí)行,程序可以使用Thread.sleep(1)來讓當(dāng)前運行的線程(主線程)睡眠1毫秒,1毫秒就夠了,因為在這1毫秒內(nèi)CPU不會空閑,它會去執(zhí)行另一個處于就緒狀態(tài)的線程,這樣就可以讓子線程立即開始執(zhí)行。

二、運行和阻塞狀態(tài)

2.1 線程調(diào)度

如果處于就緒狀態(tài)的線程獲得了CPU,開始執(zhí)行run()方法的線程執(zhí)行體,則該線程處于運行狀態(tài),如果計算機(jī)只有一個CPU。那么在任何時刻只有一個線程處于運行狀態(tài),當(dāng)然在一個多處理器的機(jī)器上,將會有多個線程并行執(zhí)行;當(dāng)線程數(shù)大于處理器數(shù)時,依然會存在多個線程在同一個CPU上輪換的現(xiàn)象。

當(dāng)一個線程開始運行后,它不可能一直處于運行狀態(tài)(除非它的線程執(zhí)行體足夠短,瞬間就執(zhí)行結(jié)束了)。線程在運行過程中需要被中斷,目的是使其他線程獲得執(zhí)行的機(jī)會,線程調(diào)度的細(xì)節(jié)取決于底層平臺所采用的策略。對于采用搶占式策略的系統(tǒng)而言,系統(tǒng)會給每個可執(zhí)行的線程一個小時間段來處理任務(wù);當(dāng)該時間段用完后,系統(tǒng)就會剝奪該線程所占用的資源,讓其他線程獲得執(zhí)行的機(jī)會。在選擇下一個線程時,系統(tǒng)會考慮線程的優(yōu)先級。

所有現(xiàn)代的桌面和服務(wù)器操作系統(tǒng)都采用搶占式調(diào)度策略,但一些小型設(shè)備如手機(jī)則可能采用協(xié)作式調(diào)度策略,在這樣的系統(tǒng)中,只有當(dāng)一個線程調(diào)用了它的sleep()yield()方法后才會放棄所占用的資源——也就是必須由該線程主動放棄所占用的資源。

2.2 線程阻塞

當(dāng)發(fā)生如下情況時,線程將會進(jìn)入阻塞狀態(tài)
① 線程調(diào)用sleep()方法主動放棄所占用的處理器資源
② 線程調(diào)用了一個阻塞式IO方法,在該方法返回之前,該線程被阻塞
③ 線程試圖獲得一個同步監(jiān)視器,但該同步監(jiān)視器正被其他線程所持有。關(guān)于同步監(jiān)視器的知識、后面將存更深入的介紹
④ 線程在等待某個通知(notify)
⑤ 程序調(diào)用了線程的suspend()方法將該線程掛起。但這個方法容易導(dǎo)致死鎖,所以應(yīng)該盡量避免使用該方法

當(dāng)前正在執(zhí)行的線程被阻塞之后,其他線程就可以獲得執(zhí)行的機(jī)會。被阻塞的線程會在合適的時候重新進(jìn)入就緒狀態(tài),注意是就緒狀態(tài)而不是運行狀態(tài)。也就是說,被阻塞線程的阻塞解除后,必須重新等待線程調(diào)度器再次調(diào)度它。

2.3 解除阻塞

針對上面幾種情況,當(dāng)發(fā)生如下特定的情況時可以解除上面的阻塞,讓該線程重新進(jìn)入就緒狀態(tài):
① 調(diào)用sleep()方法的線程經(jīng)過了指定時間。
② 線程調(diào)用的阻塞式IO方法已經(jīng)返回。
③ 線程成功地獲得了試圖取得的同步監(jiān)視器。
④ 線程正在等待某個通知時,其他線程發(fā)出了個通知。
⑤ 處于掛起狀態(tài)的線程被調(diào)甩了resume()恢復(fù)方法。
線程從阻塞狀態(tài)只能進(jìn)入就緒狀態(tài),無法直接進(jìn)入運行狀態(tài)。而就緒和運行狀態(tài)之間的轉(zhuǎn)換通常不受程序控制,而是由系統(tǒng)線程調(diào)度所決定。當(dāng)處于就緒狀態(tài)的線程獲得處理器資源時,該線程進(jìn)入運行狀態(tài);當(dāng)處于運行狀態(tài)的線程失去處理器資源時,該線程進(jìn)入就緒狀態(tài)。但有一個方法例外,調(diào)用yield()方法可以讓運行狀態(tài)的線程轉(zhuǎn)入就緒狀態(tài)。關(guān)于yield()方法后面有更詳細(xì)的介紐。

三、線程死亡

3.1 死亡狀態(tài)

線程會以如下3種方式結(jié)束,結(jié)束后就處于死亡狀態(tài):
run()call()方法執(zhí)行完成,線程正常結(jié)束。
② 線程拋出一個未捕獲的ExceptionError。
③ 直接調(diào)用該線程stop()方法來結(jié)束該線程——該方法容易導(dǎo)致死鎖,通常不推薦使用。

四、重難點考察

1. 有哪些不同的線程生命周期?

當(dāng)線程被創(chuàng)建并啟動以后,它既不是一啟動就進(jìn)入了執(zhí)行狀態(tài),也不是一直處于執(zhí)行狀態(tài)。在線程的生命周期中,它要經(jīng)過新建(New)、就緒(Runnable)、運行(Running)、阻塞(Blocked)和死亡(Dead)5種狀態(tài)。尤其是當(dāng)線程啟動以后,它不可能一直"霸占"著CPU獨自運行,所以CPU需要在多條線程之間切換,于是線程狀態(tài)也會多次在運行、阻塞之間切換

1. 新建狀態(tài),當(dāng)程序使用new關(guān)鍵字創(chuàng)建了一個線程之后,該線程就處于新建狀態(tài),此時僅由JVM為其分配內(nèi)存,并初始化其成員變量的值

2. 就緒狀態(tài),當(dāng)線程對象調(diào)用了start()方法之后,該線程處于就緒狀態(tài)。Java虛擬機(jī)會為其創(chuàng)建方法調(diào)用棧和程序計數(shù)器,等待調(diào)度運行

3. 運行狀態(tài),如果處于就緒狀態(tài)的線程獲得了CPU,開始執(zhí)行run()方法的線程執(zhí)行體,則該線程處于運行狀態(tài)

4. 阻塞狀態(tài),當(dāng)處于運行狀態(tài)的線程失去所占用資源之后,便進(jìn)入阻塞狀態(tài).

5. 轉(zhuǎn)換過程, 在線程的生命周期當(dāng)中,線程的各種狀態(tài)的轉(zhuǎn)換過程

2.線程狀態(tài),BLOCKED 和 WAITING 有什么區(qū)別

線程可以通過wait,join,LockSupport.park方式進(jìn)入wating狀態(tài),進(jìn)入wating狀態(tài)的線程等待喚醒(notifynotifyAll)才有機(jī)會獲取cpu的時間片段來繼續(xù)執(zhí)行。線程的 blocked狀態(tài)往往是無法進(jìn)入同步方法/代碼塊來完成的。這是因為無法獲取到與同步方法/代碼塊相關(guān)聯(lián)的鎖。與wating狀態(tài)相關(guān)聯(lián)的是等待隊列,與blocked狀態(tài)相關(guān)的是同步隊列,一個線程由等待隊列遷移到同步隊列時,線程狀態(tài)將會由wating轉(zhuǎn)化為blocked??梢赃@樣說,blocked狀態(tài)是處于wating狀態(tài)的線程重新煥發(fā)生命力的必由之路。

不過個人覺得實際上不用可以區(qū)分兩者, 因為兩者都會暫停線程的執(zhí)行. 兩者的區(qū)別是: 進(jìn)入waiting狀態(tài)是線程主動的, 而進(jìn)入blocked狀態(tài)是被動的. 更進(jìn)一步的說, 進(jìn)入blocked狀態(tài)是在同步(synchronized代碼之外), 而進(jìn)入waiting狀態(tài)是在同步代碼之內(nèi).

3.ThreadLocal用途是什么,原理是什么,用的時候要注意什么?

在多線程程序中,同一個線程在某個時間段只能處理一個任務(wù),我們希望在這個時間段內(nèi),任務(wù)的某些變量能夠和處理它的線程進(jìn)行綁定,,在任務(wù)需要使用這個變量的時候,這個變量能夠方便的從線程中取出來。ThreadLocal能很好的滿足這個需求,用ThreadLocal變量的程序看起來也會簡潔很多,因為減少了變量在程序中的傳遞,每個運行的線程都會有一個類型為ThreadLocal。ThreadLocalMapmap,這個map就是用來存儲與這個線程綁定的變量,mapkey就是ThreadLocal對象,value就是線程正在執(zhí)行的任務(wù)中的某個變量的包裝類Entry、在使用ThreadLocal對象,盡量使用static,不然會使線程的ThreadLocalMap產(chǎn)生太多Entry,從而造成內(nèi)存泄露。

4.Java中用到的線程調(diào)度算法是什么?
5.什么是多線程中的上下文切換?

即使是單核CPU也支持多線程執(zhí)行代碼,CPU通過給每個線程分配CPU時間片來實現(xiàn)這個機(jī)制。時間片是CPU分配給各個線程的時間,因為時間片非常短,所以CPU通過不停地切換線程執(zhí)行,讓我們感覺多個線程時同時執(zhí)行的,時間片一般是幾十毫秒(ms)。CPU通過時間片分配算法來循環(huán)執(zhí)行任務(wù),當(dāng)前任務(wù)執(zhí)行一個時間片后會切換到下一個任務(wù)。但是,在切換前會保存上一個任務(wù)的狀態(tài),以便下次切換回這個任務(wù)時,可以再次加載這個任務(wù)的狀態(tài),從任務(wù)保存到再加載的過程就是一次上下文切換。

6、你對線程優(yōu)先級的理解是什么?

每一個線程都是有優(yōu)先級的, 一般來說, 高優(yōu)先級的線程在運行時會具有優(yōu)先權(quán), 但這依賴于線程調(diào)度的實現(xiàn), 這個實現(xiàn)是和操作系統(tǒng)相關(guān)的. 我們可以定義線程的優(yōu)先級getPriority() setPriority(), 但是這并不能保證高優(yōu)先級的線程會在低優(yōu)先級的線程前執(zhí)行. 縣城優(yōu)先級是一個int變量, 1代表最低, 10代表最高。

7、什么是線程調(diào)度器 (Thread Scheduler) 和時間分片 (Time Slicing)?

線程調(diào)度器是一個操作系統(tǒng)服務(wù), 它負(fù)責(zé)為Runnable狀態(tài)的線程分配CPU時間, 一旦我們創(chuàng)建一個線程并啟動它, 它的執(zhí)行便依賴于線程調(diào)度器的實現(xiàn)。時間分片是指將可用的CPU時間分配給可用的Runnable線程的過程。分配的CPU時間可以基于線程的優(yōu)先級或者線程的等待時間, 線程調(diào)度并不受到Java虛擬機(jī)控制, 所以由應(yīng)用程序來控制它是更好的選擇(也就是說不要讓你的程序依賴于線程優(yōu)先級)。

二、線程同步

java允許多線程并發(fā)控制,當(dāng)多個線程同時操作一個可共享的資源變量時(如數(shù)據(jù)的增刪改查), 將會導(dǎo)致數(shù)據(jù)不準(zhǔn)確,相互之間產(chǎn)生沖突,因此加入同步鎖以避免在該線程沒有完成操作之前,被其他線程的調(diào)用, 從而保證了該變量的唯一性和準(zhǔn)確性。

1、 請說出你所知的線程同步的方法

1.同步方法。即有synchronized關(guān)鍵字修飾的方法。 由于java的每個對象都有一個內(nèi)置鎖,當(dāng)用此關(guān)鍵字修飾方法時, 內(nèi)置鎖會保護(hù)整個方法。在調(diào)用該方法前,需要獲得內(nèi)置鎖,否則就處于阻塞狀態(tài)。代碼如:

public synchronized void save(){}
//注:synchronized關(guān)鍵字也可以修飾靜態(tài)方法,此時如果調(diào)用該靜態(tài)方法,將會鎖住整個類

2.同步代碼塊,即有synchronized關(guān)鍵字修飾的語句塊。 被該關(guān)鍵字修飾的語句塊會自動被加上內(nèi)置鎖,從而實現(xiàn)同步

//代碼如:
synchronized(object){
}

注:同步是一種高開銷的操作,因此應(yīng)該盡量減少同步的內(nèi)容。 通常沒有必要同步整個方法,使用synchronized代碼塊同步關(guān)鍵代碼即可。

/**
     * 線程同步的運用
     */
    public class SynchronizedThread {

        class Bank {

            private int account = 100;

            public int getAccount() {
                return account;
            }

            /**
             * 用同步方法實現(xiàn)
             * @param money
             */
            public synchronized void save(int money) {
                account += money;
            }

            /**
             * 用同步代碼塊實現(xiàn)
             * @param money
             */
            public void save1(int money) {
                synchronized (this) {
                    account += money;
                }
            }
        }

        class NewThread implements Runnable {
            private Bank bank;

            public NewThread(Bank bank) {
                this.bank = bank;
            }

            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    // bank.save1(10);
                    bank.save(10);
                    System.out.println(i + "賬戶余額為:" + bank.getAccount());
                }
            }
        }

        /**
         * 建立線程,調(diào)用內(nèi)部類
         */
        public void useThread() {
            Bank bank = new Bank();
            NewThread new_thread = new NewThread(bank);
            System.out.println("線程1");
            Thread thread1 = new Thread(new_thread);
            thread1.start();
            System.out.println("線程2");
            Thread thread2 = new Thread(new_thread);
            thread2.start();
        }

        public static void main(String[] args) {
            SynchronizedThread st = new SynchronizedThread();
            st.useThread();
        }
    }

3.使用特殊域變量(volatile)實現(xiàn)線程同步
a.volatile關(guān)鍵字為域變量的訪問提供了一種免鎖機(jī)制,
b.使用volatile修飾域相當(dāng)于告訴虛擬機(jī)該域可能會被其他線程更新,
c.因此每次使用該域就要重新計算,而不是使用寄存器中的值
d.volatile不會提供任何原子操作,它也不能用來修飾final類型的變量

//只給出要修改的代碼,其余代碼與上同
        class Bank {
            //需要同步的變量加上volatile
            private volatile int account = 100;

            public int getAccount() {
                return account;
            }
            //這里不再需要synchronized 
            public void save(int money) {
                account += money;
            }
        }

注:多線程中的非同步問題主要出現(xiàn)在對域的讀寫上,如果讓域自身避免這個問題,則就不需要修改操作該域的方法。 用final域,有鎖保護(hù)的域和volatile域可以避免非同步的問題。
4.使用重入鎖實現(xiàn)線程同步,在JavaSE5.0中新增了一個java.util.concurrent包來支持同步。 ReentrantLock類是可重入、互斥、實現(xiàn)了Lock接口的鎖, 它與使用synchronized方法和快具有相同的基本行為和語義,并且擴(kuò)展了其能力ReenreantLock類的常用方法有:

ReentrantLock() : 創(chuàng)建一個ReentrantLock實例
lock() : 獲得鎖
unlock() : 釋放鎖

注:ReentrantLock()還有一個可以創(chuàng)建公平鎖的構(gòu)造方法,但由于能大幅度降低程序運行效率,不推薦使用
5.使用局部變量實現(xiàn)線程同步。如果使用ThreadLocal管理變量,則每一個使用該變量的線程都獲得該變量的副本, 副本之間相互獨立,這樣每一個線程都可以隨意修改自己的變量副本,而不會對其他線程產(chǎn)生影響。
ThreadLocal 類的常用方法

ThreadLocal() : 創(chuàng)建一個線程本地變量
get() : 返回此線程局部變量的當(dāng)前線程副本中的值
initialValue() : 返回此線程局部變量的當(dāng)前線程的"初始值"
set(T value) : 將此線程局部變量的當(dāng)前線程副本中的值設(shè)置為value

注:ThreadLocal與同步機(jī)制:a.ThreadLocal與同步機(jī)制都是為了解決多線程中相同變量的訪問沖突問題。 b.前者采用以"空間換時間"的方法,后者采用以"時間換空間"的方式
6.使用阻塞隊列實現(xiàn)線程同步,7.使用原子變量實現(xiàn)線程同步****

《關(guān)于線程同步的7種方式》

2、 synchronized 的原理是什么

Synchronized的語義底層是通過一個monitor(監(jiān)視器鎖)來實現(xiàn)的。Synchronized進(jìn)過編譯,會在同步塊的前后分別形成monitorentermonitorexit這個兩個字節(jié)碼指令。在執(zhí)行monitorenter指令時,首先要嘗試獲取對象鎖。如果這個對象沒被鎖定,或者當(dāng)前線程已經(jīng)擁有了那個對象鎖,把鎖的計算器加1,相應(yīng)的,在執(zhí)行monitorexit指令時會將鎖計算器就減1,當(dāng)計算器為0時,鎖就被釋放了。如果獲取對象鎖失敗,那當(dāng)前線程就要阻塞,直到對象鎖被另一個線程釋放為止。

monitorenter :
Each object is associated with a monitor. A monitor is locked if and only if it has an owner. The thread that executes monitorenter attempts to gain ownership of the monitor associated with objectref, as follows:
? If the entry count of the monitor associated with objectref is zero, the thread enters the monitor and sets its entry count to one. The thread is then the owner of the monitor.
? If the thread already owns the monitor associated with objectref, it reenters the monitor, incrementing its entry count.
? If another thread already owns the monitor associated with objectref, the thread blocks until the monitor's entry count is zero, then tries again to gain ownership.

每個對象有一個監(jiān)視器鎖(monitor)。當(dāng)monitor被占用時就會處于鎖定狀態(tài),線程執(zhí)行monitorenter指令時嘗試獲取monitor的所有權(quán),過程如下:
1、如果monitor的進(jìn)入數(shù)為0,則該線程進(jìn)入monitor,然后將進(jìn)入數(shù)設(shè)置為1,該線程即為monitor的所有者。
2、如果線程已經(jīng)占有該monitor,只是重新進(jìn)入,則進(jìn)入monitor的進(jìn)入數(shù)加1.
3.如果其他線程已經(jīng)占用了monitor,則該線程進(jìn)入阻塞狀態(tài),直到monitor的進(jìn)入數(shù)為0,再重新嘗試獲取monitor的所有權(quán)。

monitorexit: 
The thread that executes monitorexit must be the owner of the monitor associated with the instance referenced by objectref.
The thread decrements the entry count of the monitor associated with objectref. If as a result the value of the entry count is zero, the thread exits the monitor and is no longer its owner. Other threads that are blocking to enter the monitor are allowed to attempt to do so.

執(zhí)行monitorexit的線程必須是objectref所對應(yīng)的monitor的所有者。指令執(zhí)行時,monitor的進(jìn)入數(shù)減1,如果減1后進(jìn)入數(shù)為0,那線程退出monitor,不再是這個monitor的所有者。其他被這個monitor阻塞的線程可以嘗試去獲取這個 monitor 的所有權(quán)。

《深入分析Synchronized》 《Java并發(fā)編程之——Synchronized》

3、 synchronized 和ReentrantLock有什么不同

相似點:這兩種同步方式有很多相似之處,它們都是加鎖方式同步,而且都是阻塞式的同步,也就是說當(dāng)如果一個線程獲得了對象鎖,進(jìn)入了同步塊,其他訪問該同步塊的線程都必須阻塞在同步塊外面等待,而進(jìn)行線程阻塞和喚醒的代價是比較高的(操作系統(tǒng)需要在用戶態(tài)與內(nèi)核態(tài)之間來回切換,代價很高,不過可以通過對鎖優(yōu)化進(jìn)行改善)。

區(qū)別:這兩種方式最大區(qū)別就是對于Synchronized來說,它是java語言的關(guān)鍵字,是原生語法層面的互斥,需要jvm實現(xiàn)。而ReentrantLock它是JDK 1.5之后提供的API層面的互斥鎖,需要lock()unlock()方法配合try/finally語句塊來完成。

總的來說,Lock提供了比synchronized更多的功能。但是要注意以下幾點:

1)Lock不是Java語言內(nèi)置的,synchronized是Java語言的關(guān)鍵字,因此是內(nèi)置特性。Lock是一個類,通過這個類可以實現(xiàn)同步訪問;

2)Locksynchronized有一點非常大的不同,采用synchronized不需要用戶去手動釋放鎖,當(dāng)synchronized方法或者synchronized代碼塊執(zhí)行完之后,系統(tǒng)會自動讓線程釋放對鎖的占用;而Lock則必須要用戶去手動釋放鎖,如果沒有主動釋放鎖,就有可能導(dǎo)致出現(xiàn)死鎖現(xiàn)象。
synchronized在發(fā)生異常時,會自動釋放線程占有的鎖,因此不會導(dǎo)致死鎖現(xiàn)象發(fā)生;而Lock在發(fā)生異常時,如果沒有主動通過unLock()去釋放鎖,則很可能造成死鎖現(xiàn)象,因此使用Lock時需要在finally塊中釋放鎖;

3)Lock可以讓等待鎖的線程響應(yīng)中斷,而synchronized卻不行,使用synchronized時,等待的線程會一直等待下去,不能夠響應(yīng)中斷;

4)通過Lock可以知道有沒有成功獲取鎖,而synchronized卻無法辦到。

5)Lock可以提高多個線程進(jìn)行讀操作的效率。

在性能上來說,如果競爭資源不激烈,兩者的性能是差不多的,而當(dāng)競爭資源非常激烈時(即有大量線程同時競爭),此時Lock的性能要遠(yuǎn)遠(yuǎn)優(yōu)于synchronized。所以說,在具體使用時要根據(jù)適當(dāng)情況選擇。

《java兩種線程同步方式的區(qū)別——Synchronized和ReentrantLock》

4、什么場景下可以使用 volatile 替換 synchronized

只需要保證共享資源的可見性的時候可以使用volatile替代,synchronized保證可操作的原子性一致性和可見性。volatile適用于新值不依賴于舊值的情形

5、有T1,T2,T3三個線程,怎么確保它們按順序執(zhí)行?怎樣保證T2在T1執(zhí)行完后執(zhí)行,T3在T2執(zhí)行完后執(zhí)行

這里考察的主要知識點就是線程同步機(jī)制和鎖的問題,《Java 指定線程執(zhí)行順序(三種方式)》
另外關(guān)于執(zhí)行順序的問題,可以有很多的考察點,可以刷刷這個博客《java指定線程執(zhí)行的順序》

6、同步塊內(nèi)的線程拋出異常會發(fā)生什么

這個問題坑了很多Java程序員,若你能想到鎖是否釋放這條線索來回答還有點希望答對。無論你的同步塊是正常還是異常退出的,里面的線程都會釋放鎖,所以對比鎖接口我更喜歡同步塊,因為它不用我花費精力去釋放鎖,該功能可以在finally block里釋放鎖實現(xiàn)。

7、當(dāng)一個線程進(jìn)入一個對象的synchronized 方法A 之后,其它線程是否可進(jìn)入此對象的synchronized 方法B

不能。其它線程只能訪問該對象的非同步方法,同步方法則不能進(jìn)入。因為非靜態(tài)方法上的synchronized修飾符要求執(zhí)行方法時要獲得對象的鎖,如果已經(jīng)進(jìn)入A方法說明對象鎖已經(jīng)被取走,那么試圖進(jìn)入B方法的線程就只能在等鎖池(注意不是等待池哦 )中等待對象的鎖。

8、使用 synchronized 修飾靜態(tài)方法和非靜態(tài)方法有什么區(qū)別

static方法前加synchronizedstatic:靜態(tài)方法屬于類方法,它屬于這個類,獲取到的鎖,是屬于類的鎖。 在普通方法前加synchronizedstatic:非static方法獲取到的鎖,是屬于當(dāng)前對象的鎖。 結(jié)論:類鎖和對象鎖不同,他們之間不會產(chǎn)生互斥。

9、如何從給定集合那里創(chuàng)建一個 synchronized 的集合

我們可以使用Collections.synchronizedCollection(Collection c)根據(jù)指定集合來獲取一個synchronized(線程安全的)集合。比如HashMap可以這樣來實現(xiàn)線程安全:

Map m = Collections.synchronizedMap(new HashMap);
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容