java線程相關基礎知識

多線程

多線程面試題TOP50
基本概念
  • 什麼是線程
  • 多線程的優點
  • 多線程的幾種實現方式
線程和進程的區別
引入線程的操作系統中,通常都是把進程作爲分配資源的基本單位,而把線程作爲獨立運行和獨立調度的基本單位

進程和線程的主要差別在於它們是不同的操作系統資源管理方式。進程有獨立的地址空間,一個進程崩潰後,在保護模式下不會對其它進程產生影響,而線程只是一個進程中的不同執行路徑。線程有自己的堆棧和局部變量,但線程之間沒有單獨的地址空間,一個線程死掉就等於整個進程死掉,所以多進程的程序要比多線程的程序健壯,但在進程切換時,耗費資源較大,效率要差一些。但對於一些要求同時進行並且又要共享某些變量的併發操作,只能用線程,不能用進程。
1) 簡而言之,一個程序至少有一個進程,一個進程至少有一個線程.
2) 線程的劃分尺度小於進程,使得多線程程序的併發性高。
3) 另外,進程在執行過程中擁有獨立的內存單元,而多個線程共享內存,從而極大地提高了程序的運行效率。
4) 線程在執行過程中與進程還是有區別的。每個獨立的線程有一個程序運行的入口、順序執行序列和程序的出口。但是線程不能夠獨立執行,必須依存在應用程序中,由應用程序提供多個線程執行控制。
5) 從邏輯角度來看,多線程的意義在於一個應用程序中,有多個執行部分可以同時執行。但操作系統並沒有將多個線程看做多個獨立的應用,來實現進程的調度和管理以及資源分配。這就是進程和線程的重要區別。
多線程的優點:使用併發的方式,提升程序運行效率
實現方式有三種:
(1)繼承Thread類,調用start()方法
(2)實現Runnable接口,new Thread(Runnable).start();
(3)實現Callable接口,可以返回結構Future也可以拋出異常

用 Runnable 還是 Thread
首選Runnable,因爲這樣這個類還可以繼承。
  • 什麼是線程安全
如果不用考慮這些線程在運行時環境下的調度和交替執行,也不需要進行額外的同步,或者在調用方進行任何其他的協調操作,調用這個對象的行爲都可以獲得正確的結果,那這個對象是線程安全的。
  1. Vector, SimpleDateFormat 是線程安全類嗎
  2. 哪些集合類是線程安全的
Vector:就比Arraylist多了個同步化機制(線程安全)。
Hashtable:就比Hashmap多了個線程安全。
ConcurrentHashMap:是一種高效但是線程安全的集合。
Stack:棧,也是線程安全的,繼承於Vector。

Vector是線程安全,SimpleDateFormat不是線程安全的。
  • 多線程中的忙循環是什麼
忙循環就是程序員用循環讓一個線程等待,不像傳統方法wait(), sleep() 或 yield() 它們都放棄了CPU控制,而忙循環不會放棄CPU,它就是在運行一個空循環。這麼做的目的是爲了保留CPU緩存,在多核系統中,一個等待線程醒來的時候可能會在另一個內核運行,這樣會重建緩存。

  • 進程間如何通訊,線程間如何通訊(後面補)
(1) 管道(pipe):管道是一種半雙工的通信方式,數據只能單向流動,而且只能在具有血緣關係的進程間使用。進程的血緣關係通常指父子進程關係。
(2)有名管道(named pipe):有名管道也是半雙工的通信方式,但是它允許無親緣關係進程間通信。
(3)信號量(semophore):信號量是一個計數器,可以用來控制多個進程對共享資源的訪問。它通常作爲一種鎖機制,防止某進程正在訪問共享資源時,其他進程也訪問該資源。因此,主要作爲進程間以及同一進程內不同線程之間的同步手段。
(4)消息隊列(message queue):消息隊列是由消息組成的鏈表,存放在內核中 並由消息隊列標識符標識。消息隊列克服了信號傳遞信息少,管道只能承載無格式字節流以及緩衝區大小受限等缺點。
(5)信號(signal):信號是一種比較複雜的通信方式,用於通知接收進程某一事件已經發生。
(6)共享內存(shared memory):共享內存就是映射一段能被其他進程所訪問的內存,這段共享內存由一個進程創建,但多個進程都可以訪問,共享內存是最快的IPC方式,它是針對其他進程間的通信方式運行效率低而專門設計的。它往往與其他通信機制,如信號量配合使用,來實現進程間的同步和通信。
(7)套接字(socket):套接口也是一種進程間的通信機制,與其他通信機制不同的是它可以用於不同及其間的進程通信。
  • 什麼是多線程環境下的僞共享(false sharing) 
僞共享的非標準定義爲:緩存系統中是以緩存行(cache line)爲單位存儲的,當多線程修改互相獨立的變量時,如果這些變量共享同一個緩存行,就會無意中影響彼此的性能,這就是僞共享。

  • 同步和異步有何異同,在什麼情況下分別使用他們?舉例說明
如果數據將在線程間共享。例如正在寫的數據以後可能被另一個線程讀到,或者正在讀的數據可能已經被另一個線程寫過了,那麼這些數據就是共享數據,必須進行同步存取
當應用程序在對象上調用了一個需要花費很長時間來執行的方法,並且不希望讓程序等待方法的返回時,就應該使用異步編程,在很多情況下采用異步途徑往往更有效率。
Java中交互方式分爲同步和異步兩種:
同步交互:指發送一個請求,需要等待返回,然後才能夠發送下一個請求,有個等待過程;
異步交互:指發送一個請求,不需要等待返回,隨時可以再發送下一個請求,即不需要等待。 
區別:一個需要等待,一個不需要等待,在部分情況下,我們的項目開發中都會優先選擇不需要等待的異步交互方式。
哪些情況建議使用同步交互呢?比如銀行的轉賬系統,對數據庫的保存操作等等,都會使用同步交互操作,其餘情況都優先使用異步交互


Current
  • ConcurrentHashMap 和 Hashtable的區別
Hashtable和ConcurrentHashMap有什麼分別呢?它們都可以用於多線程的環境,但是當Hashtable的大小增加到一定的時候,性能會急劇下降,因爲迭代時需要被鎖定很長的時間。因爲ConcurrentHashMap引入了分割(segmentation),不論它變得多麼大,僅僅需要鎖定map的某個部分,而其它的線程不需要等到迭代完成才能訪問map。簡而言之,在迭代的過程中,ConcurrentHashMap僅僅鎖定map的某個部分,而Hashtable則會鎖定整個map。

  • ArrayBlockingQueue的用法
CyclicBarrier 和 CountDownLatch有什麼不同?各自的內部原理和用法是什麼
Semaphore的用法
Java中的併發工具類:
(1)等待多線程完成的CountDownLatch
允許一個或多個線程等待其他線程完成操作。當調用countDownLatch的countDown方法,N就減1,await方法會阻塞當前線程,知道N變爲零。
也可以把countDown引用傳入 ,用於執行1個線程中的N個執行步驟
用法介紹:
  1. 創建一個計數器,設置初始值,CountdownLatch countDownLatch = new CountDownLatch(2);
  2. 在 等待線程 裏調用 countDownLatch.await() 方法,進入等待狀態,直到計數值變成 0;
  3. 在 其他線程 裏,調用 countDownLatch.countDown() 方法,該方法會將計數值減小 1;
  4. 當 其他線程 的 countDown() 方法把計數值變成 0 時,等待線程 裏的 countDownLatch.await() 立即退出,繼續執行下面的代碼。
實現代碼如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
private static void runDAfterABC() {
    int worker = 3;
    CountDownLatch countDownLatch = new CountDownLatch(worker);
    new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println("D is waiting for other three threads");
            try {
                countDownLatch.await();
                System.out.println("All done, D starts working");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }).start();
    for (char threadName='A'; threadName <= 'C'; threadName++) {
        final String tN = String.valueOf(threadName);
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(tN + "is working");
                try {
                    Thread.sleep(100);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                System.out.println(tN + "finished");
                countDownLatch.countDown();
            }
        }).start();
    }
}

下面是運行結果:

1
2
3
4
5
6
7
8
9
D is waiting for other three threads
A is working
B is working
C is working
 
A finished
C finished
B finished
All done, D starts working

其實簡單點來說,CountDownLatch 就是一個倒計數器,我們把初始計數值設置爲3,當 D 運行時,先調用 countDownLatch.await() 檢查計數器值是否爲 0,若不爲 0 則保持等待狀態;當A B C 各自運行完後都會利用countDownLatch.countDown(),將倒計數器減 1,當三個都運行完後,計數器被減至 0;此時立即觸發 D 的 await() 運行結束,繼續向下執行。
因此,CountDownLatch 適用於一個線程去等待多個線程的情況。

(2)CyclicBarrier
讓一組線程到達一個屏障(同步點)時被阻塞,直到最後一個線程到達屏障時,屏障纔會開門,所有被屏障攔截的線程纔會繼續執行。當到達屏障時,當前線程調用CyclicBarrier.await()方法,告訴CyclicBarrier已經到達屏障。
默認構造方法CyclicBarrier(int parties)
高級構造方法CyclicBarrier(int partie, Runnable barrierAction)在線程到達屏障時,優先執行barrierAction
兩者區別:
CountDownLatch的計數器只能使用一次,CyclicBarrier的計數器可以使用reset方法重置
CyclicBarrier還有其他方法,
getNumberWaiting(獲得CyclicBarrier阻塞的線程數量)
isBroken(阻塞的線程是否被中斷)
使用方法:
  1. 先創建一個公共 CyclicBarrier 對象,設置 同時等待 的線程數,CyclicBarrier cyclicBarrier = new CyclicBarrier(3);
  2. 這些線程同時開始自己做準備,自身準備完畢後,需要等待別人準備完畢,這時調用 cyclicBarrier.await(); 即可開始等待別人;
  3. 當指定的 同時等待 的線程數都調用了 cyclicBarrier.await();時,意味着這些線程都準備完畢好,然後這些線程才 同時繼續執行。
  4. 可以實現線程間相互等待
實現代碼如下,設想有三個跑步運動員,各自準備好後等待其他人,全部準備好後纔開始跑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
private static void runABCWhenAllReady() {
    int runner = 3;
    CyclicBarrier cyclicBarrier = new CyclicBarrier(runner);
    final Random random = new Random();
    for (char runnerName='A'; runnerName <= 'C'; runnerName++) {
        final String rN = String.valueOf(runnerName);
        new Thread(new Runnable() {
            @Override
            public void run() {
                long prepareTime = random.nextInt(10000) + 100;
                System.out.println(rN + "is preparing for time:" + prepareTime);
                try {
                    Thread.sleep(prepareTime);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                try {
                    System.out.println(rN + "is prepared, waiting for others");
                    cyclicBarrier.await(); // 當前運動員準備完畢,等待別人準備好
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
                System.out.println(rN + "starts running"); // 所有運動員都準備好了,一起開始跑
            }
        }).start();
    }
}

打印的結果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
A is preparing for time: 4131
B is preparing for time: 6349
C is preparing for time: 8206
 
A is prepared, waiting for others
 
B is prepared, waiting for others
 
C is prepared, waiting for others
 
C starts running
A starts running
B starts running

(3)Semaphore控制併發線程數數的信號量
可以用作流量控制,特別是公用資源有限的應用場景,比如數據庫連接。
Semaphore的acquire()方法獲取一個許可證  relese()方法歸還許可證,構造函數Semaphore(10)
其他方法
int availablePermits()返回此信號量當前可用的許可證數
int getQueueLength()返回正在等待獲取許可證的線程數數
boolean hasQueuedThreads()是否有線程正在等待獲取許可證
void reducePermits(int reduction)減少reduction個許可證,是protected方法
Collection getQueuedThreads()返回所有等待獲取許可證的線程集合,是protected方法
(4)線程交換數據的Exchanger
用於進行線程間的數據交換。可以用於遺傳算法,或者用於校對工作。
如果第一個線程先執行了exchange()方法,它會一直等待第二個線程也執行exchange()方法。當兩個線程都達到同步點時,這兩個線程就可以交換數據。


Thread
  • 啓動一個線程是調用 run() 還是 start() 方法?start() 和 run() 方法有什麼區別
  • 調用start()方法時會執行run()方法,爲什麼不能直接調用run()方法
  • sleep() 方法和對象的 wait() 方法都可以讓線程暫停執行,它們有什麼區別
  • yield方法有什麼作用?sleep() 方法和 yield() 方法有什麼區別
  • Java 中如何停止一個線程
  • stop() 和 suspend() 方法爲何不推薦使用
  • 如何在兩個線程間共享數據
  • 如何強制啓動一個線程
  • 如何讓正在運行的線程暫停一段時間
  • 什麼是線程組,爲什麼在Java中不推薦使用
  • 你是如何調用 wait(方法的)?使用 if 塊還是循環?爲什麼
生命週期
  • 有哪些不同的線程生命週期
  • 線程狀態,BLOCKED 和 WAITING 有什麼區別
  • 畫一個線程的生命週期狀態圖
ThreadLocal 用途是什麼,原理是什麼,用的時候要注意什麼

ThreadPool
  • 線程池是什麼?爲什麼要使用它
線程池:基本思想還是一種對象池的思想,開闢一塊內存空間,裏面存放了衆多(未死亡)的線程,池中線程執行調度由池管理器來處理。當有線程任務時,從池中取一個,執行完成後線程對象歸池,這樣可以避免反覆創建線程對象所帶來的性能開銷,節省了系統的資源。
線程池的好處:1.降低資源消耗。2.提高響應速度。3.提高線程的可管理性

  • 如何創建一個Java線程池
  • ThreadPool用法與優勢
  • 提交任務時,線程池隊列已滿時會發會生什麼
  • newCache 和 newFixed 有什麼區別?簡述原理。構造函數的各個參數的含義是什麼,比如 coreSize, maxsize 等
  • 線程池的實現策略
  • 線程池的關閉方式有幾種,各自的區別是什麼
  • 線程池中submit() 和 execute()方法有什麼區別?
  • 線程池的原理是什麼
ThreadPool的原理:當向一個線程池提交任務時,處理流程如下
(1)核心線程數 : 線程池判斷核心線程池裏的線程是否都在執行任務,如果不是,則創建一個新的工作線程,如果核心線程池裏的線程都在執行任務,則進入下個流程。
(2)隊列:線程池判斷工作隊列是否已經滿,如果工作隊列沒有滿,則將提交的任務存儲在這個工作隊列中
(3)線程池最大線程數:如果工作隊列慢了,則線程池判斷線程池中的線程是否都處於工作狀態,如果沒有,則創建一個新的工作線程來執行任務。如果已經滿了,則交給飽和策略來處理這個任務。
(4)飽和策略:

線程池的創建:參數
(1)corePoolSize:提交任務到線程池,會創建新的任務,直到達到corePoolSize大小
(2)maximumPoolSize:線程池最大數量,
(3)keepAliveTime:線程活動保持時間
(4)TimeUit線程活動保持時間的單位
(5)runnableTaskQueue任務隊列 
ArrayBlockingQueue基於數據的有界隊列   
LinkedBlockingQueue基於鏈表的無界隊列
synchronousQueue 不存儲元素的阻塞隊列 
PriorityBlockingQueue 優先級無限
(6)RejectedExecutionHandler飽和策略
AbortPolicy直接拋出異常
CallerRunsPolicy 只用調用者所在線程來運行任務
DiscardOldestPolicy:丟棄隊列裏最近的一個任務,並執行當前任務。
DiscardPolicy不處理,丟棄掉。

提交任務submit和execute方法
execute用於提交不需要返回值的任務
submit用於提交需要返回值的任務,線程池會返回一個future類型的對象,通過這個future對象可以判斷任務是否執行成功。
future.get()獲取返回值。

關閉線程池
原理:遍歷線程池中的工作線程,然後逐個調用線程的interrupt方法來中斷線程
shutdown關閉所有,不關閉正在執行的任務
shutdownNow關閉所有包括正在執行的任務


Executor接口
ThreadPoolExecutor        corePool        maximumPool    keepAliveTime          BlockingPool       RejectedExecutionHandler    
FixedThreadPool              nThreads           nThreads                   0                 LinkedBlockingQueue           無
SingleThreadExecutor      1                        1                                0                   LinkedBlockingQueue          無
CachedThreadPool            0                Integer.MAX_VALUE       60秒              SynchronousQueue              

CachedThreadPool
(1)執行SynchronousQueue.offer(Runnable task)方法,如果有空閒線程正在執行Synchronous.poll方法,執行execute
(2)如果maximumPool中沒有線程執行poll,則創建一個線程執行execute方法
(3)步驟2中新創建的線程執行完後,會執行poll方法,這個poll讓空閒線程最多等待60秒,如果60秒內有新任務則執行execute否則空閒線程終止。    
ScheduledThreadPoolExecutor詳解執行步驟
(1)獲取任務,獲取已經到期的任務(DelayQueue.take())  
(2)執行ScheduledFutureTask
(3)修改ScheduledFutureTask的time變量爲下次將要被執行的時間。
FutureTask
三種狀態
未啓動
已經啓動
已完成
 方法執行示意圖

線程調度
  • Java中用到的線程調度算法是什麼
一般線程調度模式分爲兩種——搶佔式調度和協同式調度。搶佔式調度指的是每條線程執行的時間、線程的切換都由系統控制,系統控制指的是在系統某種運行機制下,可能每條線程都分同樣的執行時間片,也可能是某些線程執行的時間片較長,甚至某些線程得不到執行的時間片。在這種機制下,一個線程的堵塞不會導致整個進程堵塞。協同式調度指某一線程執行完後主動通知系統切換到另一線程上執行,這種模式就像接力賽一樣,一個人跑完自己的路程就把接力棒交接給下一個人,下個人繼續往下跑。線程的執行時間由線程本身控制,線程切換可以預知,不存在多線程同步問題,但它有一個致命弱點:如果一個線程編寫有問題,運行到一半就一直堵塞,那麼可能導致整個系統崩潰。
JVM的線程調度是搶佔式的。
  • 什麼是多線程中的上下文切換
即使是單核CPU也支持多線程執行代碼,CPU通過給每個線程分配CPU時間片來實現這個機制。時間片是CPU分配給各個線程的時間,因爲時間片非常短,所以CPU通過不停地切換線程執行,讓我們感覺多個線程時同時執行的,時間片一般是幾十毫秒(ms)。
CPU通過時間片分配算法來循環執行任務,當前任務執行一個時間片後會切換到下一個任務。但是,在切換前會保存上一個任務的狀態,以便下次切換回這個任務時,可以再次加載這個任務的狀態,從任務保存到再加載的過程就是一次上下文切換
  • 你對線程優先級的理解是什麼
線程會被分配到若干時間片,當線程的時間片用完了就會發生線程調度,並等待着下次分配。線程分配到的時間片多少也就決定了線程使用處理器資源的多少,而線程優先級就是決定線程需要多或者少分配一些處理器資源的線程屬性。
什麼是線程調度器 (Thread Scheduler) 和時間分片 (Time Slicing)
線程調度器是一個操作系統服務,它負責爲Runnable狀態的線程分配CPU時間。一旦我們創建一個線程並啓動它,它的執行便依賴於線程調度器的實現。時間分片是指將可用的CPU時間分配給可用的Runnable線程的過程。分配CPU時間可以基於線程優先級或者線程等待的時間。線程調度並不受到Java虛擬機控制,所以由應用程序來控制它是更好的選擇(也就是說不要讓你的程序依賴於線程的優先級)。
線程同步
  • 請說出你所知的線程同步的方法
  • synchronized 的原理是什麼
  • synchronized 和 ReentrantLock 有什麼不同
  • 什麼場景下可以使用 volatile 替換 synchronized  (currentHashMap的底層實現就用volatile代替了synchronized)
  • 有T1,T2,T3三個線程,怎麼確保它們按順序執行?怎樣保證T2在T1執行完後執行,T3在T2執行完後執行
  • 同步塊內的線程拋出異常會發生什麼
  • 當一個線程進入一個對象的 synchronized 方法A 之後,其它線程是否可進入此對象的 synchronized 方法B
  • 使用 synchronized 修飾靜態方法和非靜態方法有什麼區別
  • 如何從給定集合那裏創建一個 synchronized 的集合
我們可以使用Collections.synchronizedCollection(Collection c)根據指定集合來獲取一個synchronized(線程安全的)集合。
線程同步的方法:
(1)synchronized關鍵字和volatile
volatile關鍵字用來修飾成員變量,保證內存的可見性。
對象,監視器,同步隊列,執行線程之間的關係
任意線程對Object(Object由synchronized保護)的訪問,首先要獲得Object的監視器。如果獲取失敗,線程進入同步隊列,線程狀態編程BLOCKED。當訪問Object的前驅(獲得了鎖的線程)釋放了鎖,則該釋放操作喚醒阻塞在同步隊列中的線程,使其重新嘗試對監視器的獲取。
(2)等待通知機制
調用wait方法,同時釋放了lock的鎖。
等待通知範式如下:
synchronized(對象){
    while(條件不滿足){
        對象.wait()
    }
}
調用notify方法,通知時不釋放lock的鎖,直到當前線程釋放了lock後,waitThread才能從wait中返回。
synchronized(對象){
     改變條件
     對象.notifyAll()
}
等待隊列和同步隊列
waitThread調用wait()進入等待隊列,notifyThread調用notifyAll()將等待隊列中的線程移動到同步隊列,同步隊列中的線程爭奪Monitor,得到Monitor的線程繼續執行,如果是notify()方法,這個對象的同隊列中只有一個線程,那就直接執行。
(3)管道
面向字節
PipedOutputStream
PipedIntputStream
面向字符
PipedReader
PipedWriter
(4)Thread.join()
線程A中執行了join:當前線程A等待thread線程終止之後才能從thread.join()中返回。
synchronized join(){
        while(isAlive()){
            wait(0);
        }
}
當線程終止時,會調用線程自身的notifyAll方法,會通知所有等待在該線程對象上的線程
(5)ThreadLocal的使用
線程局部變量
每個線程都有一個LocalMap的數據結構,ThreadLocal對象爲建,任意對象爲值的存儲結構。
一個線程可以根據一個ThreadLocal對象查詢到綁定在這個線程上的一個值。
可以通過set(T)來設置這個值。通過get(T)獲取到原來設置的值。


  • Java Concurrency API 中 的 Lock 接口是什麼?對比同步它有什麼優勢
  • Lock 與 Synchronized 的區別?Lock 接口比 synchronized 塊的優勢是什麼
  • ReadWriteLock是什麼?
  • 鎖機制有什麼用
  • 什麼是樂觀鎖(Optimistic Locking)?如何實現樂觀鎖?如何避免ABA問題
  • 解釋以下名詞:重排序,自旋鎖,偏向鎖,輕量級鎖,可重入鎖,公平鎖,非公平鎖,樂觀鎖,悲觀鎖
  • 什麼時候應該使用可重入鎖
  • 簡述鎖的等級方法鎖、對象鎖、類鎖
  • Java中活鎖和死鎖有什麼區別?
  • 什麼是死鎖(Deadlock)?導致線程死鎖的原因?如何確保 N 個線程可以訪問 N 個資源同時又不導致死鎖
  • 死鎖與活鎖的區別,死鎖與飢餓的區別
  • 怎麼檢測一個線程是否擁有鎖
  • 如何實現分佈式鎖
  • 有哪些無鎖數據結構,他們實現的原理是什麼
  • 讀寫鎖可以用於什麼應用場景
使用版本號避免ABA問題
AtomicStampedReference 來解決ABA問題,這個類的compareAndSet方法的作用是首先檢查當前引用是否等於預期引用,並且檢查當前標識是否等於預期標誌。如果全部相等,則以院子方式將該引用和該標誌的值設置爲給定的更新值。 
Lock和Synchronized的區別是什麼?
(1)Lock可以非阻塞的獲取鎖
(2)能被中斷地獲取鎖
(3)可以超時獲取鎖

AbstractQueuedSynchronizer隊列同步器(AQS)
是用來構建鎖或者其他同步組件的基礎框架,它使用了一個int成員變量表示同步狀態,通過內置的FIFO隊列來完成資源獲取線程的排隊工作。
可以用三種方法來修改同步狀態
getState()獲取當前同步狀態
setState(int newState)設置當前同步狀態
compareAndSetState(int expect, int update)使用CAS設置當前狀態,該方法能夠保證狀態設置的原子性。
通過將操作代理在自定義的隊列同步器上,可以方便的實現一個自定義同步組件。

ReentrantLock可重入鎖(也是排他鎖),允許鎖上加鎖,默認是以非公平性鎖來獲取鎖
在nonfairTryAcquire方法獲得鎖中,通過判斷當前線程是否爲獲取鎖的線程來決定獲取操作是否成功,如果是,將同步狀態值進行增加並返回true。
在tryRelease方法找那個釋放所,將c減去傳入參數releases,當c==0時才釋放鎖。
如果是公平性鎖,會在tryAcquire中CAS的判斷處增加hasQueuedPredecessors方法判斷是否有前驅節點。

公平獲得鎖:在絕對時間上,先對鎖進行獲取的請求一定先被滿足。所得獲取順序符合請求的絕對時間順序,FIFO
非公平獲得鎖
公平鎖保證了鎖的獲取按照FIFO原則,而代價是進行大量的線程切換。
非公平性鎖雖然可能造成線程“飢餓”,但極少的線程切換,保證了其更大的吞吐量,提高了執行效率。

讀寫鎖ReentrantReadWriteLock
排他鎖的概念:在同一時刻只允許一個線程進行訪問。
而讀寫鎖在同一時刻可以允許多個讀線程訪問,但是在寫線程訪問時,所有的讀線程和其他的寫線程軍被阻塞。
讀寫鎖支持:
(1)公平獲取鎖和非公平獲取鎖(默認)
(2)重進入
(3)鎖降級:指寫鎖降級稱爲讀鎖,具體是指把持住(當前擁有的)寫鎖,再獲取到讀鎖,隨後釋放(先前擁有的)寫鎖的過程。
讀寫鎖的實現: 
維護一個整型變量,高16位表示讀,低16位表示寫。

寫鎖的獲取與釋放
如果存在讀鎖,則寫鎖不能被獲取,因爲讀寫鎖要確保寫鎖的操作對讀鎖可見,如果允許讀鎖在已被獲取的情況下對寫鎖的獲取,那麼正在運行的其他讀線程就無法感知到當前寫線程的操作。因此,只有等待其他線程都釋放了讀鎖,寫鎖才能被當前線程獲取,而寫鎖一旦被獲取,則其他讀寫線程的後序訪問均被阻塞。

讀鎖的獲取與釋放
讀狀態時所有線程獲取讀次數的總和。
讀鎖的次數保存在ThreadLocal中,由線程自身維護。
讀鎖的獲取,如果其他線程獲得了寫鎖,則當前線程獲取讀鎖失敗,進入等待狀態。如果當前線程獲取了寫鎖或者寫鎖未被獲取,則當前線程增加讀狀態,成功獲取讀鎖。
讀鎖的釋放(線程安全,可能有多個讀線程同時釋放讀鎖),減少值是1<<16

鎖降級:其中的讀鎖獲取是必須要的,保證數據可見性。因爲當前線程在釋放寫鎖之前獲取讀鎖,可以阻塞其他寫線程獲取寫鎖進行數據更新,保證數據時正確的。
不支持鎖升級,也是因爲要保證數據可見性,如果讀鎖已經被多個線程獲取,如果寫線程更新數據對於其他讀線程不可見,讀出的數據時有問題的。(而且在讀鎖被持有的時候,是不能獲取到寫鎖的)

LockSupport工具
park()撤銷線程的許可
unpark()給線程許可(permit)

Condition接口


Executors類是什麼? Executor和Executors的區別
什麼是Java線程轉儲(Thread Dump),如何得到它
如何在Java中獲取線程堆棧
說出 3 條在 Java 中使用線程的最佳實踐
在線程中你怎麼處理不可捕捉異常
實際項目中使用多線程舉例。你在多線程環境中遇到的常見的問題是什麼?你是怎麼解決它的
請說出與線程同步以及線程調度相關的方法
程序中有3個 socket,需要多少個線程來處理
假如有一個第三方接口,有很多個線程去調用獲取數據,現在規定每秒鐘最多有 10 個線程同時調用它,如何做到
如何在 Windows 和 Linux 上查找哪個線程使用的 CPU 時間最長
如何確保 main() 方法所在的線程是 Java 程序最後結束的線程
非常多個線程(可能是不同機器),相互之間需要等待協調才能完成某種工作,問怎麼設計這種協調方案
你需要實現一個高效的緩存,它允許多個用戶讀,但只允許一個用戶寫,以此來保持它的完整性,你會怎樣去實現它


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章