亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

面試/筆試之多線程面試問(wèn)題集錦

  發(fā)布時(shí)間:2020-02-06 16:28:05   作者:書呆子Rico   我要評(píng)論
這篇文章主要介紹了面試/筆試之多線程面試問(wèn)題集錦,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧

寫在前面:

  找工作告一段落,期間經(jīng)歷了很多事情,也思考了許多問(wèn)題,最后也收獲了一些沉甸甸的東西 —— 成長(zhǎng)和一些來(lái)自阿里、百度、京東(sp)、華為等廠的Offer。好在一切又回到正軌,接下來(lái)要好好總結(jié)一番才不枉這段經(jīng)歷,遂將此過(guò)程中筆者的一些筆試/面試心得、干貨發(fā)表出來(lái),與眾共享之。在此特別要感謝CSDN以及廣大朋友的支持,我將堅(jiān)持記錄并分享自己所學(xué)、所想、所悟,央請(qǐng)大家不吝賜教,提出您寶貴的意見和建議,以期共同探討提高。

摘要:

  本文對(duì)面試/筆試過(guò)程中經(jīng)常會(huì)被問(wèn)到的一些關(guān)于并發(fā)編程的問(wèn)題進(jìn)行了梳理和總結(jié),包括線程池、并發(fā)控制鎖、并發(fā)容器和隊(duì)列同步器等基礎(chǔ)知識(shí)點(diǎn),一方面方便自己溫故知新,另一方面也希望為找工作的同學(xué)們提供一個(gè)復(fù)習(xí)參考。關(guān)于這塊內(nèi)容的初步了解和掌握,大家可以閱讀《Java并發(fā)編程的藝術(shù)》、《《Java多線程編程核心技術(shù)》和《Java并發(fā)編程實(shí)戰(zhàn)》三本書,重點(diǎn)掌握J(rèn).U.C并發(fā)框架。

版權(quán)聲明:

1、如何停止一個(gè)線程

使用volatile變量終止正常運(yùn)行的線程 + 拋異常法/Return法

組合使用interrupt方法與interruptted/isinterrupted方法終止正在運(yùn)行的線程 + 拋異常法/Return法

使用interrupt方法終止 正在阻塞中的 線程

2、何為線程安全的類?

  在線程安全性的定義中,最核心的概念就是 正確性。當(dāng)多個(gè)線程訪問(wèn)某個(gè)類時(shí),不管運(yùn)行時(shí)環(huán)境采用何種調(diào)度方式或者這些線程將如何交替執(zhí)行,并且在主調(diào)代碼中不需要任何額外的同步或協(xié)同,這個(gè)類都能表現(xiàn)出正確的行為,那么這個(gè)類就是線程安全的。

3、為什么線程通信的方法wait(), notify()和notifyAll()被定義在Object類里?

Object lock = new Object();
synchronized (lock) {
    lock.wait();
    ...
}

  Wait-notify機(jī)制是在獲取對(duì)象鎖的前提下不同線程間的通信機(jī)制。在Java中,任意對(duì)象都可以當(dāng)作鎖來(lái)使用,由于鎖對(duì)象的任意性,所以這些通信方法需要被定義在Object類里。

4、為什么wait(), notify()和notifyAll()必須在同步方法或者同步塊中被調(diào)用?

  wait/notify機(jī)制是依賴于Java中Synchronized同步機(jī)制的,其目的在于確保等待線程從Wait()返回時(shí)能夠感知通知線程對(duì)共享變量所作出的修改。如果不在同步范圍內(nèi)使用,就會(huì)拋出java.lang.IllegalMonitorStateException的異常。

5、并發(fā)三準(zhǔn)則

  • 異常不會(huì)導(dǎo)致死鎖現(xiàn)象:當(dāng)線程出現(xiàn)異常且沒(méi)有捕獲處理時(shí),JVM會(huì)自動(dòng)釋放當(dāng)前線程占用的鎖,因此不會(huì)由于異常導(dǎo)致出現(xiàn)死鎖現(xiàn)象,同時(shí)還會(huì)釋放CPU;
  • 鎖的是對(duì)象而非引用;
  • 有wait必有notify;

6、如何確保線程安全?

  在Java中可以有很多方法來(lái)保證線程安全,諸如:

  • 通過(guò)加鎖(Lock/Synchronized)保證對(duì)臨界資源的同步互斥訪問(wèn);
  • 使用volatile關(guān)鍵字,輕量級(jí)同步機(jī)制,但不保證原子性;
  • 使用不變類 和 線程安全類(原子類,并發(fā)容器,同步容器等)。

7、volatile關(guān)鍵字在Java中有什么作用

  volatile的特殊規(guī)則保證了新值能立即同步到主內(nèi)存,以及每次使用前立即從主內(nèi)存刷新,即保證了內(nèi)存的可見性,除此之外還能 禁止指令重排序。此外,synchronized關(guān)鍵字也可以保證內(nèi)存可見性。

  指令重排序問(wèn)題在并發(fā)環(huán)境下會(huì)導(dǎo)致線程安全問(wèn)題,volatile關(guān)鍵字通過(guò)禁止指令重排序來(lái)避免這一問(wèn)題。而對(duì)于Synchronized關(guān)鍵字,其所控制范圍內(nèi)的程序在執(zhí)行時(shí)獨(dú)占的,指令重排序問(wèn)題不會(huì)對(duì)其產(chǎn)生任何影響,因此無(wú)論如何,其都可以保證最終的正確性。

8、ThreadLocal及其引發(fā)的內(nèi)存泄露

  ThreadLocal是Java中的一種線程綁定機(jī)制,可以為每一個(gè)使用該變量的線程都提供一個(gè)變量值的副本,并且每一個(gè)線程都可以獨(dú)立地改變自己的副本,而不會(huì)與其它線程的副本發(fā)生沖突。

  每個(gè)線程內(nèi)部有一個(gè) ThreadLocal.ThreadLocalMap 類型的成員變量 threadLocals,這個(gè) threadLocals 存儲(chǔ)了與該線程相關(guān)的所有 ThreadLocal 變量及其對(duì)應(yīng)的值,也就是說(shuō),ThreadLocal 變量及其對(duì)應(yīng)的值就是該Map中的一個(gè) Entry,更直白地,threadLocals中每個(gè)Entry的Key是ThreadLocal 變量本身,而Value是該ThreadLocal變量對(duì)應(yīng)的值。

(1). ThreadLocal可能引起的內(nèi)存泄露

  下面是ThreadLocalMap的部分源碼,我們可以看出ThreadLocalMap里面對(duì)Key的引用是弱引用。那么,就存在這樣的情況:當(dāng)釋放掉對(duì)threadlocal對(duì)象的強(qiáng)引用后,map里面的value沒(méi)有被回收,但卻永遠(yuǎn)不會(huì)被訪問(wèn)到了,因此ThreadLocal存在著內(nèi)存泄露問(wèn)題。

 static class ThreadLocalMap {
        /**
         * The entries in this hash map extend WeakReference, using
         * its main ref field as the key (which is always a
         * ThreadLocal object).  Note that null keys (i.e. entry.get()
         * == null) mean that the key is no longer referenced, so the
         * entry can be expunged from table.  Such entries are referred to
         * as "stale entries" in the code that follows.
         */
        static class Entry extends WeakReference<ThreadLocal> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal k, Object v) {
                super(k);
                value = v;
            }
        }
        ...
    }

  看下面的圖示, 實(shí)線代表強(qiáng)引用,虛線代表弱引用。每個(gè)thread中都存在一個(gè)map,map的類型是上文提到的ThreadLocal.ThreadLocalMap,該map中的key為一個(gè)ThreadLocal實(shí)例。這個(gè)Map的確使用了弱引用,不過(guò)弱引用只是針對(duì)key,每個(gè)key都弱引用指向ThreadLocal對(duì)象。一旦把threadlocal實(shí)例置為null以后,那么將沒(méi)有任何強(qiáng)引用指向ThreadLocal對(duì)象,因此ThreadLocal對(duì)象將會(huì)被 Java GC 回收。但是,與之關(guān)聯(lián)的value卻不能回收,因?yàn)榇嬖谝粭l從current thread連接過(guò)來(lái)的強(qiáng)引用。 只有當(dāng)前thread結(jié)束以后, current thread就不會(huì)存在棧中,強(qiáng)引用斷開,Current Thread、Map及value將全部被Java GC回收。

           

  所以,得出一個(gè)結(jié)論就是:只要這個(gè)線程對(duì)象被Java GC回收,就不會(huì)出現(xiàn)內(nèi)存泄露。但是如果只把ThreadLocal引用指向null而線程對(duì)象依然存在,那么此時(shí)Value是不會(huì)被回收的,這就發(fā)生了我們認(rèn)為的內(nèi)存泄露。比如,在使用線程池的時(shí)候,線程結(jié)束是不會(huì)銷毀的而是會(huì)再次使用的,這種情形下就可能出現(xiàn)ThreadLocal內(nèi)存泄露。  

  Java為了最小化減少內(nèi)存泄露的可能性和影響,在ThreadLocal進(jìn)行g(shù)et、set操作時(shí)會(huì)清除線程Map里所有key為null的value。所以最怕的情況就是,ThreadLocal對(duì)象設(shè)null了,開始發(fā)生“內(nèi)存泄露”,然后使用線程池,線程結(jié)束后被放回線程池中而不銷毀,那么如果這個(gè)線程一直不被使用或者分配使用了又不再調(diào)用get/set方法,那么這個(gè)期間就會(huì)發(fā)生真正的內(nèi)存泄露。因此,最好的做法是:在不使用該ThreadLocal對(duì)象時(shí),及時(shí)調(diào)用該對(duì)象的remove方法去移除ThreadLocal.ThreadLocalMap中的對(duì)應(yīng)Entry。

9、什么是死鎖(Deadlock)?如何分析和避免死鎖?

  死鎖是指兩個(gè)以上的線程永遠(yuǎn)阻塞的情況,這種情況產(chǎn)生至少需要兩個(gè)以上的線程和兩個(gè)以上的資源。

  分析死鎖,我們需要查看Java應(yīng)用程序的線程轉(zhuǎn)儲(chǔ)。我們需要找出那些狀態(tài)為BLOCKED的線程和他們等待的資源。每個(gè)資源都有一個(gè)唯一的id,用這個(gè)id我們可以找出哪些線程已經(jīng)擁有了它的對(duì)象鎖。下面列舉了一些JDK自帶的死鎖檢測(cè)工具:

(1). Jconsole:JDK自帶的圖形化界面工具,主要用于對(duì) Java 應(yīng)用程序做性能分析和調(diào)優(yōu)。

           

(2). Jstack:JDK自帶的命令行工具,主要用于線程Dump分析。

           

(3). VisualVM:JDK自帶的圖形化界面工具,主要用于對(duì) Java 應(yīng)用程序做性能分析和調(diào)優(yōu)。

10、什么是Java Timer類?如何創(chuàng)建一個(gè)有特定時(shí)間間隔的任務(wù)?

  Timer是一個(gè)調(diào)度器,可以用于安排一個(gè)任務(wù)在未來(lái)的某個(gè)特定時(shí)間執(zhí)行或周期性執(zhí)行。TimerTask是一個(gè)實(shí)現(xiàn)了Runnable接口的抽象類,我們需要去繼承這個(gè)類來(lái)創(chuàng)建我們自己的定時(shí)任務(wù)并使用Timer去安排它的執(zhí)行。

Timer timer = new Timer();
timer.schedule(new TimerTask() {
        public void run() {
            System.out.println("abc");
        }
}, 200000 , 1000);

11、什么是線程池?如何創(chuàng)建一個(gè)Java線程池?

  一個(gè)線程池管理了一組工作線程,同時(shí)它還包括了一個(gè)用于放置等待執(zhí)行的任務(wù)的隊(duì)列。線程池可以避免線程的頻繁創(chuàng)建與銷毀,降低資源的消耗,提高系統(tǒng)的反應(yīng)速度。java.util.concurrent.Executors提供了幾個(gè)java.util.concurrent.Executor接口的實(shí)現(xiàn)用于創(chuàng)建線程池,其主要涉及四個(gè)角色:

  • 線程池:Executor
  • 工作線程:Worker線程,Worker的run()方法執(zhí)行Job的run()方法
  • 任務(wù)Job:Runable和Callable
  • 阻塞隊(duì)列:BlockingQueue

阻塞隊(duì)列:BlockingQueue

           

1). 線程池Executor

  Executor及其實(shí)現(xiàn)類是用戶級(jí)的線程調(diào)度器,也是對(duì)任務(wù)執(zhí)行機(jī)制的抽象,其將任務(wù)的提交與任務(wù)的執(zhí)行分離開來(lái),核心實(shí)現(xiàn)類包括ThreadPoolExecutor(用來(lái)執(zhí)行被提交的任務(wù))和ScheduledThreadPoolExecutor(可以在給定的延遲后執(zhí)行任務(wù)或者周期性執(zhí)行任務(wù))。Executor的實(shí)現(xiàn)繼承鏈條為:(父接口)Executor -> (子接口)ExecutorService -> (實(shí)現(xiàn)類)[ ThreadPoolExecutor + ScheduledThreadPoolExecutor ]。

2). 任務(wù)Runable/Callable

  Runnable(run)和Callable(call)都是對(duì)任務(wù)的抽象,但是Callable可以返回任務(wù)執(zhí)行的結(jié)果或者拋出異常。

3). 任務(wù)執(zhí)行狀態(tài)Future

  Future是對(duì)任務(wù)執(zhí)行狀態(tài)和結(jié)果的抽象,核心實(shí)現(xiàn)類是furtureTask (所以它既可以作為Runnable被線程執(zhí)行,又可以作為Future得到Callable的返回值) ;

           

(1). 使用Callable+Future獲取執(zhí)行結(jié)果

 ExecutorService executor = Executors.newCachedThreadPool();
    Task task = new Task();
    Future<Integer> result = executor.submit(task);
    System.out.println("task運(yùn)行結(jié)果" + result.get());    

    class Task implements Callable<Integer>{
            @Override
            public Integer call() throws Exception {
                System.out.println("子線程在進(jìn)行計(jì)算");
                Thread.sleep(3000);
                int sum = 0;
                for(int i=0;i<100;i++)
                    sum += i;
                return sum;
            }

(2). 使用Callable + FutureTask獲取執(zhí)行結(jié)果

  ExecutorService executor = Executors.newCachedThreadPool();
     Task task = new Task();
     FutureTask<Integer> futureTask = new FutureTask<Integer>(task);
     executor.submit(futureTask);  
    System.out.println("task運(yùn)行結(jié)果"+futureTask.get());

    class Task implements Callable<Integer>{
            @Override
            public Integer call() throws Exception {
                System.out.println("子線程在進(jìn)行計(jì)算");
                Thread.sleep(3000);
                int sum = 0;
                for(int i=0;i<100;i++)
                    sum += i;
                return sum;
            }

4). 四種常用的線程池

(1). FixedThreadPool

  用于創(chuàng)建使用固定線程數(shù)的ThreadPool,corePoolSize = maximumPoolSize = n(固定的含義),阻塞隊(duì)列為L(zhǎng)inkedBlockingQueue。

 public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

(2). SingleThreadExecutor

  用于創(chuàng)建一個(gè)單線程的線程池,corePoolSize = maximumPoolSize = 1,阻塞隊(duì)列為L(zhǎng)inkedBlockingQueue。

 public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

(3). CachedThreadPool

  用于創(chuàng)建一個(gè)可緩存的線程池,corePoolSize = 0, maximumPoolSize = Integer.MAX_VALUE,阻塞隊(duì)列為SynchronousQueue(沒(méi)有容量的阻塞隊(duì)列,每個(gè)插入操作必須等待另一個(gè)線程對(duì)應(yīng)的移除操作,反之亦然)。

   public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

(4). ScheduledThreadPoolExecutor

  用于創(chuàng)建一個(gè)大小無(wú)限的線程池,此線程池支持定時(shí)以及周期性執(zhí)行任務(wù)的需求。

public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE, 0, TimeUnit.NANOSECONDS,
              new DelayedWorkQueue());
    }

5). 線程池的飽和策略

  當(dāng)阻塞隊(duì)列滿了,且沒(méi)有空閑的工作線程,如果繼續(xù)提交任務(wù),必須采取一種策略處理該任務(wù),線程池提供了4種策略:

  • AbortPolicy:直接拋出異常,默認(rèn)策略;
  • CallerRunsPolicy:用調(diào)用者所在的線程來(lái)執(zhí)行任務(wù);
  • DiscardOldestPolicy:丟棄阻塞隊(duì)列中最老的任務(wù),并執(zhí)行當(dāng)前任務(wù);
  • DiscardPolicy:直接丟棄任務(wù);

  當(dāng)然也可以根據(jù)應(yīng)用場(chǎng)景實(shí)現(xiàn)RejectedExecutionHandler接口,自定義飽和策略,如記錄日志或持久化存儲(chǔ)不能處理的任務(wù)。

6). 線程池調(diào)優(yōu)

  • 設(shè)置最大線程數(shù),防止線程資源耗盡;
  • 使用有界隊(duì)列,從而增加系統(tǒng)的穩(wěn)定性和預(yù)警能力(飽和策略);
  • 根據(jù)任務(wù)的性質(zhì)設(shè)置線程池大?。篊PU密集型任務(wù)(CPU個(gè)數(shù)個(gè)線程),IO密集型任務(wù)(CPU個(gè)數(shù)兩倍的線程),混合型任務(wù)(拆分)。

12、CAS : CAS自旋volatile變量,是一種很經(jīng)典的用法。

  CAS,Compare and Swap即比較并交換,設(shè)計(jì)并發(fā)算法時(shí)常用到的一種技術(shù)。CAS有3個(gè)操作數(shù),內(nèi)存值V,舊的預(yù)期值A(chǔ),新值B。當(dāng)且僅當(dāng)預(yù)期值A(chǔ)和內(nèi)存值V相同時(shí),將內(nèi)存值V修改為B,否則什么都不做。CAS是通過(guò)unsafe類的compareAndSwap (JNI, Java Native Interface) 方法實(shí)現(xiàn)的,該方法包括四個(gè)參數(shù):第一個(gè)參數(shù)是要修改的對(duì)象,第二個(gè)參數(shù)是對(duì)象中要修改變量的偏移量,第三個(gè)參數(shù)是修改之前的值,第四個(gè)參數(shù)是預(yù)想修改后的值。

  CAS雖然很高效的解決原子操作,但是CAS仍然存在三大問(wèn)題:ABA問(wèn)題、循環(huán)時(shí)間長(zhǎng)開銷大和只能保證一個(gè)共享變量的原子操作。

ABA問(wèn)題:因?yàn)镃AS需要在操作值的時(shí)候檢查下值有沒(méi)有發(fā)生變化,如果沒(méi)有發(fā)生變化則更新,但是如果一個(gè)值原來(lái)是A,變成了B,又變成了A,那么使用CAS進(jìn)行檢查時(shí)會(huì)發(fā)現(xiàn)它的值沒(méi)有發(fā)生變化,但是實(shí)際上卻變化了。ABA問(wèn)題的解決思路就是使用版本號(hào)。在變量前面追加上版本號(hào),每次變量更新的時(shí)候把版本號(hào)加一,那么A-B-A 就會(huì)變成1A-2B-3A。

不適用于競(jìng)爭(zhēng)激烈的情形中:并發(fā)越高,失敗的次數(shù)會(huì)越多,CAS如果長(zhǎng)時(shí)間不成功,會(huì)極大的增加CPU的開銷。因此CAS不適合競(jìng)爭(zhēng)十分頻繁的場(chǎng)景。

只能保證一個(gè)共享變量的原子操作:當(dāng)對(duì)一個(gè)共享變量執(zhí)行操作時(shí),我們可以使用循環(huán)CAS的方式來(lái)保證原子操作,但是對(duì)多個(gè)共享變量操作時(shí),循環(huán)CAS就無(wú)法保證操作的原子性,這個(gè)時(shí)候就可以用鎖,或者有一個(gè)取巧的辦法,就是把多個(gè)共享變量合并成一個(gè)共享變量來(lái)操作。比如有兩個(gè)共享變量i=2,j=a,合并一下ij=2a,然后用CAS來(lái)操作ij。從Java1.5開始JDK提供了AtomicReference類來(lái)保證引用對(duì)象之間的原子性,因此可以把多個(gè)變量放在一個(gè)對(duì)象里來(lái)進(jìn)行CAS操作。

13、AQS : 隊(duì)列同步器

  隊(duì)列同步器(AbstractQueuedSynchronizer)是用來(lái)構(gòu)建鎖和其他同步組件的基礎(chǔ)框架,技術(shù)是 CAS自旋Volatile變量:它使用了一個(gè)Volatile成員變量表示同步狀態(tài),通過(guò)CAS修改該變量的值,修改成功的線程表示獲取到該鎖;若沒(méi)有修改成功,或者發(fā)現(xiàn)狀態(tài)state已經(jīng)是加鎖狀態(tài),則通過(guò)一個(gè)Waiter對(duì)象封裝線程,添加到等待隊(duì)列中,并掛起等待被喚醒。

  同步器是實(shí)現(xiàn)鎖的關(guān)鍵,子類通過(guò)繼承同步器并實(shí)現(xiàn)它的抽象方法來(lái)管理同步狀態(tài),利用同步器實(shí)現(xiàn)鎖的語(yǔ)義。特別地,鎖是面向鎖使用者的,它定義了使用者與鎖交互的接口,隱藏了實(shí)現(xiàn)細(xì)節(jié);同步器面向的是鎖的實(shí)現(xiàn)者,它簡(jiǎn)化了鎖的實(shí)現(xiàn)方式,屏蔽了同步狀態(tài)管理、線程排隊(duì)、等待與喚醒等底層操作。鎖和同步器很好地隔離了鎖的使用者與鎖的實(shí)現(xiàn)者所需關(guān)注的領(lǐng)域。

  一般來(lái)說(shuō),自定義同步器要么是獨(dú)占方式,要么是共享方式,他們也只需實(shí)現(xiàn)tryAcquire-tryRelease、tryAcquireShared-tryReleaseShared中的一種即可。但AQS也支持自定義同步器同時(shí)實(shí)現(xiàn)獨(dú)占和共享兩種方式,如ReentrantReadWriteLock。

  同步器的設(shè)計(jì)是基于 模板方法模式 的,也就是說(shuō),使用者需要繼承同步器并重寫指定的方法,隨后將同步器組合在自定義同步組件的實(shí)現(xiàn)中,并調(diào)用同步器提供的模板方法,而這些模板方法將會(huì)調(diào)用使用者重寫的方法。

           

  AQS維護(hù)了一個(gè)volatile int state(代表共享資源)和一個(gè)FIFO線程等待隊(duì)列(多線程爭(zhēng)用資源被阻塞時(shí)會(huì)進(jìn)入此隊(duì)列)。這里volatile是核心關(guān)鍵詞,具體volatile的語(yǔ)義,在此不述。state的訪問(wèn)方式有三種:getState()、setState()以及compareAndSetState()。

  AQS定義了兩種資源共享方式:Exclusive(獨(dú)占,只有一個(gè)線程能執(zhí)行,如ReentrantLock)和Share(共享,多個(gè)線程可同時(shí)執(zhí)行,如Semaphore/CountDownLatch)。不同的自定義同步器爭(zhēng)用共享資源的方式也不同。自定義同步器在實(shí)現(xiàn)時(shí)只需要實(shí)現(xiàn)共享資源state的獲取與釋放方式即可,至于具體線程等待隊(duì)列的維護(hù)(如獲取資源失敗入隊(duì)/喚醒出隊(duì)等),AQS已經(jīng)在頂層實(shí)現(xiàn)好了。自定義同步器實(shí)現(xiàn)時(shí)主要實(shí)現(xiàn)以下幾種方法:

isHeldExclusively():該線程是否正在獨(dú)占資源。只有用到condition才需要去實(shí)現(xiàn)它;

tryAcquire(int):獨(dú)占方式。嘗試獲取資源,成功則返回true,失敗則返回false;

tryRelease(int):獨(dú)占方式。嘗試釋放資源,成功則返回true,失敗則返回false;

tryAcquireShared(int):共享方式。嘗試獲取資源。負(fù)數(shù)表示失?。?表示成功,但沒(méi)有剩余可用資源;正數(shù)表示成功,且有剩余資源;

tryReleaseShared(int):共享方式。嘗試釋放資源,成功則返回true,失敗則返回false。

  以ReentrantLock為例,state初始化為0,表示未鎖定狀態(tài)。A線程lock()時(shí),會(huì)調(diào)用tryAcquire()獨(dú)占該鎖并將state+1。此后,其他線程再tryAcquire()時(shí)就會(huì)失敗,直到A線程unlock()到state=0(即釋放鎖)為止,其它線程才有機(jī)會(huì)獲取該鎖。當(dāng)然,釋放鎖之前,A線程自己是可以重復(fù)獲取此鎖的(state會(huì)累加),這就是可重入的概念。但要注意,獲取多少次就要釋放多么次,這樣才能保證state是能回到零態(tài)的。

14、Java Concurrency API中的Lock接口(Lock interface)是什么?對(duì)比同步它有什么優(yōu)勢(shì)?

  synchronized是Java的關(guān)鍵字,是Java的內(nèi)置特性,在JVM層面實(shí)現(xiàn)了對(duì)臨界資源的同步互斥訪問(wèn)。Synchronized的語(yǔ)義底層是通過(guò)一個(gè)monitor對(duì)象來(lái)完成的,線程執(zhí)行monitorenter/monitorexit指令完成鎖的獲取與釋放。而Lock是一個(gè)Java接口(API如下圖所示),是基于JDK層面實(shí)現(xiàn)的,通過(guò)這個(gè)接口可以實(shí)現(xiàn)同步訪問(wèn),它提供了比synchronized關(guān)鍵字更靈活、更廣泛、粒度更細(xì)的鎖操作,底層是由AQS實(shí)現(xiàn)的。二者之間的差異總結(jié)如下:

  • 實(shí)現(xiàn)層面:synchronized(JVM層面)、Lock(JDK層面)
  • 響應(yīng)中斷:Lock 可以讓等待鎖的線程響應(yīng)中斷,而使用synchronized時(shí),等待的線程會(huì)一直等待下去,不能夠響應(yīng)中斷;
  • 立即返回:可以讓線程嘗試獲取鎖,并在無(wú)法獲取鎖的時(shí)候立即返回或者等待一段時(shí)間,而synchronized卻無(wú)法辦到;
  • 讀寫鎖:Lock可以提高多個(gè)線程進(jìn)行讀操作的效率
  • 可實(shí)現(xiàn)公平鎖:Lock可以實(shí)現(xiàn)公平鎖,而sychronized天生就是非公平鎖
  • 顯式獲取和釋放:synchronized在發(fā)生異常時(shí),會(huì)自動(dòng)釋放線程占有的鎖,因此不會(huì)導(dǎo)致死鎖現(xiàn)象發(fā)生;而Lock在發(fā)生異常時(shí),如果沒(méi)有主動(dòng)通過(guò)unLock()去釋放鎖,則很可能造成死鎖現(xiàn)象,因此使用Lock時(shí)需要在finally塊中釋放鎖;

           

15、Condition

  Condition可以用來(lái)實(shí)現(xiàn)線程的分組通信與協(xié)作。以生產(chǎn)者/消費(fèi)者問(wèn)題為例,

wait/notify/notifyAll:在隊(duì)列為空時(shí),通知所有線程;在隊(duì)列滿時(shí),通知所有線程,防止生產(chǎn)者通知生產(chǎn)者,消費(fèi)者通知消費(fèi)者的情形產(chǎn)生。

await/signal/signalAll:將線程分為消費(fèi)者線程和生產(chǎn)者線程兩組:在隊(duì)列為空時(shí),通知生產(chǎn)者線程生產(chǎn);在隊(duì)列滿時(shí),通知消費(fèi)者線程消費(fèi)。

           

16、什么是阻塞隊(duì)列?如何使用阻塞隊(duì)列來(lái)實(shí)現(xiàn)生產(chǎn)者-消費(fèi)者模型?

  java.util.concurrent.BlockingQueue的特性是:當(dāng)隊(duì)列是空的時(shí),從隊(duì)列中獲取或刪除元素的操作將會(huì)被阻塞,或者當(dāng)隊(duì)列是滿時(shí),往隊(duì)列里添加元素的操作會(huì)被阻塞。特別地,阻塞隊(duì)列不接受空值,當(dāng)你嘗試向隊(duì)列中添加空值的時(shí)候,它會(huì)拋出NullPointerException。另外,阻塞隊(duì)列的實(shí)現(xiàn)都是線程安全的,所有的查詢方法都是原子的并且使用了內(nèi)部鎖或者其他形式的并發(fā)控制。

  BlockingQueue 接口是java collections框架的一部分,它主要用于實(shí)現(xiàn)生產(chǎn)者-消費(fèi)者問(wèn)題。特別地,SynchronousQueue是一個(gè)沒(méi)有容量的阻塞隊(duì)列,每個(gè)插入操作必須等待另一個(gè)線程的對(duì)應(yīng)移除操作,反之亦然。CachedThreadPool使用SynchronousQueue把主線程提交的任務(wù)傳遞給空閑線程執(zhí)行。

17、同步容器(強(qiáng)一致性)

  同步容器指的是 Vector、Stack、HashTable及Collections類中提供的靜態(tài)工廠方法創(chuàng)建的類。其中,Vector實(shí)現(xiàn)了List接口,Vector實(shí)際上就是一個(gè)數(shù)組,和ArrayList類似,但是Vector中的方法都是synchronized方法,即進(jìn)行了同步措施;Stack也是一個(gè)同步容器,它的方法也用synchronized進(jìn)行了同步,它實(shí)際上是繼承于Vector類;HashTable實(shí)現(xiàn)了Map接口,它和HashMap很相似,但是HashTable進(jìn)行了同步處理,而HashMap沒(méi)有。

  Collections類是一個(gè)工具提供類,注意,它和Collection不同,Collection是一個(gè)頂層的接口。在Collections類中提供了大量的方法,比如對(duì)集合或者容器進(jìn)行排序、查找等操作。最重要的是,在它里面提供了幾個(gè)靜態(tài)工廠方法來(lái)創(chuàng)建同步容器類,如下圖所示:

           

18、什么是CopyOnWrite容器(弱一致性)?

  CopyOnWrite容器即寫時(shí)復(fù)制的容器,適用于讀操作遠(yuǎn)多于修改操作的并發(fā)場(chǎng)景中。通俗的理解是當(dāng)我們往一個(gè)容器添加元素的時(shí)候,不直接往當(dāng)前容器添加,而是先將當(dāng)前容器進(jìn)行Copy,復(fù)制出一個(gè)新的容器,然后新的容器里添加元素,添加完元素之后,再將原容器的引用指向新的容器。這樣做的好處是我們可以對(duì)CopyOnWrite容器進(jìn)行并發(fā)的讀,而不需要加鎖,因?yàn)楫?dāng)前容器不會(huì)添加任何元素。所以CopyOnWrite容器也是一種讀寫分離的思想,讀和寫不同的容器。

  從JDK1.5開始Java并發(fā)包里提供了兩個(gè)使用CopyOnWrite機(jī)制實(shí)現(xiàn)的并發(fā)容器,它們是CopyOnWriteArrayList和CopyOnWriteArraySet。CopyOnWrite容器主要存在兩個(gè)弱點(diǎn):

容器對(duì)象的復(fù)制需要一定的開銷,如果對(duì)象占用內(nèi)存過(guò)大,可能造成頻繁的YoungGC和Full GC;

CopyOnWriteArrayList不能保證數(shù)據(jù)實(shí)時(shí)一致性,只能保證最終一致性。

19、ConcurrentHashMap (弱一致性)

  ConcurrentHashMap的弱一致性主要是為了提升效率,也是一致性與效率之間的一種權(quán)衡。要成為強(qiáng)一致性,就得到處使用鎖,甚至是全局鎖,這就與Hashtable和同步的HashMap一樣了。ConcurrentHashMap的弱一致性主要體現(xiàn)在以下幾方面:

get操作是弱一致的:get操作只能保證一定能看到已完成的put操作;

           

clear操作是弱一致的:在清除完一個(gè)segments之后,正在清理下一個(gè)segments的時(shí)候,已經(jīng)清理的segments可能又被加入了數(shù)據(jù),因此clear返回的時(shí)候,ConcurrentHashMap中是可能存在數(shù)據(jù)的。

public void clear() {
    for (int i = 0; i < segments.length; ++i)
        segments[i].clear();
}

ConcurrentHashMap中的迭代操作是弱一致的(未遍歷的內(nèi)容發(fā)生變化可能會(huì)反映出來(lái)):在遍歷過(guò)程中,如果已經(jīng)遍歷的數(shù)組上的內(nèi)容變化了,迭代器不會(huì)拋出ConcurrentModificationException異常。如果未遍歷的數(shù)組上的內(nèi)容發(fā)生了變化,則有可能反映到迭代過(guò)程中。

20、happens-before

  happens-before 指定了兩個(gè)操作間的執(zhí)行順序:如果 A happens before B,那么Java內(nèi)存模型將向程序員保證 —— A 的執(zhí)行順序排在 B 之前,并且 A 操作的結(jié)果將對(duì) B 可見,其具體包括如下8條規(guī)則:

程序順序規(guī)則:?jiǎn)尉€程內(nèi),按照程序代碼順序,書寫在前面的操作先行發(fā)生于書寫在后面的操作;

管程鎖定規(guī)則:一個(gè)unlock操作先行發(fā)生于對(duì)同一個(gè)鎖的lock操作;

volatile變量規(guī)則:對(duì)一個(gè)Volatile變量的寫操作先行發(fā)生于對(duì)這個(gè)變量的讀操作;

線程啟動(dòng)規(guī)則:Thread對(duì)象的start()方法先行發(fā)生于此線程的其他動(dòng)作;

線程中斷規(guī)則:對(duì)線程interrupt()方法的調(diào)用先行發(fā)生于被中斷線程的代碼檢測(cè)到中斷事件的發(fā)生;

線程終止規(guī)則:線程中所有的操作都先行發(fā)生于線程的終止檢測(cè),我們可以通過(guò)Thread.join()方法結(jié)束、Thread.isAlive()的返回值手段檢測(cè)到線程已經(jīng)終止執(zhí)行;

對(duì)象終結(jié)規(guī)則:一個(gè)對(duì)象的初始化完成先行發(fā)生于它的finalize()方法的開始;

傳遞規(guī)則:如果操作A先行發(fā)生于操作B,而操作B又先行發(fā)生于操作C,則可以得出操作A先行發(fā)生于操作C;

21、鎖優(yōu)化技術(shù)

  鎖優(yōu)化技術(shù)的目的在于線程之間更高效的共享數(shù)據(jù),解決競(jìng)爭(zhēng)問(wèn)題,更好提高程序執(zhí)行效率。

自旋鎖(上下文切換代價(jià)大):互斥鎖 -> 阻塞 –> 釋放CPU,線程上下文切換代價(jià)較大 + 共享變量的鎖定時(shí)間較短 == 讓線程通過(guò)自旋等一會(huì)兒,自旋鎖

鎖粗化(一個(gè)大鎖優(yōu)于若干小鎖):一系列連續(xù)操作對(duì)同一對(duì)象的反復(fù)頻繁加鎖/解鎖會(huì)導(dǎo)致不必要的性能損耗,建議粗化鎖
一般而言,同步范圍越小越好,這樣便于其他線程盡快拿到鎖,但仍然存在特例。

偏向鎖(有鎖但當(dāng)前情形不存在競(jìng)爭(zhēng)):消除數(shù)據(jù)在無(wú)競(jìng)爭(zhēng)情況下的同步原語(yǔ),提高帶有同步但無(wú)競(jìng)爭(zhēng)的程序性能。

鎖消除(有鎖但不存在競(jìng)爭(zhēng),鎖多余):JVM編譯優(yōu)化,將不存在數(shù)據(jù)競(jìng)爭(zhēng)的鎖消除

22、主線程等待子線程運(yùn)行完畢再運(yùn)行的方法

(1). Join

  Thread提供了讓一個(gè)線程等待另一個(gè)線程完成的方法 — join()方法。當(dāng)在某個(gè)程序執(zhí)行流程中調(diào)用其它線程的join()方法時(shí),調(diào)用線程將被阻塞,直到被join()方法加入的join線程執(zhí)行完畢為止,在繼續(xù)運(yùn)行。join()方法的實(shí)現(xiàn)原理是不停檢查join線程是否存活,如果join線程存活則讓當(dāng)前線程永遠(yuǎn)等待。直到j(luò)oin線程完成后,線程的this.notifyAll()方法會(huì)被調(diào)用。

(2). CountDownLatch

  Countdown Latch允許一個(gè)或多個(gè)線程等待其他線程完成操作。CountDownLatch的構(gòu)造函數(shù)接收一個(gè)int類型的參數(shù)作為計(jì)數(shù)器,如果你想等待N個(gè)點(diǎn)完成,這里就傳入N。當(dāng)我們調(diào)用countDown方法時(shí),N就會(huì)減1,await方法會(huì)阻塞當(dāng)前線程,直到N變成0。這里說(shuō)的N個(gè)點(diǎn),可以使用N個(gè)線程,也可以是1個(gè)線程里的N個(gè)執(zhí)行步驟。

           

(3). Sleep

  用sleep方法,讓主線程睡眠一段時(shí)間,當(dāng)然這個(gè)睡眠時(shí)間是主觀的時(shí)間,是我們自己定的,這個(gè)方法不推薦,但是在這里還是寫一下,畢竟是解決方法。

引用:

ThreadLocal可能引起的內(nèi)存泄露
java自帶的監(jiān)控工具VisualVM一
深入分析java線程池的實(shí)現(xiàn)原理
Java并發(fā)之AQS詳解
《為什么ConcurrentHashMap是弱一致的》

以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

最新評(píng)論