1. Callable接口
類似于Runnable接口,Runnable描述的任務,不帶返回值;Callable描述的任務帶返回值。
public class Test {//創建線程,計算1+2+...+1000public static void main(String[] args) throws ExecutionException, InterruptedException {//使用Callable定義一個任務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構造方法不能直接傳callable,需要借助futureTaskThread t = new Thread(futureTask);t.start();//獲取線程計算結果//get方法會阻塞等待直到call方法計算完畢,get才會返回System.out.println(futureTask.get());}
}
2. ReentrantLock
和synchronized一樣是可重入鎖,但是兩者又有些不同,可以說reentrantLock是對synchronized的補充。
核心方法
- lock()加鎖
- unlock()解鎖
- tryLock()嘗試加鎖
和synchronized相比的缺點
進入synchronized內自動加鎖,出了synchronized自動解鎖。而reentrantLock需要手動加鎖,解鎖。這就可能出現解鎖失敗
public static void main(String[] args) {ReentrantLock locker = new ReentrantLock();locker.lock();if(true) {return;}locker.unlock();
}
上面代碼直接return沒有執行unlock()方法,解決方法就是使用try finally
public static void main(String[] args) {ReentrantLock locker = new ReentrantLock();try {locker.lock();if(true) {return;}} finally {locker.unlock();}
}
和synchronized相比的優點
- tryLock嘗試加鎖,可以設置加鎖等待時間。而synchronized采用的是“死等策略”,死等需要慎重。
- ReentrantLock可以實現成公平鎖,默認是非公平鎖
ReentrantLock locker = new ReentrantLock(true);
- synchronized是搭配wait/notify實現等待通知機制,隨機喚醒一個等待的線程。ReentrantLock是搭配Condition類實現,可以指定喚醒哪個等待的線程。(實例化多個Condition對象,使用await/signal方法)
面試題:談談兩者的區別
上面的優點+缺點
補充:synchronized是java關鍵字,底層是JVM實現的;而ReentrantLock是標準庫的一個類,底層基于java實現的
3.原子類
原子類的底層是基于CAS實現的,使用原子類,最常見的場景是多線程計數。例如求服務器有多少并發量。
4.信號量Semaphore
信號量相當于一個計數器,表示可用資源的個數。
信號量的基本操作:
- P操作,申請一個資源
- V操作,釋放一個資源
當計數為0的時候,繼續P操作,就會阻塞等待到其他線程V操作。
信號量可用視為一個廣義的鎖,而鎖相當于一個可用資源為1的信號量。
public static void main(String[] args) throws InterruptedException {//3個可用資源的信號量Semaphore semaphore = new Semaphore(3);//P操作,申請一個資源semaphore.acquire();System.out.println("p操作");//V操作釋放一個資源semaphore.release();System.out.println("V操作");semaphore.acquire();System.out.println("p操作1");semaphore.acquire();System.out.println("p操作2");semaphore.acquire();System.out.println("p操作3");semaphore.acquire();System.out.println("p操作4");}
發現“p操作4”沒有打印,可用資源只有3,前面已經申請3次了,所以沒打印,只能阻塞等待到釋放資源。
5.CountDownLatch
直譯過來就是“計數關閉門閥”,很難理解對吧?舉個例子馬上讓你通透。
5名選手進行百米比賽的時候,當最后一個選手撞線比賽才結束。使用CountDownLatch也是類似,每個選手撞線的時候,就調用countDown方法,當撞線次數達到選手個數,就認為比賽結束。
在舉個例子,使用多線程下載一個很大的文件,就切分成多個部分,每個線程負責下載一個部分,當一個線程下載完畢,就調用countDown方法,當所有線程都下載完畢,整個文件就下載完畢。
public static void main(String[] args) throws InterruptedException {//10個選手參加比賽CountDownLatch countDownLatch = new CountDownLatch(5);//創建10個線程來執行任務for (int i = 0; i < 5; i++) {Thread t = new Thread(() -> {System.out.println("選手出發" + Thread.currentThread().getName());try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("選手到達" + Thread.currentThread().getName());//選手撞線countDownLatch.countDown();});t.start();}//阻塞等待,直到所有選手都撞線,才能解除阻塞countDownLatch.await();System.out.println("比賽結束");
}