Java并發(fā)實(shí)踐

Java Concurrency

在多線程環(huán)境下,為了保證共享數(shù)據(jù)的原子和內(nèi)存可見(jiàn)性昧廷,需要進(jìn)行鎖操作憔晒。在JAVA中提供了內(nèi)置鎖和顯示鎖。本文使用用例結(jié)合明吩,來(lái)介紹以下鎖的用法:

內(nèi)置鎖(synchronized)

  • 內(nèi)置鎖用來(lái)鎖定代碼塊间学,在進(jìn)入代碼的時(shí)候獲取鎖定,在退出(或者異常退出)釋放鎖定印荔。內(nèi)置鎖是互斥的低葫,意味中同一時(shí)刻只能有一個(gè)線程獲取該鎖,其它線程只能等待或者阻塞直到鎖的釋放躏鱼。如下面代碼中氮采,假如線程1執(zhí)行addOne操作,當(dāng)線程2調(diào)用getOne時(shí)染苛,就需要等待線程1執(zhí)行完成并釋放鎖鹊漠。
    public class ProductPool {
        private Integer product = new Integer(0);

        public synchronized Integer getProduct() {
            return product;
        }

        public synchronized void addOne() {
            this.product = this.product + 1;
            LOG.info("produce value: {}", this.product);
        }

        public synchronized Integer getOne() {
            Integer old = new Integer(this.product);
            this.product = this.product - 1;
            return old;
        }
    }
  • 內(nèi)置鎖是可以重入的。當(dāng)線程A獲取鎖執(zhí)行某操作茶行,如果在當(dāng)前線程A內(nèi)躯概,某個(gè)步驟也需要獲取該鎖,該步驟是可以獲取到鎖的畔师。如下例子娶靡,當(dāng)ChildClass的對(duì)象執(zhí)行doPrint時(shí)已經(jīng)獲取到了鎖,內(nèi)部繼續(xù)調(diào)用super.doPrint看锉,如果不能重入就會(huì)發(fā)生死鎖姿锭。在同一線程內(nèi)塔鳍,鎖可以重入。

    public class SynchronizedDeakLock {
        private static final Logger LOG = LoggerFactory.getLogger(SynchronizedLock.class);

        public class BaseClass {
            public synchronized void doPrint() {
                LOG.info("base class print");
            }
        }
    
        public class ChildClass extends BaseClass {
            @Override
            public synchronized void doPrint() {
                LOG.info("child class do print");
                super.doPrint();
            }
        }
    
        @Test
        public void testDeadLock() {
            ChildClass childClass = new ChildClass();
            childClass.doPrint();
        }
    }
  • 內(nèi)置鎖使用非常簡(jiǎn)單呻此,在需要同步的方法轮纫、代碼塊上加入synchronized就行了,不需要顯示的獲取和釋放鎖焚鲜。且內(nèi)置鎖是JVM內(nèi)置的掌唾,它可以執(zhí)行部分優(yōu)化,比如在線程封閉鎖對(duì)象(該對(duì)象使用了鎖忿磅,但是卻不是共享對(duì)象糯彬,只在某一個(gè)線程使用)的鎖消除,改變鎖的粒度來(lái)消除內(nèi)置鎖的同步等葱她。
  • 在某些情況下撩扒,我們希望獲取鎖但又不想一直等待,所以我們指定獲取到鎖的最大時(shí)間览效,如果獲取不到就超時(shí)却舀。內(nèi)置鎖對(duì)這種細(xì)粒度的控制是不支持的,JAVA提供了一種新的鎖機(jī)制:顯示鎖锤灿。下章挽拔,我們就對(duì)該話題進(jìn)行討論。

ReentrantLock

ReentrantLock是JAVA 5提供的細(xì)粒度的鎖但校,作為內(nèi)置鎖在某些場(chǎng)景的補(bǔ)充螃诅。比如:支持線程獲取鎖的時(shí)間設(shè)置,支持獲取鎖線程對(duì)interrupt事件響應(yīng)状囱。但是在使用時(shí)必須顯示的獲取鎖术裸,然后在finally中釋放。如果不釋放亭枷,相當(dāng)于在程序中放置了個(gè)定時(shí)炸彈袭艺,后期很難發(fā)現(xiàn)。它實(shí)現(xiàn)了Lock的以下API(部分例子為了達(dá)到測(cè)試效果沒(méi)有unlock, 實(shí)際使用中絕對(duì)不能這樣):

1 . void lock() 獲取鎖叨粘,一致等待直到獲取猾编。下面的例子中,在主線程中獲取鎖且不釋放升敲, 子線程調(diào)用lock方法來(lái)獲取鎖答倡。可以看到驴党,子線程一致處于RUNNABLE狀態(tài)瘪撇,即使它被interrupt。

    @Test
    public void testLockWithInterrupt() throws InterruptedException {
        final Lock lock = new ReentrantLock();
        lock.lock();
        Thread childThread = new Thread(() -> {
               lock.lock();
            }, "t1 thread");
        childThread.start();
        childThread.interrupt();

        LOG.info("the child thread state: {}", childThread.getState().name());
        assertFalse(childThread.isInterrupted());
    }

2 . void lockInterruptibly() throws InterruptedException; 獲取鎖直到線程被interrupt, 線程拋出InterruptedException。下面的例子中倔既,主線程獲取鎖且不釋放恕曲,子線程調(diào)用lockInterruptibly方法來(lái)獲取鎖。首先在子線程獲取不到鎖的時(shí)候叉存,會(huì)處于一直等待狀態(tài)码俩;當(dāng)主線程中調(diào)用子線程interrupt時(shí),子線程會(huì)拋出InterruptedException歼捏。

    @Test(expected = InterruptedException.class)
    public void testLockInterruptibly() throws Exception {
        final Lock lock = new ReentrantLock();
        lock.lock();
        Thread.sleep(1000);
        Thread mainThread = Thread.currentThread();
        Thread t1=new Thread(new Runnable(){
            @Override
            public void run() {
                try {
                    lock.lockInterruptibly();
                } catch (InterruptedException e) {
                    LOG.error(" thread interrupted: {}", e);
                    mainThread.interrupt();
                }
            }
        }, "t1 thread");
        t1.start();
        Thread.sleep(1000);
        t1.interrupt();
        Thread.sleep(1000000);
    }

3 . boolean tryLock() 獲取鎖,如果獲取不到則立即返回false笨篷。

    @Test
    public void testTryLock() throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(1);
        ReentrantLock reentrantLock = new ReentrantLock();
        Runnable runnable = () -> {
            reentrantLock.lock();
            try {
                Thread.sleep(2 * 1000l);
                countDownLatch.countDown();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                reentrantLock.unlock();
            }
        };
    
        Runnable interruptRunnable = () -> {
            boolean result = reentrantLock.tryLock();
            if (result) {
                LOG.info("lock success");
                reentrantLock.unlock();
            } else {
                LOG.info("lock failed");
            }
        };
    
        new Thread(runnable).start();
        new Thread(interruptRunnable).start();
        countDownLatch.await();
    }
    

4 . boolean tryLock(long time, TimeUnit unit) throws InterruptedException 在指定的時(shí)間內(nèi)獲取鎖瞳秽,且返回結(jié)果。

@Test
public void testTryLockWithTime() throws InterruptedException, ExecutionException {
    final Lock lock = new ReentrantLock();
    lock.lock();
    CompletableFuture<Boolean> completableFuture = CompletableFuture.supplyAsync(() -> tryLock(lock));
    assertFalse(completableFuture.get().booleanValue());
}

private boolean tryLock(Lock lock) {
    try {
        boolean result = lock.tryLock(100, TimeUnit.MILLISECONDS);
        LOG.info("lock result: {}", result);
        return result;
    } catch (InterruptedException e) {
        LOG.error("interrupted: {}", e);
    }
    return false;
}
    

Semaphore

信號(hào)量常常用來(lái)控制對(duì)某一資源的訪問(wèn)數(shù)量率翅。例如练俐,下面的測(cè)試中我們?cè)O(shè)置信號(hào)量的permits為5,當(dāng)其中5個(gè)現(xiàn)在獲取且沒(méi)釋放冕臭,其它訪問(wèn)線程是獲取不到permit的腺晾。

@Test
public void testSemaphore() throws InterruptedException {
    Semaphore semaphore = new Semaphore(5);
    CountDownLatch countDownLatch = new CountDownLatch(2000);
    Executor executor = Executors.newFixedThreadPool(10);

    Runnable runnable = () -> {
        boolean isAcquired = semaphore.tryAcquire();
        if (isAcquired) {
            try {
                LOG.info("semaphore is acquired");
                TimeUnit.MICROSECONDS.sleep(2);
            } catch (InterruptedException ex) {
                LOG.error("error: {}", ex);
            } finally {
                semaphore.release();
            }
        } else {
            LOG.info("semaphore is not acquired");
        }
        countDownLatch.countDown();
    };
    IntStream.range(1, 2001).forEach(i ->
            executor.execute(runnable)
    );
    countDownLatch.await();
}

線程池(Thread pool)

線程池中的任務(wù)相對(duì)獨(dú)立,才能使它的性能達(dá)到最優(yōu)辜贵。在線程池中悯蝉,如果出現(xiàn)相互依賴的線程,這可能導(dǎo)致線程死鎖托慨。比如:我們開(kāi)啟一個(gè)只有1個(gè)線程的線程池鼻由,調(diào)用A任務(wù)時(shí),A開(kāi)始了B任務(wù)厚棵。然后A任務(wù)依賴B任務(wù)的完成蕉世。在實(shí)際執(zhí)行中,A使用了線程池中的線程婆硬,B任務(wù)不能獲取線程執(zhí)行狠轻,導(dǎo)致A任務(wù)不停的處于等待,而B(niǎo)任務(wù)也在等待A釋放線程彬犯。

    @Test
    public void testThreadPoolThreadDependency() {
        ExecutorService executor = Executors.newSingleThreadExecutor();
        Callable<String> stringRunnable = () -> {
            return "test";
        };
        Callable<String> runnable = () -> {
            Future<String> result = executor.submit(stringRunnable);
            try {
                return result.get();
            } catch (InterruptedException e) {
                return null;
            } catch (ExecutionException e) {
                return null;
            }
        };
        try {
            LOG.info(executor.submit(runnable).get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }

運(yùn)行上面測(cè)試向楼,會(huì)發(fā)現(xiàn)處于一直等待的情況,查看thread dump:

"pool-1-thread-1" #11 prio=5 os_prio=31 tid=0x00007fd6d606f000 nid=0x5703 waiting on condition [0x0000000122af2000]
   java.lang.Thread.State: WAITING (parking)
        at sun.misc.Unsafe.park(Native Method)
        - parking to wait for  <0x0000000795f453d8> (a java.util.concurrent.FutureTask)
        at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
        at java.util.concurrent.FutureTask.awaitDone(FutureTask.java:429)
        at java.util.concurrent.FutureTask.get(FutureTask.java:191)
        at com.eyesee.concurrency.threadpool.ThreadPoolExecutorTest.lambda$testThreadPoolThreadDependency$1(ThreadPoolExecutorTest.java:25)
        at com.eyesee.concurrency.threadpool.ThreadPoolExecutorTest$$Lambda$2/183264084.call(Unknown Source)
        at java.util.concurrent.FutureTask.run(FutureTask.java:266)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
        at java.lang.Thread.run(Thread.java:745)

   Locked ownable synchronizers:
        - <0x0000000795f22fd0> (a java.util.concurrent.ThreadPoolExecutor$Worker)

源代碼詳見(jiàn):https://github.com/jessepys/javaconcurrency

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末躏嚎,一起剝皮案震驚了整個(gè)濱河市蜜自,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌卢佣,老刑警劉巖重荠,帶你破解...
    沈念sama閱讀 223,207評(píng)論 6 521
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異虚茶,居然都是意外死亡戈鲁,警方通過(guò)查閱死者的電腦和手機(jī)仇参,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,455評(píng)論 3 400
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)婆殿,“玉大人诈乒,你說(shuō)我怎么就攤上這事∑怕” “怎么了怕磨?”我有些...
    開(kāi)封第一講書(shū)人閱讀 170,031評(píng)論 0 366
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)消约。 經(jīng)常有香客問(wèn)我肠鲫,道長(zhǎng),這世上最難降的妖魔是什么或粮? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 60,334評(píng)論 1 300
  • 正文 為了忘掉前任导饲,我火速辦了婚禮,結(jié)果婚禮上氯材,老公的妹妹穿的比我還像新娘渣锦。我一直安慰自己,他們只是感情好氢哮,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,322評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布袋毙。 她就那樣靜靜地躺著,像睡著了一般命浴。 火紅的嫁衣襯著肌膚如雪娄猫。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 52,895評(píng)論 1 314
  • 那天生闲,我揣著相機(jī)與錄音媳溺,去河邊找鬼。 笑死碍讯,一個(gè)胖子當(dāng)著我的面吹牛悬蔽,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播捉兴,決...
    沈念sama閱讀 41,300評(píng)論 3 424
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼蝎困,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了倍啥?” 一聲冷哼從身側(cè)響起禾乘,我...
    開(kāi)封第一講書(shū)人閱讀 40,264評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎虽缕,沒(méi)想到半個(gè)月后始藕,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,784評(píng)論 1 321
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,870評(píng)論 3 343
  • 正文 我和宋清朗相戀三年伍派,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了江耀。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,989評(píng)論 1 354
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡诉植,死狀恐怖祥国,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情晾腔,我是刑警寧澤舌稀,帶...
    沈念sama閱讀 36,649評(píng)論 5 351
  • 正文 年R本政府宣布,位于F島的核電站灼擂,受9級(jí)特大地震影響扩借,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜缤至,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,331評(píng)論 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望康谆。 院中可真熱鬧领斥,春花似錦、人聲如沸沃暗。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,814評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)孽锥。三九已至嚼黔,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間惜辑,已是汗流浹背唬涧。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,940評(píng)論 1 275
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留盛撑,地道東北人碎节。 一個(gè)月前我還...
    沈念sama閱讀 49,452評(píng)論 3 379
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像抵卫,于是被迫代替她去往敵國(guó)和親狮荔。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,995評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容