欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

如何測試Java類的線程安全性

 更新時間:2019年12月11日 08:31:45   作者:碼之初  
這篇文章主要介紹了如何測試Java類的線程安全性,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下

這篇文章主要介紹了如何測試Java類的線程安全性,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下

線程安全性是Java等語言/平臺中類的一個重要標(biāo)準(zhǔn),在Java中,我們經(jīng)常在線程之間共享對象。由于缺乏線程安全性而導(dǎo)致的問題很難調(diào)試,因?yàn)樗鼈兪桥及l(fā)的,而且?guī)缀醪豢赡苡心康牡刂噩F(xiàn)。如何測試對象以確保它們是線程安全的?

假如有一個內(nèi)存書架

package com.mzc.common.thread;
 
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
 
/**
 * <p class="detail">
 * 功能: 內(nèi)存書架
 * </p>
 *
 * @author Moore
 * @ClassName Books.
 * @Version V1.0.
 * @date 2019.12.10 14:00:13
 */
public class Books {
 final Map<Integer, String> map = new ConcurrentHashMap<>();
 
 /**
 * <p class="detail">
 * 功能: 存書,并返回書的id
 * </p>
 *
 * @param title :
 * @return int
 * @author Moore
 * @date 2019.12.10 14:00:16
 */
 int add(String title) {
 final Integer next = this.map.size() + 1;
 this.map.put(next, title);
 return next;
 }
 
 /**
 * <p class="detail">
 * 功能: 根據(jù)書的id讀取書名
 * </p>
 *
 * @param id :
 * @return string
 * @author Moore
 * @date 2019.12.10 14:00:16
 */
 String title(int id) {
 return this.map.get(id);
 }
}

首先,我們把一本書放進(jìn)書架,書架會返回它的ID。然后,我們可以通過它的ID來讀取書名,像這樣:

Books books = new Books();
String title = "Elegant Objects";
int id = books.add(title);
assert books.title(id).equals(title);

這個類似乎是線程安全的,因?yàn)槲覀兪褂玫氖蔷€程安全的ConcurrentHashMap,而不是更原始和非線程安全的HashMap,對吧?我們先來測試一下:

public class BooksTest {
 @Test
 public void addsAndRetrieves() {
 Books books = new Books();
 String title = "Elegant Objects";
 int id = books.add(title);
 assert books.title(id).equals(title);
 }
}

查看測試結(jié)果:

測試通過了,但這只是一個單線程測試。讓我們嘗試從幾個并行線程中進(jìn)行相同的操作(我使用的是Hamcrest):

/**
 * <p class="detail">
 * 功能: 多線程測試
 * </p>
 *
 * @throws ExecutionException the execution exception
 * @throws InterruptedException the interrupted exception
 * @author Moore
 * @date 2019.12.10 14:16:34
 */
 @Test
 public void addsAndRetrieves2() throws ExecutionException, InterruptedException {
 Books books = new Books();
 int threads = 10;
 ExecutorService service = Executors.newFixedThreadPool(threads);
 Collection<Future<Integer>> futures = new ArrayList<>(threads);
 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));
 }

首先,我通過執(zhí)行程序創(chuàng)建線程池。然后,我通過Submit()提交10個Callable類型的對象。他們每個都會在書架上添加一本唯一的新書。所有這些將由池中的10個線程中的某些線程以某種不可預(yù)測的順序執(zhí)行。

然后,我通過Future類型的對象列表獲取其執(zhí)行者的結(jié)果。最后,我計算創(chuàng)建的唯一圖書ID的數(shù)量。如果數(shù)字為10,則沒有沖突。我使用Set集合來確保ID列表僅包含唯一元素。

我們看一下這樣改造后的運(yùn)行結(jié)果:

測試也通過了,但是,它不夠強(qiáng)壯。這里的問題是它并沒有真正從多個并行線程測試這些書。在兩次調(diào)用commit()之間經(jīng)過的時間足夠長,可以完成books.add()的執(zhí)行。這就是為什么實(shí)際上只有一個線程可以同時運(yùn)行的原因。

我們可以通過修改一些代碼再來檢查它:

@Test
 public void addsAndRetrieves3() {
  Books books = new Books();
  int threads = 10;
  ExecutorService service = Executors.newFixedThreadPool(threads);
  AtomicBoolean running = new AtomicBoolean();
  AtomicInteger overlaps = new AtomicInteger();
  Collection<Future<Integer>> futures = new ArrayList<>(threads);
  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));
 }

看一下測試結(jié)果:

執(zhí)行錯誤,說明插入的書和返回的id數(shù)量是不沖突的。

通過上面的代碼,我試圖了解線程之間的重疊頻率以及并行執(zhí)行的頻率。但是基本上概率為0,所以這個測試還沒有真正測到我想測的,還不是我們想要的,它只是把十本書一本一本地加到書架上。

再來:

可以看到,如果我把線程數(shù)增加到1000,它們會開始重疊或者并行運(yùn)行。

但是我希望即使線程數(shù)只有10個的時候,也會出現(xiàn)重疊并行的情況。怎么辦呢?為了解決這個問題,我使用CountDownLatch:

@Test
  public void addsAndRetrieves4() throws ExecutionException, InterruptedException {
    Books books = new Books();
    int threads = 10;
    ExecutorService service = Executors.newFixedThreadPool(threads);
    CountDownLatch latch = new CountDownLatch(1);
    AtomicBoolean running = new AtomicBoolean();
    AtomicInteger overlaps = new AtomicInteger();
    Collection<Future<Integer>> futures = new ArrayList<>(threads);
    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)在,每個線程在接觸書本之前都要等待鎖權(quán)限。當(dāng)我們通過Submit()提交所有內(nèi)容時,它們將保留并等待。然后,我們用countDown()釋放鎖,它們才同時開始運(yùn)行。

查看運(yùn)行結(jié)果:

通過運(yùn)行結(jié)果可以知道,現(xiàn)在線程數(shù)還是為10,但是線程的重疊數(shù)是大于0的,所以assertTrue執(zhí)行通過,ids也不等于10了,也就是沒有像以前那樣得到10個圖書ID。顯然,Books類不是線程安全的!

在修復(fù)優(yōu)化該類之前,教大家一個簡化測試的方法,使用來自Cactoos的RunInThreads,它與我們上面所做的完全一樣,但代碼是這樣的:

@Test
  public void addsAndRetrieves5() {
    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()的第一個參數(shù)是Func(一個函數(shù)接口)的實(shí)例,接受AtomicInteger(RunsThreads的第一個參數(shù))并返回布爾值。此函數(shù)將在10個并行線程上執(zhí)行,使用與上述相同的基于鎖的方法。

這個RunInThreads看起來非常緊湊,用起來也很方便,推薦給大家,可以用起來的。只需要在你的項(xiàng)目中添加一個依賴:

<dependency>
      <groupId>org.llorllale</groupId>
      <artifactId>cactoos-matchers</artifactId>
      <version>0.18</version>
    </dependency>

最后,為了使Books類成為線程安全的,我們只需要向其方法add()中同步添加就可以了?;蛘?,聰明的碼小伙伴們,你們有更好的方案嗎?歡迎留言,大家一起討論。

以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

最新評論