如何用Java编写多线程测试

在家里度过一个周末为您提供了无限的可能性。 例如,我们玩棋盘游戏,观看在线课程,甚至用粘土重建巨石阵。 但是我们不是可以同时完成所有这些事情的多核CPU(并行性)。 我们人类的注意力跨度更像是一个线程,必须高效地从一个任务切换到另一个任务(并发)。 黏土混合几乎阻止了我的任何其他活动!

线程就是这篇文章的主题。 这篇文章试图给你一个例子,说明如何编写具有多个线程的测试。 您可以使用它来证明您的应用程序是线程安全的。 或像我这样:确保数据库锁按预期的方式工作。

Setup

想象有一个资源,几乎每个人都可以访问,但不应同时由两个人访问。 说:超级市场中的厕纸。

@Entity
@Data
public class ToiletPaper {
    @Id
    @GeneratedValue
    long id;
    boolean available = true;
}

如您所见,我们的厕纸仅由ID标识,并带有有关是否可用的信息。

存放卫生纸的仓库总是一件好事! 我们要保持简单,并定义两个函数来获取可用的厕纸包。 唯一的区别是将实现一个PESSIMISTIC WRITE锁定。

@Repository
public interface ToiletPaperRepository extends JpaRepository<ToiletPaper, Long> {

    Optional<ToiletPaper> findTopByAvailableTrue();

    @Lock(LockModeType.PESSIMISTIC_WRITE)
    Optional<ToiletPaper> findFirstByAvailableTrue();
}

一旦线程持有数据,此锁定将阻止任何其他进程读取,更新或删除数据。

抓住卫生纸卷后,我们将其可用性更新为false。 请注意,以上查询将不再找到该卫生纸实例。

@Transactional
public ToiletPaper grabToiletPaper() {
    return toiletPaperRepository.findTopByAvailableTrue()
            .map(this::updateToiletPaperToUnavailable)
            .orElseThrow(OutOfToiletPaperException::new);
}

private ToiletPaper updateToiletPaperToUnavailable(ToiletPaper toiletPaper) {
    toiletPaper.setAvailable(false);
    return toiletPaperRepository.save(toiletPaper);
}

请记住,有一个类似的抓厕纸将调用Lock实现的版本的方法。

A multi-threaded test

现在,让我们模拟两种现实生活中的超级市场情况:

  • 每个人都同时伸出手去拿厕纸。如果有人使用卫生纸,其他人将无法使用。

每个超市客户将由一个线程代表。 他将执行的指令定义在一个名为贪婪赛跑者要么耐心奔跑两者都会实施可运行界面如下:

@AllArgsConstructor
class GreedyWorker implements Runnable {
    private CountDownLatch threadsReadyCounter;
    private CountDownLatch threadsCalledBlocker;
    private CountDownLatch threadsCompletedCounter;

    @Override
    public void run() {
        long threadId = Thread.currentThread().getId();
        threadsReadyCounter.countDown();
        log.info("Thread-{} ready!", threadId);
        try {
            threadsCalledBlocker.await();
            try {
                ToiletPaper toiletPaper = toiletPaperService.grabToiletPaper();
                log.info("Thread-{} got Toilet Paper no. {}!", threadId, toiletPaper.getId());
            } catch (OutOfToiletPaperException ootpe) {
                log.info("No Toilet Paper in stock!");
            }
        } catch (InterruptedException ie) {
            log.info("Thread-{} got interrupted!", threadId);
        } finally {
            threadsCompletedCounter.countDown();
        }

    }
}

You see that we defined three so-called CountDownLatch objects. You can imagine these like Countdowns. You can define a number, for example 3. Calling the await() function makes the thread wait until the CountDownLatch reaches 0. So, if we were to call the countdown() three times, the execution would still be suspended during the first two countdown() calls but continue as soon as the third countdown(). So, what such a worker does is:

  • 倒数threadReadyCounter-表示已准备就绪等待,直至threadCalledBlocker达到0抓卫生纸并记录是否成功倒数threadsCompletedCounter-表示已完成任务

如果所有工作程序都执行此逻辑,我们可以确保所有线程将同时启动。 这可以向我们展示数据库锁定将如何影响结果。 这就是多线程测试的样子:

@Test
void multiThreadedGrabToiletPaper_NoLock() throws InterruptedException {
    CountDownLatch readyCounter = new CountDownLatch(NUMBER_OF_THREADS);
    CountDownLatch callBlocker = new CountDownLatch(1);
    CountDownLatch completeCounter = new CountDownLatch(NUMBER_OF_THREADS);

    List<Thread> workers = Stream
            .generate(() -> new Thread(new GreedyWorker(readyCounter, callBlocker, completeCounter)))
            .limit(NUMBER_OF_THREADS)
            .collect(Collectors.toList());

    workers.forEach(Thread::start);

    readyCounter.await();
    log.info("Open the Toilet Paper Hunt!");
    callBlocker.countDown();
    completeCounter.await();
    log.info("Hunt ended!");
}

首先,我们初始化CountDownLatches。 我们希望确保所有工作人员都同时开始,因此我们定义了从NUMBER_OF_THREADS。 我们创建相同数量的工人并启动他们。 正如我们之前所看到的,每个人都会倒计时readyCounter每个工作人员准备就绪后,测试将继续执行。

现在,我们对callBlocker进行倒计时,这反过来又使每个工人都开始抓厕纸! 然后等到Mayham结束。 这是发生了什么:

5位GreedyWorkers-数据库中有3张厕纸-数据库无锁:

[           main] c.s.toiletpaperrush.ToiletPaperIT: Open the Toilet Paper Hunt!
[       Thread-3] c.s.toiletpaperrush.ToiletPaperIT: Thread-30 ready!
[       Thread-1] c.s.toiletpaperrush.ToiletPaperIT: Thread-28 ready!
[       Thread-5] c.s.toiletpaperrush.ToiletPaperIT: Thread-32 ready!
[       Thread-2] c.s.toiletpaperrush.ToiletPaperIT: Thread-29 ready!
[       Thread-4] c.s.toiletpaperrush.ToiletPaperIT: Thread-31 ready!
[       Thread-2] c.s.toiletpaperrush.ToiletPaperIT: Thread-29 got Toilet Paper no. 1!
[       Thread-3] c.s.toiletpaperrush.ToiletPaperIT: Thread-30 got Toilet Paper no. 1!
[       Thread-5] c.s.toiletpaperrush.ToiletPaperIT: Thread-32 got Toilet Paper no. 1!
[       Thread-4] c.s.toiletpaperrush.ToiletPaperIT: Thread-31 got Toilet Paper no. 1!
[       Thread-1] c.s.toiletpaperrush.ToiletPaperIT: Thread-28 got Toilet Paper no. 1!
[           main] c.s.toiletpaperrush.ToiletPaperIT: Hunt ended!

5 PatientWorkers-数据库中有3座厕纸-锁定数据库:

[           main] c.s.toiletpaperrush.ToiletPaperIT: Open the Toilet Paper Hunt!
[       Thread-8] c.s.toiletpaperrush.ToiletPaperIT: Thread-35 ready!
[       Thread-6] c.s.toiletpaperrush.ToiletPaperIT: Thread-33 ready!
[       Thread-9] c.s.toiletpaperrush.ToiletPaperIT: Thread-36 ready!
[      Thread-10] c.s.toiletpaperrush.ToiletPaperIT: Thread-37 ready!
[       Thread-7] c.s.toiletpaperrush.ToiletPaperIT: Thread-34 ready!
[       Thread-8] c.s.toiletpaperrush.ToiletPaperIT: Thread-35 got Toilet Paper no. 4!
[      Thread-10] c.s.toiletpaperrush.ToiletPaperIT: Thread-37 got Toilet Paper no. 5!
[       Thread-9] c.s.toiletpaperrush.ToiletPaperIT: Thread-36 got Toilet Paper no. 6!
[       Thread-6] c.s.toiletpaperrush.ToiletPaperIT: No Toilet Paper in stock!
[       Thread-7] c.s.toiletpaperrush.ToiletPaperIT: No Toilet Paper in stock!
[           main] c.s.toiletpaperrush.ToiletPaperIT: Hunt ended!

我们看到,贪婪的人抓住了同一卷厕纸,尽管还剩下其他人。 医护人员(一次只能有一个线程可以访问一张厕纸的情况)抓起厕纸直到数据库为空。 但是可以确保没有两个线程可以同时访问同一实体。

I hope that you got some insights on how to use Runnables and CountDownLatches to write a multi-threaded test. For everyone interested, I was inspired by this example. And maybe you will remember this the next time you go on your toilet paper scavenger hunt in these trying times!

from: https://dev.to//schmowser/how-to-write-multi-threaded-tests-in-java-2iki

你可能感兴趣的:(如何用Java编写多线程测试)