在 Java 中引发竞争条件

发布于 2024-12-05 05:50:18 字数 1337 浏览 0 评论 0原文

我必须编写一个引发竞争条件的单元测试,以便我可以测试稍后是否可以解决问题。 问题是竞争条件很少发生,可能是因为我的计算机只有两个核心。

代码如下:

class MyDateTime {
  String getColonTime() {
    // datetime is some kind of lazy caching variable declared somewhere(does not matter)
    if (datetime == null) {
      initDateTime(); //Uses lazy to initlialize variable, takes some time
    }
    // Colon time stores hh:mm as string
    if (datetime.colonTime == null) {
      StringBuilder sb = new StringBuilder();
      //Now do some steps to build the hh:mm string
      //...
      //set colon time
      datetime.colonTime = sb.toString();
    }
  return datetime.colonTime;
  }
}

说明: initDateTime 为 dateTime 分配一个新实例,因此,datetime.colonTime 之后为 null(因为我们想要延迟初始化它,正如我之前所说的)。 现在,如果线程 A 进入该方法,然后调度程序在它可以运行 initDateTime() 之前停止它。线程 B 现在运行 getColonTime(),发现日期时间仍然为 null 并对其进行初始化。 datetime.colonTime 为 null,因此执行第二个 if 块,并且 datetime.colonTime 获取 StringBuilder 的值。 如果调度程序停止该行和 return 语句之间的线程并恢复线程 A,则会发生以下情况: 由于 A 在调用 initDateTime 之前停止,因此 A 现在调用 initDateTime(),这将重置 datetime 对象,再次将 datetime.colonTime 设置为 null。然后线程 A 将进入第二个 if 块,但调度程序会在 datetime.colonTime = sb.toString(); 之前中断 A。被称为。结论是,dateTime.colonTime 仍然为 null。 现在调度程序恢复 B 并且该方法返回 null。

我尝试通过让多个线程对 MyDateTime 的单个(最终)实例调用 getColonTime() 来引发竞争条件,但它仅在某些极其罕见的情况下失败:( 关于如何编写 JUnit“测试”有任何提示吗?

I got to write a unit test which provokes a race condition so I can test if I probably fixed the problem later on.
The problem is that the race condition only occurs very rarely, maybe because my computer has only two cores.

The code is something like the following:

class MyDateTime {
  String getColonTime() {
    // datetime is some kind of lazy caching variable declared somewhere(does not matter)
    if (datetime == null) {
      initDateTime(); //Uses lazy to initlialize variable, takes some time
    }
    // Colon time stores hh:mm as string
    if (datetime.colonTime == null) {
      StringBuilder sb = new StringBuilder();
      //Now do some steps to build the hh:mm string
      //...
      //set colon time
      datetime.colonTime = sb.toString();
    }
  return datetime.colonTime;
  }
}

Explanation:
initDateTime assigns a new instance to dateTime, therefor, datetime.colonTime is null afterwards (as we want to initialize it lazy, as I stated before).
Now if Thread A enters the method and then the scheduler stops it just before it can run initDateTime(). Thread B now runst getColonTime(), sees that datetime is still null and initialzes it. datetime.colonTime is null so the second if block is executed and datetime.colonTime gets the value of the StringBuilder.
If then the scheduler stops the thread between this line and the return statement and resumes thread A, the following happens:
As A was stopped just before initDateTime is called, A now calls initDateTime(), which will kind of reset the datetime object, setting datetime.colonTime to null again. Thread A then will enter the second if block, but the scheduler will interrupt A before datetime.colonTime = sb.toString(); is called. As a conclusion, dateTime.colonTime is still null.
Now the scheduler resumes B and the method returns null.

I tried to provoke the race condition by having a number of threads calling getColonTime() to a single (final) instance of MyDateTime, but it only fails in some extreeemly rare cases :(
Any hints how to write a JUnit "test"?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

水水月牙 2024-12-12 05:50:18

正如您所提到的,竞争条件很难一致地重现。然而,平均法则站在你这边。如果您创建一个预计会失败一百次之一的测试,然后让它发生一千次,您可能会在旧代码中相当一致地捕获错误。因此,为了符合 TDD 原则,您应该按照以前的方式开始编写代码,提出一个迭代足够多次的测试,以确保旧代码始终失败,然后更改为新代码并确保它不会失败。

As you mention, race conditions are exceedingly difficult to reproduce consistently. However, the law of averages is on your side. If you create a test that you expect to fail maybe one in a hundred times, and then make it happen a thousand times, you'll probably catch the error fairly consistently in your old code. So, in keeping with TDD principles, you should start with the code the way it was before, come up with a test iterates enough times to fail consistently against the old code, then change to your new code and make sure it doesn't fail.

缱倦旧时光 2024-12-12 05:50:18

您可以查看 Thread Weaver,或者可能还有其他用于测试多线程代码的框架。我还没有使用过它,但用户指南看起来好像它是专门为这种测试。

You could look at Thread Weaver, or there may be other frameworks for testing multi-threaded code. I have not used it, but the Users' Guide looks as if it is designed for exactly this kind of testing.

极度宠爱 2024-12-12 05:50:18

我知道这篇文章很旧,但我面临着类似的情况。我倾向于做的是通过睡眠来支持竞争条件。

在你的情况下,我会做类似的事情

    class MyDateTime {
        String getColonTime() throws InterruptedException{
           if (datetime == null) {
              Thread.sleep(new Random().nextInt(100); //Wait  to enhance the chances that multiple threads enter here and reset colonTime.
              initDateTime(); 
           }
           Thread.sleep(new Random().nextInt(100); //Wait  to enhance the chances that colonTime stays null for a while.
           if (datetime.colonTime == null) {
              StringBuilder sb = new StringBuilder();
              datetime.colonTime = sb.toString();
           }
           Thread.sleep(new Random().nextInt(100); //Wait to favour reset of colonTime by another thread in the meantime.
           return datetime.colonTime;
        }
    }

,但显然这很快就会变得混乱。我希望有某种方法可以迫使调度程序在给定一些“断点”的情况下探索所有路径。

由于这篇文章有点旧,我想知道您是否找到了在 Java 中测试竞争条件的好方法。有什么建议可以分享吗?

谢谢

I know this post is quite old but I'm facing a similar situation. What I tend to do is to favour the race condition with sleeps.

In your case, I would do something like

    class MyDateTime {
        String getColonTime() throws InterruptedException{
           if (datetime == null) {
              Thread.sleep(new Random().nextInt(100); //Wait  to enhance the chances that multiple threads enter here and reset colonTime.
              initDateTime(); 
           }
           Thread.sleep(new Random().nextInt(100); //Wait  to enhance the chances that colonTime stays null for a while.
           if (datetime.colonTime == null) {
              StringBuilder sb = new StringBuilder();
              datetime.colonTime = sb.toString();
           }
           Thread.sleep(new Random().nextInt(100); //Wait to favour reset of colonTime by another thread in the meantime.
           return datetime.colonTime;
        }
    }

But clearly this becomes messy quite rapidly. I wish there was some way to force the scheduler to explore all the paths given some "break points".

As the post is a bit old, I was wondering whether you found good ways to test race conditions in Java. Any piece of advice to share ?

Thank you

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文