目錄
- 🌴Callable 接口
- 🎍ReentrantLock
- 🍀原子類
- 🌳線程池
- 🌲信號量 Semaphore
- ??CountDownLatch、
- ?相關面試題
🌴Callable 接口
Callable 是?個 interface . 相當于把線程封裝了?個 “返回值”. ?便程序猿借助多線程的?式計算結
果.
**代碼示例: **
創建線程計算 1 + 2 + 3 + … + 1000, 不使? Callable 版本。
? 創建?個類 Result , 包含?個 sum 表?最終結果, lock 表?線程同步使?的鎖對象.
? main ?法中先創建 Result 實例, 然后創建?個線程 t. 在線程內部計算 1 + 2 + 3 + … + 1000.
? 主線程同時使? wait 等待線程 t 計算結束. (注意, 如果執?到 wait 之前, 線程 t 已經計算完了, 就不
必等待了).
? 當線程 t 計算完畢后, 通過 notify 喚醒主線程, 主線程再打印結果.
public class Demo {static class Result {public int sum = 0;public Object lock = new Object();}public static void main(String[] args) throws InterruptedException {Result result = new Result();Thread t = new Thread() {@Overridepublic void run() {int sum = 0;for (int i = 1; i <= 1000; i++) {sum += i;}synchronized (result.lock) {result.sum = sum;result.lock.notify();}}};t.start();synchronized (result.lock) {while (result.sum == 0) {result.lock.wait();}System.out.println(result.sum);}}
}
可以看到, 上述代碼需要?個輔助類 Result, 還需要使??系列的加鎖和 wait notify 操作, 代碼復雜,
容易出錯.
使? Callable 版本代碼示例
? 創建?個匿名內部類, 實現 Callable 接?. Callable 帶有泛型參數. 泛型參數表?返回值的類型.
? 重寫 Callable 的 call ?法, 完成累加的過程. 直接通過返回值返回計算結果.
? 把 callable 實例使? FutureTask 包裝?下.
? 創建線程, 線程的構造?法傳? FutureTask . 此時新線程就會執? FutureTask 內部的 Callable 的
call ?法, 完成計算. 計算結果就放到了 FutureTask 對象中.
? 在主線程中調? futureTask.get() 能夠阻塞等待新線程計算完畢. 并獲取到 FutureTask 中的
結果.
public class CallableTest {public static void main(String[] args) throws ExecutionException, InterruptedException {Callable<Integer> callable = new Callable<Integer>() {@Overridepublic Integer call() throws Exception {int sum = 0;for(int i = 1; i<=1000; i++){sum+=i;}return sum;}};FutureTask<Integer> futuretask = new FutureTask<>(callable);Thread t = new Thread(futuretask);t.start();int result = futuretask.get();System.out.println(result);}
}
可以看到, 使? Callable 和 FutureTask 之后, 代碼簡化了很多, 也不必?動寫線程同步代碼了.
理解 Callable
Callable 和 Runnable 相對, 都是描述?個 “任務”. Callable 描述的是帶有返回值的任務, Runnable
描述的是不帶返回值的任務.
Callable 通常需要搭配 FutureTask 來使?. FutureTask ?來保存 Callable 的返回結果. 因為
Callable 往往是在另?個線程中執?的, 啥時候執?完并不確定.
FutureTask 就可以負責這個等待結果出來的?作.
理解 FutureTask
想象去吃?辣燙. 當餐點好后, 后廚就開始做了. 同時前臺會給你?張 “?票” . 這個?票就是
FutureTask. 后?我們可以隨時憑這張?票去查看??的這份?辣燙做出來了沒.
🎍ReentrantLock
可重?互斥鎖. 和 synchronized 定位類似, 都是?來實現互斥效果, 保證線程安全.
ReentrantLock 也是可重?鎖. “Reentrant” 這個單詞的原意就是 “可重?”
ReentrantLock 的?法:
? lock(): 加鎖, 如果獲取不到鎖就死等.
? trylock(超時時間): 加鎖, 如果獲取不到鎖, 等待?定的時間之后就放棄加鎖.
? unlock(): 解鎖
ReentrantLock lock = new ReentrantLock();
-----------------------------------------
lock.lock();
try { // working
} finally { lock.unlock()
}
ReentrantLock 和 synchronized 的區別:
? synchronized 是?個關鍵字, 是 JVM 內部實現的(?概率是基于 C++ 實現). ReentrantLock 是標準
庫的?個類, 在 JVM 外實現的(基于 Java 實現).
? synchronized 使?時不需要?動釋放鎖. ReentrantLock 使?時需要?動釋放. 使?起來更靈活, 但
是也容易遺漏 unlock.
? synchronized 在申請鎖失敗時, 會死等. ReentrantLock 可以通過 trylock 的?式等待?段時間就放
棄.
? synchronized 是?公平鎖, ReentrantLock 默認是?公平鎖. 可以通過構造?法傳??個 true 開啟
公平鎖模式.
// ReentrantLock 的構造?法
public ReentrantLock(boolean fair) {sync = fair ? new FairSync() : new NonfairSync();
}
? 更強?的喚醒機制. synchronized 是通過 Object 的 wait / notify 實現等待-喚醒. 每次喚醒的是?個
隨機等待的線程. ReentrantLock 搭配 Condition 類實現等待-喚醒, 可以更精確控制喚醒某個指定
的線程.
如何選擇使?哪個鎖?
? 鎖競爭不激烈的時候, 使? synchronized, 效率更?, ?動釋放更?便.
? 鎖競爭激烈的時候, 使? ReentrantLock, 搭配 trylock 更靈活控制加鎖的?為, ?不是死等.
? 如果需要使?公平鎖, 使? ReentrantLock
🍀原子類
原?類內部?的是 CAS 實現,所以性能要?加鎖實現 i++ ?很多。原?類有以下?個
? AtomicBoolean
? AtomicInteger
? AtomicIntegerArray
? AtomicLong
? AtomicReference
? AtomicStampedReference
以 AtomicInteger 舉例,常??法有
addAndGet(int delta); i += delta;
decrementAndGet(); --i;
getAndDecrement(); i--;
incrementAndGet(); ++i;
getAndIncrement(); i++;
使用示例:
public class AtomicTest {static AtomicInteger count = new AtomicInteger();public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(()->{for (int i = 0; i < 5000; i++) {count.getAndIncrement();}});Thread t2 = new Thread(()->{for (int i = 0; i < 5000; i++) {count.getAndIncrement();}});t1.start();t2.start();t1.join();t2.join();System.out.println(count.get());}
}
🌳線程池
雖然創建銷毀線程?創建銷毀進程更輕量, 但是在頻繁創建銷毀線程的時候還是會?較低效.
線程池就是為了解決這個問題. 如果某個線程不再使?了, 并不是真正把線程釋放, ?是放到?個 “池
?” 中, 下次如果需要?到線程就直接從池?中取, 不必通過系統來創建了.
ExecutorService 和 Executors
關于線程池這部分大家可以看博主之前的線程池詳解
🌲信號量 Semaphore
信號量, ?來表? “可?資源的個數”. 本質上就是?個計數器
理解信號量
可以把信號量想象成是停?場的展?牌: 當前有?位 100 個. 表?有 100 個可?資源.
當有?開進去的時候, 就相當于申請?個可?資源, 可??位就 -1 (這個稱為信號量的 P 操作)
當有?開出來的時候, 就相當于釋放?個可?資源, 可??位就 +1 (這個稱為信號量的 V 操作)
如果計數器的值已經為 0 了, 還嘗試申請資源, 就會阻塞等待, 直到有其他線程釋放資源.
Semaphore 的 PV 操作中的加減計數器操作都是原?的, 可以在多線程環境下直接使?.
代碼?例
? 創建 Semaphore ?例, 初始化為 4, 表?有 4 個可?資源.
? acquire ?法表?申請資源(P操作), release ?法表?釋放資源(V操作)
? 創建 20 個線程, 每個線程都嘗試申請資源, sleep 1秒之后, 釋放資源. 觀察程序的執?效果.
public class Test {public static void main(String[] args) {Semaphore semaphore = new Semaphore(4);Runnable runnable = new Runnable() {@Overridepublic void run() {try {System.out.println("申請資源");semaphore.acquire();System.out.println("我獲取到資源了");Thread.sleep(1000);System.out.println("我釋放資源了");semaphore.release();} catch (InterruptedException e) {e.printStackTrace();}}};for (int i = 0; i < 20; i++) {Thread t = new Thread(runnable);t.start();}}
??CountDownLatch、
同時等待 N 個任務執?結束.
好像跑步?賽,10個選?依次就位,哨聲響才同時出發;所有選?都通過終點,才能公布成績。
? 構造 CountDownLatch 實例, 初始化 10 表?有 10 個任務需要完成.
? 每個任務執?完畢, 都調? latch.countDown() . 在 CountDownLatch 內部的計數器同時?
減.
? 主線程中使? latch.await(); 阻塞等待所有任務執?完畢. 相當于計數器為 0 了.
public class Demo {public static void main(String[] args) throws Exception {CountDownLatch latch = new CountDownLatch(10);Runnable r = new Runable() {@Overridepublic void run() {try {Thread.sleep(Math.random() * 10000);latch.countDown();} catch (Exception e) {e.printStackTrace();}}};for (int i = 0; i < 10; i++) {new Thread(r).start();}// 必須等到 10 ?全部回來latch.await();System.out.println("?賽結束");}
}
?相關面試題
- 線程同步的?式有哪些?
synchronized, ReentrantLock, Semaphore 等都可以?于線程同步
- 為什么有了 synchronized 還需要 juc 下的 lock?
以 juc 的 ReentrantLock 為例,
? synchronized 使?時不需要?動釋放鎖. ReentrantLock 使?時需要?動釋放. 使?起來更靈活,
? synchronized 在申請鎖失敗時, 會死等. ReentrantLock 可以通過 trylock 的?式等待?段時間就放
棄.
? synchronized 是?公平鎖, ReentrantLock 默認是?公平鎖. 可以通過構造?法傳??個 true 開啟
公平鎖模式.
? synchronized 是通過 Object 的 wait / notify 實現等待-喚醒. 每次喚醒的是?個隨機等待的線程.
ReentrantLock 搭配 Condition 類實現等待-喚醒, 可以更精確控制喚醒某個指定的線程.
- AtomicInteger 的實現原理是什么?
基于 CAS 機制. 偽代碼如下:
class AtomicInteger {private int value;public int getAndIncrement() {int oldValue = value;while ( CAS(value, oldValue, oldValue+1) != true) {oldValue = value;}return oldValue;}
}
執?過程參考 “CAS詳解與應用” 博客.
- 信號量聽說過么?之前都?在過哪些場景下?
信號量, ?來表? “可?資源的個數”. 本質上就是?個計數器.
比特就業課
使?信號量可以實現 “共享鎖”, ?如某個資源允許 3 個線程同時使?, 那么就可以使? P 操作作為加
鎖, V 操作作為解鎖, 前三個線程的 P 操作都能順利返回, 后續線程再進? P 操作就會阻塞等待, 直到前
?的線程執?了 V 操作.
- 解釋?下 ThreadPoolExecutor 構造?法的參數的含義
參考博主的 線程池詳解 博客