|
1. 是什么? LockSupport是JUC包下的一個(gè)類,是用來創(chuàng)建鎖和其他同步類的基本線程阻塞原語。 相信大多數(shù)人看了這句話也還是不太明白它到底是啥東西,那你還記得等待喚醒機(jī)制嗎?之前實(shí)現(xiàn)等待喚醒機(jī)制可以用wait/notify,可以用await/signal,這個(gè)LockSupport就是它們的改良版。 2. 等待喚醒機(jī)制: 先來回顧一下等待喚醒機(jī)制。 先看看用wait/notify實(shí)現(xiàn): private static Object lockObj = new Object();
private static void waitNotify() { new Thread(() -> { try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (lockObj){ System.out.println("線程" + Thread.currentThread().getName() + "進(jìn)來了"); try { lockObj.wait(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("線程" + Thread.currentThread().getName() + "被喚醒"); } }, "A").start();
new Thread(() -> { synchronized (lockObj){ System.out.println("線程" + Thread.currentThread().getName() + "進(jìn)來了"); lockObj.notify(); System.out.println("線程" + Thread.currentThread().getName() + "喚醒另一個(gè)線程"); } }, "B").start(); }
這段代碼就很簡(jiǎn)單了,線程A先wait,線程B去notify,線程B執(zhí)行完了A就被喚醒了,這就是最開始學(xué)的等待喚醒機(jī)制。 假如我現(xiàn)在注釋掉synchronized,如下: new Thread(() -> { //synchronized (lockObj){ System.out.println("線程" + Thread.currentThread().getName() + "進(jìn)來了"); try { lockObj.wait(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("線程" + Thread.currentThread().getName() + "被喚醒"); //} }, "A").start();
再次運(yùn)行,結(jié)果報(bào)錯(cuò)了,如下: 異常信息得出第一個(gè)結(jié)論:wait/notify必須在同步代碼塊中才能使用。 如果先執(zhí)行notify,再執(zhí)行wait,情況如何呢?讓A線程睡3秒鐘,使B先執(zhí)行,先notify,然后A中wait,執(zhí)行結(jié)果如下: 運(yùn)行結(jié)果可以發(fā)現(xiàn)”線程A被喚醒“這句話一直沒有打印出來。 得出第二個(gè)結(jié)論:先notify再wait的話,程序無法被喚醒。 再來看看用await/notify實(shí)現(xiàn): private static Lock lock = new ReentrantLock(); private static Condition condition = lock.newCondition(); private static void awaitSignal(){ new Thread(() -> { lock.lock(); try { System.out.println("線程" + Thread.currentThread().getName() + "進(jìn)來了"); condition.await(); System.out.println("線程" + Thread.currentThread().getName() + "被喚醒"); } catch (Exception e){ e.printStackTrace(); } finally { lock.unlock(); } }, "A").start();
new Thread(() -> { try { lock.lock(); System.out.println("線程" + Thread.currentThread().getName() + "進(jìn)來了"); condition.signal(); System.out.println("線程" + Thread.currentThread().getName() + "喚醒另一個(gè)線程"); } catch (Exception e){ e.printStackTrace(); } finally { lock.unlock(); } }, "B").start(); }
這個(gè)是用await/signal實(shí)現(xiàn)的等待喚醒機(jī)制。 假如注釋掉lock和unlock這兩個(gè)操作,再次執(zhí)行,還是會(huì)拋之前那個(gè)異常,即IllegalMonitorStateException。 得出第一個(gè)結(jié)論:await/notify必須伴隨lock/unlock出現(xiàn)。 假如先signal,再await,情況也是和之前用wait/notify一樣,await的線程一直沒被喚醒。 得出第二個(gè)結(jié)論:必須先await再signal。 所以不管是wait/notify還是await/signal,都有兩個(gè)限制條件: 線程要先獲得并持有鎖,必須在鎖塊中執(zhí)行;
3. LockSupport怎么用? LockSupport主要就是用park(等待)和unpark(喚醒)方法來實(shí)現(xiàn)等待喚醒。它的原理就是使用了一種名為permit(許可證)的概念來實(shí)現(xiàn)等待喚醒功能,每個(gè)線程都有一個(gè)許可證,許可證只有兩個(gè)值,一個(gè)是0,一個(gè)是1。默認(rèn)值是0,表示沒有許可證,就會(huì)被阻塞。那誰來發(fā)放許可證呢,就是unpark方法。這兩個(gè)方法底層其實(shí)是UNSAFE類的park和unpark方法,調(diào)用park時(shí),將permit的值設(shè)置為0,調(diào)用unpark時(shí),將permit的值設(shè)置為1。 用法如下: private static void lockSupportTest(){ Thread a = new Thread(() -> { System.out.println("線程" + Thread.currentThread().getName() + "進(jìn)來了"); LockSupport.park(); // 等待 System.out.println("線程" + Thread.currentThread().getName() + "被喚醒"); }, "A"); a.start();
Thread b = new Thread(() -> { System.out.println("線程" + Thread.currentThread().getName() + "進(jìn)來了"); LockSupport.unpark(a); // 喚醒 }, "B"); b.start(); }
首先它不需要再同步塊中使用,這是第一個(gè)優(yōu)點(diǎn)。那么先unpark再park會(huì)不會(huì)報(bào)錯(cuò)呢?要知道另兩種方式先喚醒再等待的話,都會(huì)導(dǎo)致線程無法被喚醒的。假如我先unpark,再park,其實(shí)也是可以的,相當(dāng)于提前發(fā)放了通行證,先給A線程unpark了,那么A線程執(zhí)行的時(shí)候,就相當(dāng)于沒有park這一行。 LockSupport總結(jié): 是一個(gè)線程阻塞喚醒的工具類,所有方法都是靜態(tài)方法,可以讓線程在任意位置阻塞,其底層調(diào)用的是UNSAFE類的native方法。每調(diào)用一次unpark方法,permit就會(huì)變成1,每調(diào)一次park方法,就會(huì)消耗掉一個(gè)許可證,即permit就變成0,每個(gè)線程都有一個(gè)permit,permit最多也就一個(gè),多次調(diào)用unpark也不會(huì)累加。因?yàn)檫@是根據(jù)是否有permit去判斷是否要阻塞線程的,所以,先unpark再park也可以,跟順序無關(guān),只看是否有permit。如果先unpark了兩次,再park兩次,那么線程還是會(huì)被阻塞,因?yàn)閜ermit不會(huì)累加,unpark兩次,permit的值還是1,第一次park的時(shí)變成0了,所以第二次park就會(huì)阻塞線程。
|