如何测试Java类的线程安全性
我在最近的一次網(wǎng)絡(luò)研討會(huì)中談到了這個(gè)問(wèn)題,現(xiàn)在是時(shí)候以書面形式進(jìn)行解釋了。 線程安全是Java等語(yǔ)言/平臺(tái)中類的重要品質(zhì),我們經(jīng)常在線程之間共享對(duì)象。 缺乏線程安全性導(dǎo)致的問(wèn)題很難調(diào)試,因?yàn)樗鼈兪橇阈堑牟⑶規(guī)缀醪豢赡苡幸鈴?fù)制。 您如何測(cè)試對(duì)象以確保它們是線程安全的? 這就是我的做法。
女人的香氣(1992),馬丁·布雷斯特(Martin Brest)
我們說(shuō)有一個(gè)簡(jiǎn)單的內(nèi)存書架:
class Books {final Map<Integer, String> map =new ConcurrentHashMap<>();int add(String title) {final Integer next = this.map.size() + 1;this.map.put(next, title);return next;}String title(int id) {return this.map.get(id);} }首先,我們將一本書放在那里,書架返回其ID。 然后,我們可以通過(guò)ID讀取該書的標(biāo)題:
Books books = new Books(); String title = "Elegant Objects"; int id = books.add(title); assert books.title(id).equals(title);該類似乎是線程安全的,因?yàn)槲覀兪褂玫氖蔷€程安全的ConcurrentHashMap而不是更原始的和非線程安全的HashMap ,對(duì)吧? 讓我們嘗試測(cè)試一下:
class BooksTest {@Testpublic void addsAndRetrieves() {Books books = new Books();String title = "Elegant Objects";int id = books.add(title);assert books.title(id).equals(title);} }測(cè)試通過(guò)了,但這只是一個(gè)單線程測(cè)試。 讓我們嘗試從幾個(gè)并行線程中進(jìn)行相同的操作(我正在使用Hamcrest ):
class BooksTest {@Testpublic void addsAndRetrieves() {Books books = new Books();int threads = 10;ExecutorService service =Executors.newFixedThreadPool(threads);Collection<Future<Integer>> futures =new LinkedList<>();for (int t = 0; t < threads; ++t) {final String title = String.format("Book #%d", t);futures.add(service.submit(() -> books.add(title)));}Set<Integer> ids = new HashSet<>();for (Future<Integer> f : futures) {ids.add(f.get());}assertThat(ids.size(), equalTo(threads));} }首先,我通過(guò)Executors創(chuàng)建線程池。 然后,我通過(guò)submit()提交10個(gè)Callable類型的對(duì)象。 他們每個(gè)人都會(huì)在書架上添加一本獨(dú)特的新書。 所有這些將由池中的那十個(gè)線程中的某些線程以某種不可預(yù)測(cè)的順序執(zhí)行。
然后,我通過(guò)Future類型的對(duì)象列表獲取其執(zhí)行者的結(jié)果。 最后,我計(jì)算創(chuàng)建的唯一圖書ID的數(shù)量。 如果數(shù)字為10,則沒(méi)有沖突。 我使用Set集合來(lái)確保ID列表僅包含唯一元素。
測(cè)試通過(guò)了我的筆記本電腦。 但是,它不夠堅(jiān)固。 這里的問(wèn)題是它并沒(méi)有真正從多個(gè)并行線程測(cè)試這些工作Books 。 我們調(diào)用之間經(jīng)過(guò)的時(shí)間submit()是足夠大的,完成的執(zhí)行books.add() 這就是為什么實(shí)際上只有一個(gè)線程可以同時(shí)運(yùn)行的原因。 我們可以通過(guò)修改一些代碼來(lái)檢查它:
AtomicBoolean running = new AtomicBoolean(); AtomicInteger overlaps = new AtomicInteger(); Collection<Future<Integer>> futures = new LinkedList<>(); for (int t = 0; t < threads; ++t) {final String title = String.format("Book #%d", t);futures.add(service.submit(() -> {if (running.get()) {overlaps.incrementAndGet();}running.set(true);int id = books.add(title);running.set(false);return id;})); } assertThat(overlaps.get(), greaterThan(0));通過(guò)此代碼,我試圖查看線程相互重疊的頻率并并行執(zhí)行某些操作。 這永遠(yuǎn)不會(huì)發(fā)生,并且overlaps等于零。 因此,我們的測(cè)試尚未真正完成任何測(cè)試。 它只是在書架上一一增加了十本書。 如果我將線程數(shù)量增加到1000,它們有時(shí)會(huì)開(kāi)始重疊。 但是,即使它們數(shù)量很少,我們也希望它們重疊。 為了解決這個(gè)問(wèn)題,我們需要使用CountDownLatch :
CountDownLatch latch = new CountDownLatch(1); AtomicBoolean running = new AtomicBoolean(); AtomicInteger overlaps = new AtomicInteger(); Collection<Future<Integer>> futures = new LinkedList<>(); for (int t = 0; t < threads; ++t) {final String title = String.format("Book #%d", t);futures.add(service.submit(() -> {latch.await();if (running.get()) {overlaps.incrementAndGet();}running.set(true);int id = books.add(title);running.set(false);return id;})); } latch.countDown(); Set<Integer> ids = new HashSet<>(); for (Future<Integer> f : futures) {ids.add(f.get()); } assertThat(overlaps.get(), greaterThan(0));現(xiàn)在,每個(gè)線程在接觸書本之前,都要等待latch給予的許可。 當(dāng)我們通過(guò)submit()提交所有內(nèi)容時(shí),它們將保持等待狀態(tài)。 然后,我們使用countDown()釋放閂鎖,它們同時(shí)開(kāi)始運(yùn)行。 現(xiàn)在,在我的筆記本電腦上,即使threads為10, overlaps也等于3-5。
最后一個(gè)assertThat()現(xiàn)在崩潰了! 我沒(méi)有像以前那樣得到10個(gè)圖書ID。 它是7-9,但絕不是10。顯然,該類不是線程安全的!
但是在修復(fù)該類之前,讓我們簡(jiǎn)化測(cè)試。 讓我們用RunInThreads從Cactoos ,這確實(shí)是我們?cè)谇懊嬉呀?jīng)做了完全一樣的,但引擎蓋下:
class BooksTest {@Testpublic void addsAndRetrieves() {Books books = new Books();MatcherAssert.assertThat(t -> {String title = String.format("Book #%d", t.getAndIncrement());int id = books.add(title);return books.title(id).equals(title);},new RunsInThreads<>(new AtomicInteger(), 10));} }
assertThat()的第一個(gè)參數(shù)是Func (功能接口)的實(shí)例,它接受AtomicInteger ( RunsInThreads的第一個(gè)參數(shù))并返回Boolean 。 使用與上述相同的基于閂鎖的方法,將在10個(gè)并行線程上執(zhí)行此功能。
該RunInThreads似乎緊湊且方便,我已經(jīng)在一些項(xiàng)目中使用它。
順便說(shuō)一句,為了使Books線程安全性,我們只需要向其方法add()添加synchronized 。 或者,也許您可??以提出更好的解決方案?
翻譯自: https://www.javacodegeeks.com/2018/03/how-i-test-my-java-classes-for-thread-safety.html
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎(jiǎng)勵(lì)來(lái)咯,堅(jiān)持創(chuàng)作打卡瓜分現(xiàn)金大獎(jiǎng)總結(jié)
以上是生活随笔為你收集整理的如何测试Java类的线程安全性的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: phpnow服务器安装步骤图解
- 下一篇: 电脑双肩包哪个牌子好(电脑双肩包哪个牌子