成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

Java并發(fā)總結(jié)

szysky / 2602人閱讀

摘要:限期阻塞調(diào)用方法等待時(shí)間結(jié)束或線程執(zhí)行完畢。終止?fàn)顟B(tài)線程執(zhí)行完畢或出現(xiàn)異常退了。和都會(huì)檢查線程何時(shí)中斷,并且在發(fā)現(xiàn)中斷時(shí)提前放回。工廠方法將線程池的最大大小設(shè)置為,而將基本大小設(shè)置為,并將超時(shí)大小設(shè)置為分鐘。

wait()、notify()、notifyAll()

Object是所有類的基類,它有5個(gè)方法組成了等待、通知機(jī)制的核心:notify()、notifyAll()、wait()、wait(long) 和wait(long,int)。在java中,所有的類都是從Object繼承而來,因此,所有的類都擁有這些共同的方法可供使用。

wait()
public final void wait()  throws InterruptedException,IllegalMonitorStateException

該方法用來將當(dāng)前線程置入休眠狀態(tài),直到接到通知或中斷為止。在調(diào)用wait()之前,線程必須要獲得對象的對象級別的鎖,即只能在同步方法或同步代碼塊中調(diào)用wait()方法。進(jìn)入wait()方法后,當(dāng)前線程釋放鎖。在從wait()返回前,線程與其他線程競爭重新獲得鎖。如果調(diào)用wait()時(shí),沒有持有適當(dāng)?shù)逆i,則拋出IllegalMonitorStateException,它是RuntimeException的一個(gè)子類,因此不需要try-catch結(jié)構(gòu)。

notify()
    public final native void notify() throws IllegalMonitorStateException

該方法也要在同步方法或同步代碼塊中調(diào)用,即在調(diào)用前,線程也必須要獲得該對象的對象級別鎖,如果調(diào)用notify()時(shí)沒有持有適當(dāng)?shù)逆i,也會(huì)拋出IllegalMonitorStateException。

該方法用來通知那些等待該對象的對象鎖的其他線程。如果有多個(gè)線程等待,則線程規(guī)劃器任意挑選其中一個(gè)wait()狀態(tài)得線程來發(fā)出通知,并使它們等待獲取該對象的對象鎖。(notify后,當(dāng)前線程不會(huì)馬上釋放對象鎖,wait所在的線程并不能馬上獲取該對象鎖,要等到程序退出synchronized代碼塊后,當(dāng)前線程才會(huì)釋放鎖,wait所在的線程才可以獲得該對象鎖)。

但不驚動(dòng)其他同樣等待該對象notify的線程們,當(dāng)?shù)谝粋€(gè)獲得了該對象的wait線程運(yùn)行完畢之后,它會(huì)釋放該對象的鎖,此時(shí)如果該對象沒有再次使用notify語句,則即便對象已經(jīng)空閑,其他wait狀態(tài)等待的線程由于沒有得到該對象的通知,會(huì)繼續(xù)阻塞在wait狀態(tài),直到這個(gè)對象發(fā)出一個(gè)notify或notifyAll。

wait狀態(tài)等待的是被notify而不是鎖。

notifyAll()
     public final native void notifyAll() throws IllegalMonitorStateException

該方法與notify()方法的工作方式相同,重要的一點(diǎn)差異:
notifyAll使所有的方法在該對象wait的線程統(tǒng)統(tǒng)退出wait狀態(tài),變成等待獲取該對象上的鎖,一旦該對象鎖被釋放(即notifyAll線程退出同步代碼塊時(shí)),它們就會(huì)去競爭。如果其中一個(gè)線程獲得了該對象的鎖,它就會(huì)繼續(xù)往下執(zhí)行,在它退出synchronized代碼塊,釋放鎖后,其他的已經(jīng)被喚醒的線程將會(huì)繼續(xù)競爭該鎖,一直進(jìn)行下去,直到所有被喚醒的線程都執(zhí)行完畢。

    
    public class WaitAndNotify {
        public static void main(String[] args) throws InterruptedException{
            WaitAndNotify wan = new WaitAndNotify();
    //        synchronized(wan){
    //            wan.wait();
    //            System.out.println("wait");
    //        }
            new Thread(new Runnable(){
                public void run(){
                    synchronized(wan){
                        try {
                            wan.wait();
                            System.out.println("wait");
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }).start();
            new Thread(new Runnable(){
                public void run(){
                    synchronized(wan){
                        try {
                            wan.wait();
                            System.out.println("wait");
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }).start();
            new Thread(new Runnable(){
                public void run(){
                    synchronized(wan){
                        wan.notifyAll();        //當(dāng)notify方法時(shí)只執(zhí)行一個(gè)wait、而notifyAll方法將執(zhí)行兩個(gè)wait
                        System.out.println("notify");
                    }
                }
            }).start();
        }
    }
線程的狀態(tài)及其轉(zhuǎn)換

新建

創(chuàng)建之后未啟動(dòng)

就緒狀態(tài)

調(diào)用了start方法,不過還未被OS調(diào)用,或者正在等待CPU時(shí)間片。

運(yùn)行狀態(tài)

正在被OS執(zhí)行。

阻塞狀態(tài)

阻塞狀態(tài)分三種:

同步阻塞:線程正在等待一個(gè)排它鎖。

限期阻塞:調(diào)用Thread.sleep()、join()方法等待sleep時(shí)間結(jié)束或join線程執(zhí)行完畢。

無限期阻塞:通過wait()方法進(jìn)入,等待notify()或notifyAll()方法喚醒。

終止?fàn)顟B(tài)

線程執(zhí)行完畢或出現(xiàn)異常退了run()。

volatile 作用

volatile可以保證線程的可見性并提供一定的有序性,但是無法保證原子性。

原理

在JVM底層,volatile是采用內(nèi)存屏障來實(shí)現(xiàn)的。

內(nèi)存屏障

內(nèi)存屏障是一個(gè)cpu指令,他的作用:

確保一些特定操作執(zhí)行的順序

影響一些數(shù)據(jù)的可見性(可能是某些指令執(zhí)行后的結(jié)果)。編譯器和cpu在保證輸出結(jié)果一樣的情況下對指令重排序,使性能得到優(yōu)化。插入一個(gè)內(nèi)存屏障,相當(dāng)于告訴CPU和編譯器先于這個(gè)命令的必須先執(zhí)行,后于這個(gè)命令的必須后執(zhí)行。

內(nèi)存屏障跟volatile的關(guān)系

如果字段是volatile,java內(nèi)存模型將在寫操作后插入一個(gè)寫屏障指令,在讀操作前插入一個(gè)讀屏障指令。這就意味著如果你對一個(gè)volatile字段進(jìn)行寫操作,那么,一旦你完成寫入,任何訪問這個(gè)字段的線程都將會(huì)得到最新的值;在你寫入之前,會(huì)保證之前發(fā)生的事情已經(jīng)發(fā)生,并且任何更新過的數(shù)據(jù)值都是可見的。

任務(wù)執(zhí)行

串行執(zhí)行任務(wù):在單個(gè)線程中串行地執(zhí)行各項(xiàng)任務(wù)。

顯式地為任務(wù)創(chuàng)建線程:通過為每個(gè)線程創(chuàng)建一個(gè)新的線程來提供服務(wù),從而實(shí)現(xiàn)更高的響應(yīng)性。這種方式存在一定缺陷:

線程生命周期的開銷非常高:線程的創(chuàng)建于銷毀需要消耗計(jì)算資源。

資源消耗:活躍的線程會(huì)消耗系統(tǒng)資源,尤其是內(nèi)存。如果在可用處理器的數(shù)量小于可運(yùn)行的線程數(shù)量時(shí),那么有些線程將會(huì)被閑置。

穩(wěn)定性:在可創(chuàng)建線程的數(shù)量上存在一個(gè)限制,如果破壞了這些限制,那么可能會(huì)拋出OutOfMemoryError異常。

線程池

Executor框架:

Executor雖然是一個(gè)簡單的接口,但它卻為靈活且強(qiáng)大的異步任務(wù)執(zhí)行框架提供了基礎(chǔ)。它提供了一種標(biāo)準(zhǔn)的方法將任務(wù)的提交過程與執(zhí)行過程解耦開來,應(yīng)用Runnable來表示任務(wù)。同時(shí)它還提供了對生命周期的支持?;谏a(chǎn)者-消費(fèi)者模式,提交任務(wù)的操作相當(dāng)于生產(chǎn)者,執(zhí)行任務(wù)的線程則相當(dāng)于消費(fèi)者。

Executor的生命周期

Executor的實(shí)現(xiàn)通常會(huì)創(chuàng)建線程來執(zhí)行任務(wù),但如果無法正確關(guān)閉Executor,那么jvm也將無法關(guān)閉。

為了解決執(zhí)行服務(wù)的生命周期問題,Executor擴(kuò)展了ExecutorSerivce借口,添加了一些用戶生命周期管理的方法。

ExecutorService的生命周期有三種狀態(tài):運(yùn)行、關(guān)閉和已終止。

ExecutorService在初始化創(chuàng)建時(shí)為運(yùn)行狀態(tài)。

Shutdown方法將執(zhí)行平緩的關(guān)閉過程:不在接受新的任務(wù),同時(shí)等待已經(jīng)提交的任務(wù)執(zhí)行完成——包括還未開始執(zhí)行的任務(wù)。

ShutdownNow方法將執(zhí)行粗暴的關(guān)閉過程:它將嘗試取消所有運(yùn)行的任務(wù),并且不在啟動(dòng)隊(duì)列中尚未開始執(zhí)行的任務(wù)。

Callable跟Future

Runnable是一定有很大局限的抽象,它不能返回一個(gè)值或拋出一個(gè)受檢查的異常。

Callable是一種更好的抽象,它認(rèn)為主入口點(diǎn)(cell)能夠返回一個(gè)值,并可能拋出一個(gè)異常。

Future表示一個(gè)任務(wù)的生命周期,并提供相應(yīng)的方法來判斷是否完成或取消。Executor執(zhí)行的任務(wù)有4個(gè)生命周期:創(chuàng)建、提交、開始和完成。任務(wù)的生命周期只能前進(jìn)不能后退。Future的get方法的行為取決于任務(wù)的狀態(tài),如果完成,那么get會(huì)立即返回或者拋出一個(gè)Exception,如果任務(wù)沒有完成,那么get將阻塞并指導(dǎo)任務(wù)完成,如果任務(wù)拋出異常,那么get將該異常封裝為ExecutionException并重新拋出。

Future的創(chuàng)建方式:

ExecutorService的所有submit方法都返回一個(gè)Future,從而將一個(gè)Runnable或Callable提交給Executor,并得到一個(gè)Futrue用來獲取任務(wù)的執(zhí)行結(jié)果或者取消任務(wù)。

顯式的為某個(gè)指定的Runnable或Callable實(shí)例化一個(gè)FutureTask。(FutureTask實(shí)現(xiàn)了Runnable,因此可以將它交給Executor來執(zhí)行或者直接調(diào)用它的run方法)

線程中斷:

Java并沒有提供任何機(jī)制來安全的終止線程,但它提供了中斷,這是一種協(xié)作機(jī)制,能夠使一個(gè)線程終止另一個(gè)線程的當(dāng)前工作。

Thread的中斷方法:

public class Thread{
        public void interrupt(){….}
        public Boolean isInterrupted(){….}
        public static Boolean interrupted(){….}
}

對中斷的正確理解:他并不會(huì)真正的中斷一個(gè)正在運(yùn)行的線程,而只是發(fā)出中斷請求,然后由線程在下一個(gè)合適的時(shí)刻中斷自己。

Interrupt():中斷目標(biāo)線程,

IsInterrupt():放回目標(biāo)線程的中斷狀態(tài)

Interrupted():清除當(dāng)前線程的中斷狀態(tài),并返回它之前的值,這也是清除中斷狀態(tài)的唯一方法。

Thread.sleep()和Object.wait()都會(huì)檢查線程何時(shí)中斷,并且在發(fā)現(xiàn)中斷時(shí)提前放回。它們在響應(yīng)中斷時(shí)執(zhí)行的操作:清除中斷狀態(tài),拋出InterruptedException,表示阻塞操作由于中斷而提前結(jié)束。能夠中斷處于阻塞、限期等待、無限期等待等狀態(tài),但不能中斷I/O阻塞跟synchronized鎖阻塞。

在調(diào)用interrupted()時(shí)返回true,除非像屏蔽這個(gè)中斷,不然就必須對它進(jìn)行處理。如果一個(gè)線程的 run() 方法執(zhí)行一個(gè)無限循環(huán),并且沒有執(zhí)行 sleep() 等會(huì)拋出 InterruptedException 的操作,那么調(diào)用線程的 interrupt() 方法就無法使線程提前結(jié)束。
但是調(diào)用 interrupt() 方法會(huì)設(shè)置線程的中斷標(biāo)記,此時(shí)調(diào)用 interrupted() 方法會(huì)返回 true。因此可以在循環(huán)體中使用 interrupted() 方法來判斷線程是否處于中斷狀態(tài),從而提前結(jié)束線程。

通過ExecutorService中斷

shutdownNow跟shutdown

通過Future中斷

Future擁有一個(gè)cancel方法,該方法帶有一個(gè)boolean類型參數(shù)mayInterruptIfRunning,表示取消操作是否成功,如果mayIterruptIfRunning為true并且任務(wù)當(dāng)前正在某個(gè)線程中執(zhí)行,那么這個(gè)線程能被中斷,如果mayInterruptIfRunning為false,那么意味著,若任務(wù)還沒有啟動(dòng),就不要執(zhí)行它。

線程池

線程池:管理一組同構(gòu)工作線程的資源池,通過重用現(xiàn)有的線程而不是創(chuàng)建新線程,可以在處理多個(gè)請求時(shí)分?jǐn)傇诰€程創(chuàng)建和銷毀過程中產(chǎn)生巨大開銷。

類庫提供了一個(gè)靈活的線程池以及一些有用的默認(rèn)配置??梢杂眠^調(diào)用Executors中的靜態(tài)工廠方法之一來創(chuàng)建一個(gè)線程池:

newFixedThreadPool: 創(chuàng)建一個(gè)固定長度的線程池,每當(dāng)提交一個(gè)任務(wù)時(shí)就創(chuàng)建一個(gè)線程,直到達(dá)到線程池的最大數(shù)量。newFixedThreadPool工廠方法將線程池的基本大小和最大大小設(shè)置為參數(shù)中的值,并且創(chuàng)建的線程池不會(huì)超時(shí)。

newCachedThreadPool: 創(chuàng)建一個(gè)可緩存的線程池,如果線程池的當(dāng)前規(guī)模超過了處理需求時(shí),那么將回收空閑的線程,而當(dāng)需求添加時(shí),則可以添加新的線程,線程池的規(guī)模不存在任何限制。newCacheThreadPool工廠方法將線程池的最大大小設(shè)置為Integer.MAX_VALUE,而將基本大小設(shè)置為0,并將超時(shí)大小設(shè)置為1分鐘。

newSingleThreadPool: 一個(gè)單線程Executor,創(chuàng)建單個(gè)工作線程來執(zhí)行任務(wù),如果這個(gè)線程異常結(jié)束,會(huì)創(chuàng)建另一個(gè)線程來替代。newSingleThreadPool能夠確保依照任務(wù)在隊(duì)列中的順序來串行執(zhí)行(例如FIFO、LIFO、優(yōu)先級)

ThreadPoolExecutor

ThreadPoolExecutor為一些Executor提供了基本實(shí)現(xiàn),這些Executor時(shí)由Executors中的newFixedThreadPool、newCachedThreadPool和newScheduledThreadExecutor等工廠方法返回的。

ThreadPoolExecutor是一個(gè)靈活的、穩(wěn)定的線程池,允許進(jìn)行各種定制。如果默認(rèn)的執(zhí)行策略不能滿足需求,那么可以通過ThreadPoolExecutor的構(gòu)造參數(shù)來實(shí)例化一個(gè)對象,并根據(jù)自己的需求來定制。

   public ThreadPoolExecutor( int corePoolSize,
                                 int maximumPoolSize,
                                 long keepAliveTime,
                                 TimeUnit unit,
                                 BlockingQueue workQueue,
                                 ThreadFactory threadFactory,
                                  RejectedExecutionHandler handler){…}

線程池的基本大小,最大大小以及存活時(shí)間等因素公共負(fù)責(zé)線程的創(chuàng)建和銷毀。

基本大小(corePoolSize):線程池的目標(biāo)大小,即在沒有任務(wù)執(zhí)行時(shí)線程池的大小,并且只有 在工作隊(duì)列滿了的情況下才會(huì)創(chuàng)建超過這個(gè)數(shù)量的線程。

最大大小(maximumPoolSize):表示可同時(shí)活動(dòng)的線程數(shù)量的上限。

存活時(shí)間(keepAliveTime):當(dāng)某個(gè)線程的空閑時(shí)間超過了存活時(shí)間,那么將被標(biāo)記為可回收的,并且當(dāng)線程的當(dāng)前大小超過了基本大小時(shí),這個(gè)線程將被終止。

管理隊(duì)列任務(wù)

在線程池中,如果新請求的到達(dá)速率超過線程池的處理速率,那么新到來的請求將積累起來。在線程池中,這些請求會(huì)在一個(gè)由Executor管理的Runnable隊(duì)列中等待。

ThreadPoolExecutor允許提供一個(gè)BlockingQueue來保存等待執(zhí)行的任務(wù)?;镜娜蝿?wù)排隊(duì)方法有三種:有界隊(duì)列、無界隊(duì)列和同步移交。隊(duì)列的選擇與其他的配置參數(shù)有關(guān),例如線程池的大小等。

newFixedThreadPool和newSingleThreadPool在默認(rèn)情況下使用一個(gè)無界的LinkedBlockingQueue。

一個(gè)更穩(wěn)妥的資源管理策略是使用有界隊(duì)列,例如ArrayBlockingQueue、有界的LinkedBlockingQueu、PriorityBlockingQueue。通過飽和策略來解決隊(duì)列填滿之后,新任務(wù)到來的情況。

對于非常大的或者無界的線程池,可以通過使用SynchronousQueue來避免任務(wù)排隊(duì),以及直接將任務(wù)從生產(chǎn)者持戒交給工作者線程。SynchronousQueue并不是一個(gè)真正的隊(duì)列,而是一種在線程之間隊(duì)形移交的機(jī)制。要將線程一個(gè)元素放入SynchronousQueue中,必須有另一個(gè)線程在等待接受。如果沒有線程等待,并且線程池的當(dāng)前大小小于最大值,那么ThreadPoolExecutor將創(chuàng)建一個(gè)新的線程。否則根據(jù)飽和策略,這個(gè)任務(wù)將被拒絕。使用直接移交更高效。只有線程池時(shí)無界或者可以拒絕任務(wù)時(shí),SynchronousQueue才有實(shí)際價(jià)值。在newCacheThreadPool工廠方法中就使用了SynchronousQueue。

飽和策略

當(dāng)有界隊(duì)列被填滿之后,飽和策略開始發(fā)揮作用。JDK提供了集中不同的RejectedExecutionHadler來實(shí)現(xiàn)。

終止(AbortPolicy):默認(rèn)的飽和策略,將策略將拋出未檢查的RejectExecutionException,調(diào)用者可以捕獲這個(gè)異常進(jìn)行處理。

拋棄(DiscardPolicy):將悄悄拋棄該任務(wù)。

拋棄最舊的(DiscardOldestPolicy):拋棄下一個(gè)將被執(zhí)行的任務(wù),然后嘗試重新提交新的任務(wù)。(如果工作隊(duì)列是一個(gè)優(yōu)先隊(duì)列,那么將拋棄優(yōu)先級最高的任務(wù))

調(diào)用者執(zhí)行(CallerRunsPolicy):將任務(wù)會(huì)退給調(diào)用者,從而降低任務(wù)的流量。它不是在線程池中的線程執(zhí)行新提交的任務(wù),而是在一個(gè)調(diào)用了execute的線程執(zhí)行該任務(wù)。在WebServer中使用有界隊(duì)列且”調(diào)用者運(yùn)行”飽和策略時(shí),并且線程池所有的線程都被占用,隊(duì)列已滿的情況下,下一個(gè)任務(wù)將會(huì)由調(diào)用execute的主線程執(zhí)行,此時(shí)主線程在執(zhí)行任務(wù)期間不會(huì)accept請求,故新到來的請求被保存在TCP層的隊(duì)列中而不是應(yīng)用程序的隊(duì)列中,如果持續(xù)過載,那么TCP層的請求隊(duì)列最終將被填滿,因?yàn)橥瑯訒?huì)開始拋出請求。

線程工廠:

每當(dāng)線程池需要?jiǎng)?chuàng)建一個(gè)線程時(shí),都是通過線程工廠方法來完成的,默認(rèn)的線程工廠方法將創(chuàng)建一個(gè)新的、非守護(hù)的線程,并且不包含特殊的配置信息。通過制定一個(gè)特定的工廠方法,可以定制線程池的配置信息。在ThreadFactory中只定義了一個(gè)方法newThread,每當(dāng)線程池需要?jiǎng)?chuàng)建一個(gè)新線程時(shí)都會(huì)調(diào)用這個(gè)方法。

public interface ThreadFactory {
    /**
     * Constructs a new {@code Thread}.  Implementations may also initialize
     * priority, name, daemon status, {@code ThreadGroup}, etc.
     *
     * @param r a runnable to be executed by new thread instance
     * @return constructed thread, or {@code null} if the request to
     *         create a thread is rejected
     */
    Thread newThread(Runnable r);
}
顯式鎖

Java5.0增加了一種新的機(jī)制:ReentranLock。與內(nèi)置加鎖機(jī)制不同的時(shí),Lock提供了一種無條件的、可輪詢的、定時(shí)的以及可中斷的鎖獲取操作,所有加鎖和解鎖的方法都是顯式的。

Lock lock = new ReentrantLock();
        //...
        lock.lock();
        try {
            //更新對象狀態(tài)
            //捕獲異常,并在必要時(shí)恢復(fù)不變形條件
        } finally {
            lock.unlock();  //不會(huì)自動(dòng)清除鎖
        }

當(dāng)程序的執(zhí)行控制單元離開被保護(hù)的代碼塊時(shí),并不會(huì)自動(dòng)清除鎖。

輪詢鎖與定時(shí)鎖

可定時(shí)的與可輪詢的鎖獲取模式是由tryLock方法實(shí)現(xiàn)的,與無條件的鎖獲取模式相比,它具有更完善的錯(cuò)誤恢復(fù)機(jī)制。在內(nèi)置鎖中,死鎖是一個(gè)很嚴(yán)重的問題,恢復(fù)程序的唯一方式是重新啟動(dòng)程序,而防止死鎖的唯一方法就是在構(gòu)造程序時(shí)避免出現(xiàn)不一致的鎖順序??啥〞r(shí)的與可輪詢的鎖提供了另一種選擇:避免死鎖的發(fā)生。

如果不能獲得所有需要的鎖,那么可以使用可定時(shí)的或可輪詢的鎖獲取方式,從而使你重新獲得控制權(quán),它會(huì)釋放已經(jīng)獲得的鎖,然后重新嘗試獲取所有鎖。(或其他操作)

在實(shí)現(xiàn)具有時(shí)間限制的操作時(shí),定時(shí)鎖同樣費(fèi)用有用:當(dāng)在帶有時(shí)間限制的操作中調(diào)用一個(gè)阻塞方法時(shí),它能根據(jù)剩余時(shí)間來提供一個(gè)時(shí)限,如果不能在制定的時(shí)間內(nèi)給出結(jié)果,那么就會(huì)使程序提前結(jié)束。

可中斷鎖

內(nèi)置鎖是不可中斷的,故有時(shí)將使得實(shí)現(xiàn)可取消得任務(wù)變得復(fù)雜,而顯示鎖可以中斷,lockInterruptibly方法能夠獲得鎖的同時(shí)保持對中斷的響應(yīng)。LockInterruptibly優(yōu)先考慮響應(yīng)中斷,而不是響應(yīng)鎖的普通獲取或重入獲取,既允許線程在還等待時(shí)就中斷。(lock優(yōu)先考慮獲取鎖,待獲取鎖成功之后才響應(yīng)中斷)

公平性

在ReentrantLock的構(gòu)造函數(shù)中提供了兩種公平性選擇:創(chuàng)建一個(gè)非公平的鎖(默認(rèn))或者一個(gè)公平的鎖,在公平的鎖上,線程講按照它們發(fā)出的請求的順序來獲取鎖,但在非公平鎖上,則允許插隊(duì)(當(dāng)一個(gè)線程請求非公平鎖時(shí),同時(shí)該鎖的狀態(tài)變?yōu)榭捎?,那么這個(gè)線程將跳過隊(duì)列中所有的等待線程并獲得這個(gè)鎖)。

在公平鎖中,如果有一個(gè)線程持有這個(gè)鎖或者有其他線程在隊(duì)列中等待這個(gè)鎖,那么新發(fā)的請求的線程將會(huì)放入隊(duì)列中,而在非公平鎖中,只有當(dāng)鎖被某個(gè)線程持有時(shí),新發(fā)出的請求的線程才會(huì)放入隊(duì)列中。

大多數(shù)情況下,非公平鎖的性能要高于公平鎖

公平性鎖在線程的掛起以及恢復(fù)中需要一定開銷

假設(shè)線程A持有一個(gè)鎖,并且線程B請求這個(gè)鎖,那么在A釋放時(shí),講喚醒B,這時(shí)C也請求這個(gè)鎖,那么可能C很可能會(huì)在B被完全喚醒之前就執(zhí)行完了。

java內(nèi)存模型

Java虛擬機(jī)規(guī)范試圖定義一種java內(nèi)存模型(Java Memory Model,JMM)來屏蔽掉各種硬件和操作系統(tǒng)的內(nèi)存訪問差異,以實(shí)現(xiàn)讓Java程序在各種平臺(tái)下都能達(dá)到一致的內(nèi)存訪問效果。

主內(nèi)存和工作內(nèi)存

計(jì)算機(jī)系統(tǒng)中處理器上的寄存器的讀寫速度比內(nèi)存要快幾個(gè)數(shù)量級別,為了解決這種矛盾,在它們之間加入高速緩存。

加入高速緩存的存儲(chǔ)交互很好的解決了處理器與內(nèi)存的速度矛盾,但是也為計(jì)算機(jī)系統(tǒng)帶來了更高的復(fù)雜度,因?yàn)樗肓艘粋€(gè)新的問題:緩存一致性。

多處理器系統(tǒng)中,每個(gè)處理器都有自己的高速緩存,而它們又共享同一主內(nèi)存,當(dāng)多個(gè)處理器的運(yùn)算任務(wù)涉及到同一塊主內(nèi)存區(qū)域時(shí),將可能導(dǎo)致各自的緩存不一致。

為了解決一致性問題,需要各個(gè)處理器訪問緩存時(shí)都遵循一些協(xié)議,在讀寫時(shí)根據(jù)協(xié)議來進(jìn)行操作。

Java內(nèi)存模型的主要目標(biāo)是定義程序中各個(gè)變量的訪問規(guī)則,即在虛擬機(jī)中將變量存儲(chǔ)到內(nèi)存以及從內(nèi)存中取出變量的底層細(xì)節(jié)。

JMM將所有變量(實(shí)例字段、靜態(tài)字段、數(shù)組對象的元素,線程共享的)都存儲(chǔ)到主內(nèi)存中,每個(gè)線程有自己的工作內(nèi)存,工作內(nèi)存中保存了被線程使用到的變量的主內(nèi)存的副本拷貝,線程對變量的所有操作都必須在工作內(nèi)存中進(jìn)行。

內(nèi)存間的交互操作

Java內(nèi)存模型定義了8個(gè)操作來完成主內(nèi)存和工作內(nèi)存間的交互操作。

read:把一個(gè)變量從主內(nèi)存?zhèn)鬏數(shù)焦ぷ鲀?nèi)存。

load:在read之后執(zhí)行,把read得到的值放入工作內(nèi)存的變量副本中。

use:把工作內(nèi)存中的一個(gè)變量的值傳遞給執(zhí)行引擎。

assign:把一個(gè)從執(zhí)行引擎接收到的值賦給工作內(nèi)存的變量。

store:把工作內(nèi)存的一個(gè)變量傳送到主內(nèi)存中。

write:在store之后執(zhí)行,把store得到的值放入主內(nèi)存的變量中。

lock:作用與主內(nèi)存變量,它把一個(gè)變量標(biāo)識為一條線程獨(dú)占的狀態(tài)。

unlock:把處于鎖定狀態(tài)得變量釋放出來。

內(nèi)存模型的三大特性 原子性

Java內(nèi)存模型保證了read、load、use、assign、store、write、lock和unlock操作具有原子性,例如對一個(gè)int類型的變量執(zhí)行assign復(fù)制操作,這個(gè)操作就是原子性的。但是Java內(nèi)存模型允許虛擬機(jī)將沒有被volatile修飾的64位數(shù)據(jù)(long,double)的讀寫操作劃分為兩個(gè)32位的操作來進(jìn)行,即load、store、read和write操作可以不具備原子性。

雖然Java內(nèi)存模型保證了這些操作的原子性,但是int等原子性操作在多線程中還是會(huì)出現(xiàn)線程安全性問題。
可通過兩個(gè)方式來解決:

使用AtomicInteger

通過synchronized互斥鎖來保證操作的原子性。

可見性

可見性指一個(gè)線程修改共享變量的值,其他線程能夠立即得知這個(gè)修改。Java內(nèi)存模型通過在變量修改后將新值同步回主內(nèi)存中,在變量讀取前從主內(nèi)存刷新變量值來實(shí)現(xiàn)可見性。

主要有三種方式實(shí)現(xiàn)可見性:

volatile

synchronized,對一個(gè)變量執(zhí)行unlock操作之前,必須把變量值同步回主內(nèi)存。

final:被final修飾的字段在構(gòu)造器中一旦初始化完成,并且沒有發(fā)生this逃逸(其他線程有可能通過這個(gè)引用訪問到初始化了一半的對象),那么其他線程就能夠看見final字段的值。

有序性

有序性是指:在本線程內(nèi)觀察,所有操作都是有序的。在一個(gè)線程觀察另一個(gè)線程,所有操作都是無序的,無序是因?yàn)榘l(fā)生了指令重排序。在Java內(nèi)存模型中,允許編譯器和處理器對指令進(jìn)行重排序,重排序過程中不會(huì)影響到單線程程序的執(zhí)行,卻會(huì)影響多線程并發(fā)執(zhí)行的正確性。

volatile關(guān)鍵字通過添加內(nèi)存屏障的方式禁止重排序,即重排序時(shí)不能把后面的指令放在內(nèi)存屏障之前。

也可以通過synchronized來保證有序性,它保證每個(gè)時(shí)刻只有一個(gè)線程執(zhí)行同步代碼,相當(dāng)于讓線程順序執(zhí)行同步代碼。

線程安全的方法 互斥同步

synchronized和ReentranLock。、

非阻塞同步

互斥同步最主要的問題是線程阻塞和喚醒所帶來的性能問題,因此這種這種同步燁稱為阻塞同步。

互斥同步是一種悲觀的并發(fā)策略,總是以為只要不去做正確的同步策略,那就肯定會(huì)出問題,無論共享數(shù)據(jù)是否真的會(huì)出現(xiàn)競爭,它都需要進(jìn)行枷鎖。

CAS

隨著硬件指令集的發(fā)展,我們可以使用基于沖突檢測的樂觀并發(fā)策略:先進(jìn)行操作,如果沒有其他線程競爭共享資源,那么操作就成功了,否則采取補(bǔ)償措施(不斷嘗試、知道成功為止)。這種樂觀的并發(fā)策略的許多實(shí)現(xiàn)都不需要將線程阻塞,因此這種同步操作稱為非阻塞同步。

樂觀鎖需要操作和沖突檢測這兩個(gè)步驟具備原子性,這里就不能再使用互斥同步來保證,只能靠硬件來完成。硬件支持的原子性操作最典型的是:比較和交換(Compare-And-swap,CAS)。CAS指令需要3個(gè)操作數(shù),分別是內(nèi)存地址V、舊的預(yù)期值A(chǔ)和新值B。當(dāng)操作完成時(shí),只有內(nèi)存地址V等值舊的預(yù)期值時(shí),才將V值更新為B。

無同步方案 棧封閉

多個(gè)線程訪問同一個(gè)方法的局部變量時(shí),不會(huì)出現(xiàn)線程安全問題,因?yàn)榫植孔兞看鎯?chǔ)在虛擬機(jī)棧中,屬于線程私用。

本地線程存儲(chǔ)(Thead Local Storage)

如果一段代碼中所需要的數(shù)據(jù)必須與其他代碼共享,那就看看這些共享數(shù)據(jù)的代碼是否能保證在同一個(gè)線程中執(zhí)行。如果能保證,我們就可以把共享數(shù)據(jù)的可見范圍限制在同一個(gè)線程之內(nèi),這樣,無須同步也能保證線程之間不出現(xiàn)數(shù)據(jù)爭用的問題。

符合這種特點(diǎn)的應(yīng)用并不少見,大部分使用消費(fèi)隊(duì)列的架構(gòu)模式(如“生產(chǎn)者-消費(fèi)者”模式)都會(huì)將產(chǎn)品的消費(fèi)過程盡量在一個(gè)線程中消費(fèi)完。其中最重要的一個(gè)應(yīng)用實(shí)例就是經(jīng)典 Web 交互模型中的“一個(gè)請求對應(yīng)一個(gè)服務(wù)器線程”(Thread-per-Request)的處理方式,這種處理方式的廣泛應(yīng)用使得很多 Web 服務(wù)端應(yīng)用都可以使用線程本地存儲(chǔ)來解決線程安全問題。

可以使用 java.lang.ThreadLocal 類來實(shí)現(xiàn)線程本地存儲(chǔ)功能。

synchronized的實(shí)現(xiàn)

synchronized關(guān)鍵字在經(jīng)過編譯之后,會(huì)在同步代碼塊前后分別形成monitorenter和monitorexit兩個(gè)字節(jié)碼指令。

在執(zhí)行monitorenter指令時(shí),首先嘗試獲取對象的鎖,如果這個(gè)對象沒有被鎖定或者當(dāng)前線程已經(jīng)擁有了這個(gè)鎖,就把鎖的計(jì)算器+1,相應(yīng)的執(zhí)行完monitorexit指令時(shí)將鎖計(jì)算器減1,當(dāng)計(jì)算器為0時(shí),鎖就被釋放。

鎖優(yōu)化

指JVM對synchronized的優(yōu)化。

自旋鎖

互斥同步進(jìn)入阻塞狀態(tài)的開銷都很大,應(yīng)該盡量避免,在許多應(yīng)用中,共享數(shù)據(jù)的鎖定狀態(tài)只會(huì)持續(xù)很短的一段時(shí)間。自旋鎖的思想是讓一個(gè)線程在共享數(shù)據(jù)的鎖執(zhí)行忙循環(huán)(自旋)一段時(shí)間,如果在這段時(shí)間內(nèi)能夠獲得鎖,就可以避免進(jìn)入阻塞狀態(tài)。

自旋鎖雖然能夠避免進(jìn)入阻塞狀態(tài)從而減少開銷,但是它需要進(jìn)行忙循環(huán)操作占用cpu時(shí)間,它只適用于共享數(shù)據(jù)的鎖定狀態(tài)很短的場景。

在JDK1.6中引入了自適應(yīng)的自旋鎖,自適應(yīng)意味著自旋次數(shù)不再是固定的,而是由前一次在同一個(gè)鎖上的自旋次數(shù)及鎖的擁塞者的狀態(tài)來決定。

鎖清除

鎖清除是指對被檢測出不存在競爭的共享數(shù)據(jù)的鎖進(jìn)行清除。

鎖清除主要通過逃逸分析來支持,如果堆上的共享數(shù)據(jù)不可能逃逸出去被其他線程訪問到,那么就可以把他們當(dāng)成私有數(shù)據(jù),也就可以將他們的鎖清除。

對于一些看起來沒有加鎖的代碼,其實(shí)隱式的加了很多鎖,例如一下的字符串拼接代碼就隱式加了鎖:

public static String concatString(String s1, String s2, String s3) {
    return s1 + s2 + s3;
}

String是一個(gè)不可變的類,編譯器會(huì)對String的拼接進(jìn)行自動(dòng)優(yōu)化。在JDK1.5之前會(huì)轉(zhuǎn)化為StringBuffer對象連續(xù)append()操作。

public static String concatString(String s1, String s2, String s3) {
    StringBuffer sb = new StringBuffer();
    sb.append(s1);
    sb.append(s2);
    sb.append(s3);
    return sb.toString();
}

每一個(gè)append()方法中都有一個(gè)同步塊,虛擬機(jī)觀察變量sb,很快發(fā)現(xiàn)它的動(dòng)態(tài)作用域被限制在concatString方法內(nèi)部,也就是說,sb的所有引用永遠(yuǎn)不會(huì)逃逸到contatString()方法之外,其他線程無法訪問到它,因此可以進(jìn)行鎖清除。

鎖粗化

如果一系列的操作都對同一對象反復(fù)加鎖和解鎖,頻繁的加鎖操作就會(huì)導(dǎo)致性能消耗。

上一節(jié)的示例代碼中連續(xù)的append()方法就屬于這種情況。如果虛擬機(jī)探測到由這樣的一串零碎的操作都是對同一個(gè)對象加鎖,將會(huì)把鎖的范圍擴(kuò)展(粗化)到整個(gè)操作序列的外部。對于上一節(jié)的示例代碼就是擴(kuò)展到第一個(gè) append() 操作之前直至最后一個(gè) append() 操作之后,這樣只需要加鎖一次就可以了。

輕量級鎖

輕量級鎖跟偏向鎖是java1.6中引入的,并且規(guī)定鎖只可以升級而不能降級,這就意味著偏向鎖升級成輕量級鎖后不能降低為偏向鎖,這種策略是為了提高獲得鎖的效率。

Java對象頭通常由兩個(gè)部分組成,一個(gè)是Mark Word存儲(chǔ)對象的hashCode或者鎖信息,另一個(gè)是Class Metadata Address用于存儲(chǔ)對象類型數(shù)據(jù)的指針,如果對象是數(shù)組,還會(huì)有一部分存儲(chǔ)的是數(shù)據(jù)的長度。

對象頭中的Mark Word布局

輕量級鎖是相對于傳統(tǒng)的重量級鎖而言,它使用CAS操作來避免重量級鎖使用互斥量的開銷。對于大部分的鎖,在整個(gè)同步周期內(nèi)都是不存在晶振的,因此也就不需要使用互斥量進(jìn)行同步,可以先采用CAS操作進(jìn)行同步,如果CAS失敗了再改用互斥量進(jìn)行同步。

當(dāng)嘗試獲取一個(gè)鎖對象時(shí),如果鎖對象標(biāo)記為0 01,說明鎖對象的鎖未鎖定(unlock)狀態(tài),此時(shí)虛擬機(jī)在當(dāng)前線程的虛擬機(jī)棧創(chuàng)建Lock Record,然后使用CAS操作將對象的Mark Word更新為Lock Record指針。如果CAS操作成功了,那么線程就獲取了該對象上的鎖,并且對象的Mark Word的鎖標(biāo)記變?yōu)?0,表示該對象處于輕量級鎖狀態(tài)。

如果CAS操作失敗了,虛擬機(jī)首先會(huì)檢查對象的Mark Word是否指向當(dāng)前線程的虛擬機(jī)棧,如果是的話說明當(dāng)前線程已經(jīng)擁有了這個(gè)鎖對象,那就可以直接進(jìn)入同步塊執(zhí)行,否則說明這個(gè)鎖對象已經(jīng)被其他線程搶占了。如果有兩個(gè)以上的線程競爭同一個(gè)鎖,那輕量級鎖就不再有效,要膨脹為重量級鎖。

輕量級鎖的步驟如下:

線程1在執(zhí)行同步代碼塊之前,JVM會(huì)先在當(dāng)前線程的棧幀中創(chuàng)建一個(gè)空間用來存儲(chǔ)鎖記錄,然后再把對象頭中的Mark Word復(fù)制到該鎖記錄中,官方稱之為Displaced Mark Word。然后線程嘗試使用CAS將對象頭中的Mark Word 替換為指向鎖記錄的指針。如果成功,則獲得鎖,進(jìn)入步驟(3)。如果失敗執(zhí)行步驟(2)

線程自旋,自旋成功則獲得鎖,進(jìn)入步驟(3)。自旋失敗,則膨脹成為重量級鎖,并把鎖標(biāo)志位變?yōu)?0,線程阻塞進(jìn)入步驟(3)

鎖的持有線程執(zhí)行同步代碼,執(zhí)行完CAS替換Mark Word成功釋放鎖,如果CAS成功則流程結(jié)束,CAS失敗執(zhí)行步驟(4)

CAS執(zhí)行失敗說明期間有線程嘗試獲得鎖并自旋失敗,輕量級鎖升級為了重量級鎖,此時(shí)釋放鎖之后,還要喚醒等待的線程

偏向鎖

偏向鎖的思想是偏向于讓第一個(gè)獲取鎖對象的線程,在之后的獲取該鎖就不再需要進(jìn)行同步操作,甚至連CAS操作也不需要。

當(dāng)鎖對象第一次被線程獲得的時(shí)候,進(jìn)入偏向狀態(tài),標(biāo)記為1 01。同時(shí)使用CAS操作將線程ID記錄到Mark Word中,如果CAS操作成功,這個(gè)線程以后每次進(jìn)入這個(gè)鎖相關(guān)的同步塊就不需要再進(jìn)行任何同步操作。

當(dāng)有另一個(gè)線程去嘗試獲取這個(gè)鎖對象,偏向狀態(tài)就宣告結(jié)束,此時(shí)偏向取消后恢復(fù)到未鎖定狀態(tài)或者輕量級鎖狀態(tài)。

偏向鎖獲得鎖的步驟分為:

初始時(shí)對象的Mark Word位為1,表示對象處于可偏向的狀態(tài),并且ThreadId為0,這是該對象是biasable&unbiased狀態(tài),可以加上偏向鎖進(jìn)入(2)。如果一個(gè)線程試圖鎖住biasable&biased并且ThreadID不等于自己ID的時(shí)候,由于鎖競爭應(yīng)該直接進(jìn)入(4)撤銷偏向鎖。

線程嘗試用CAS將自己的ThreadID放置到Mark Word中相應(yīng)的位置,如果CAS操作成功進(jìn)入到3),否則進(jìn)入(4)

進(jìn)入到這一步代表當(dāng)前沒有鎖競爭,Object繼續(xù)保持biasable狀態(tài),但此時(shí)ThreadID已經(jīng)不為0了,對象處于biasable&biased狀態(tài)

當(dāng)線程執(zhí)行CAS失敗,表示另一個(gè)線程當(dāng)前正在競爭該對象上的鎖。當(dāng)?shù)竭_(dá)全局安全點(diǎn)時(shí)(cpu沒有正在執(zhí)行的字節(jié))獲得偏向鎖的線程將被掛起,撤銷偏向(偏向位置0),如果這個(gè)線程已經(jīng)死了,則把對象恢復(fù)到未鎖定狀態(tài)(標(biāo)志位改為01),如果線程還活著,則把偏向鎖置0,變成輕量級鎖(標(biāo)志位改為00),釋放被阻塞的線程,進(jìn)入到輕量級鎖的執(zhí)行路徑中,同時(shí)被撤銷偏向鎖的線程繼續(xù)往下執(zhí)行。

運(yùn)行同步代碼塊

參考

Java并發(fā)編程實(shí)戰(zhàn)

淺談Java里的三種鎖:偏向鎖、輕量級鎖和重量級鎖

CS-Notes

【Java并發(fā)編程】之十:使用wait/notify/notifyAll實(shí)現(xiàn)線程間通信的幾點(diǎn)重要說明

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/71605.html

相關(guān)文章

  • 后臺(tái)開發(fā)常問面試題集錦(問題搬運(yùn)工,附鏈接)

    摘要:基礎(chǔ)問題的的性能及原理之區(qū)別詳解備忘筆記深入理解流水線抽象關(guān)鍵字修飾符知識點(diǎn)總結(jié)必看篇中的關(guān)鍵字解析回調(diào)機(jī)制解讀抽象類與三大特征時(shí)間和時(shí)間戳的相互轉(zhuǎn)換為什么要使用內(nèi)部類對象鎖和類鎖的區(qū)別,,優(yōu)缺點(diǎn)及比較提高篇八詳解內(nèi)部類單例模式和 Java基礎(chǔ)問題 String的+的性能及原理 java之yield(),sleep(),wait()區(qū)別詳解-備忘筆記 深入理解Java Stream流水...

    spacewander 評論0 收藏0
  • 后臺(tái)開發(fā)常問面試題集錦(問題搬運(yùn)工,附鏈接)

    摘要:基礎(chǔ)問題的的性能及原理之區(qū)別詳解備忘筆記深入理解流水線抽象關(guān)鍵字修飾符知識點(diǎn)總結(jié)必看篇中的關(guān)鍵字解析回調(diào)機(jī)制解讀抽象類與三大特征時(shí)間和時(shí)間戳的相互轉(zhuǎn)換為什么要使用內(nèi)部類對象鎖和類鎖的區(qū)別,,優(yōu)缺點(diǎn)及比較提高篇八詳解內(nèi)部類單例模式和 Java基礎(chǔ)問題 String的+的性能及原理 java之yield(),sleep(),wait()區(qū)別詳解-備忘筆記 深入理解Java Stream流水...

    xfee 評論0 收藏0
  • 后臺(tái)開發(fā)常問面試題集錦(問題搬運(yùn)工,附鏈接)

    摘要:基礎(chǔ)問題的的性能及原理之區(qū)別詳解備忘筆記深入理解流水線抽象關(guān)鍵字修飾符知識點(diǎn)總結(jié)必看篇中的關(guān)鍵字解析回調(diào)機(jī)制解讀抽象類與三大特征時(shí)間和時(shí)間戳的相互轉(zhuǎn)換為什么要使用內(nèi)部類對象鎖和類鎖的區(qū)別,,優(yōu)缺點(diǎn)及比較提高篇八詳解內(nèi)部類單例模式和 Java基礎(chǔ)問題 String的+的性能及原理 java之yield(),sleep(),wait()區(qū)別詳解-備忘筆記 深入理解Java Stream流水...

    makeFoxPlay 評論0 收藏0
  • 《深入理解java虛擬機(jī)》學(xué)習(xí)筆記系列——垃圾收集器&內(nèi)存分配策略

    摘要:虛擬機(jī)所處的區(qū)域,則表示它是屬于新生代收集器還是老年代收集器。虛擬機(jī)總共運(yùn)行了分鐘,其中垃圾收集花掉分鐘,那么吞吐量就是。收集器線程所占用的數(shù)量為。 本文主要從GC(垃圾回收)的角度試著對jvm中的內(nèi)存分配策略與相應(yīng)的垃圾收集器做一個(gè)介紹。 注:還是老規(guī)矩,本著能畫圖就不BB原則,盡量將各知識點(diǎn)通過思維導(dǎo)圖或者其他模型圖的方式進(jìn)行說明。文字僅記錄額外的思考與心得,以及其他特殊情況 內(nèi)存...

    calx 評論0 收藏0
  • 三年Java后端面試經(jīng)歷

    摘要:前言三年后端開發(fā)經(jīng)驗(yàn),面的目標(biāo)崗位是的高級后端開發(fā)。面試結(jié)束,應(yīng)該沒有后續(xù)。 前言 三年Java后端開發(fā)經(jīng)驗(yàn),面的目標(biāo)崗位是20k-35k的高級后端Java開發(fā)。 第一場,基本裸面,關(guān)于曾經(jīng)的項(xiàng)目部門答的不好,所以還是得好好準(zhǔn)備。 某C輪在線旅游公司 筆試 先做半個(gè)小時(shí)的筆試題,一共六個(gè)題目,兩道go語言的基礎(chǔ)題,一道斐波那契相關(guān),一道數(shù)據(jù)庫行列轉(zhuǎn)置,一道實(shí)現(xiàn)一個(gè)棧,還有一道是百萬計(jì)...

    darry 評論0 收藏0

發(fā)表評論

0條評論

最新活動(dòng)
閱讀需要支付1元查看
<