2015-10-15 133 views
3

我有一个简单的类:为什么java并发测试失败?

import org.slf4j.Logger; 
import org.slf4j.LoggerFactory; 

public class DummyService { 
    private final Logger logger = LoggerFactory.getLogger(getClass()); 
    private boolean dataIndexing = false; 

    public boolean isDataIndexing() { 
     logger.info("isDataIndexing: {}", dataIndexing); 
     return dataIndexing; 
    } 

    public void cancelIndexing() { 
     logger.info("cancelIndexing: {}", dataIndexing); 
     dataIndexing = false; 
    } 

    public void createIndexCorp() { 
     logger.info("createIndexCorp: {}", dataIndexing); 
     createIndex(); 
    } 

    public void createIndexEntr() { 
     logger.info("createIndexEntr: {}", dataIndexing); 
     createIndex(); 
    } 

    private void createIndex() { 
     logger.info("createIndex: {}", dataIndexing); 
     if(dataIndexing) 
      throw new IllegalStateException("Service is busy!"); 
     dataIndexing = true; 
     try { 
      while(dataIndexing) { 
       Thread.sleep(100); 
       logger.debug("I am busy..."); 
      } 
      logger.info("Indexing canceled"); 
     } catch (InterruptedException e) { 
      logger.error("Error during sleeping", e); 
     } finally { 
      dataIndexing = false; 
     } 
    } 
} 

和一个单元测试,与我想测试对象行为:

public class CommonUnitTest 
{ 
    @Test 
    public void testCreateIndexWithoutAsync() throws InterruptedException { 
     final long sleepMillis = 500; 
     final DummyService indexService = new DummyService(); 
     assertFalse(indexService.isDataIndexing()); 
     new Thread(() -> { 
        indexService.createIndexCorp(); 
       } 
     ).start(); 
     Thread.sleep(sleepMillis); 
     assertTrue(indexService.isDataIndexing()); 
     // TaskExecutor should fails here 
     new Thread(() -> { 
        indexService.createIndexEntr(); 
        logger.error("Exception expected but not occurred"); 
       } 
     ).start(); 
     assertTrue(indexService.isDataIndexing()); 
     indexService.cancelIndexing(); 
     Thread.sleep(sleepMillis); 
     assertFalse(indexService.isDataIndexing()); 
    } 
} 

对象的行为必须是:如果该方法createIndexCorp或createIndexEntr是由一个线程调用,那么另一个线程必须通过尝试调用其中一个方法来获得异常。但是这不会发生!这里是日志:

2015-10-15 17:15:06.277 INFO --- [   main] c.c.o.test.DummyService     : isDataIndexing: false 
2015-10-15 17:15:06.318 INFO --- [  Thread-0] c.c.o.test.DummyService     : createIndexCorp: false 
2015-10-15 17:15:06.319 INFO --- [  Thread-0] c.c.o.test.DummyService     : createIndex: false 
2015-10-15 17:15:06.419 DEBUG --- [  Thread-0] c.c.o.test.DummyService     : I am busy... 
2015-10-15 17:15:06.524 DEBUG --- [  Thread-0] c.c.o.test.DummyService     : I am busy... 
2015-10-15 17:15:06.624 DEBUG --- [  Thread-0] c.c.o.test.DummyService     : I am busy... 
2015-10-15 17:15:06.724 DEBUG --- [  Thread-0] c.c.o.test.DummyService     : I am busy... 
2015-10-15 17:15:06.818 INFO --- [   main] c.c.o.test.DummyService     : isDataIndexing: true 
2015-10-15 17:15:06.820 INFO --- [   main] c.c.o.test.DummyService     : isDataIndexing: true 
2015-10-15 17:15:06.820 INFO --- [  Thread-1] c.c.o.test.DummyService     : createIndexEntr: true 
2015-10-15 17:15:06.820 INFO --- [   main] c.c.o.test.DummyService     : cancelIndexing: true 
2015-10-15 17:15:06.820 INFO --- [  Thread-1] c.c.o.test.DummyService     : createIndex: true 
2015-10-15 17:15:06.824 DEBUG --- [  Thread-0] c.c.o.test.DummyService     : I am busy... 
2015-10-15 17:15:06.921 DEBUG --- [  Thread-1] c.c.o.test.DummyService     : I am busy... 
2015-10-15 17:15:06.924 DEBUG --- [  Thread-0] c.c.o.test.DummyService     : I am busy... 
2015-10-15 17:15:07.021 DEBUG --- [  Thread-1] c.c.o.test.DummyService     : I am busy... 
2015-10-15 17:15:07.024 DEBUG --- [  Thread-0] c.c.o.test.DummyService     : I am busy... 
2015-10-15 17:15:07.121 DEBUG --- [  Thread-1] c.c.o.test.DummyService     : I am busy... 
2015-10-15 17:15:07.124 DEBUG --- [  Thread-0] c.c.o.test.DummyService     : I am busy... 
2015-10-15 17:15:07.221 DEBUG --- [  Thread-1] c.c.o.test.DummyService     : I am busy... 
2015-10-15 17:15:07.224 DEBUG --- [  Thread-0] c.c.o.test.DummyService     : I am busy... 
2015-10-15 17:15:07.321 DEBUG --- [  Thread-1] c.c.o.test.DummyService     : I am busy... 
2015-10-15 17:15:07.321 INFO --- [   main] c.c.o.test.DummyService     : isDataIndexing: true 

你可以看到第二个线程可以启动进程,但它应该会得到异常。测试代码中的最后一个断言也失败了。这怎么可能发生?我不明白这种行为。我试图使用volatile和synchronized关键字,但没有任何帮助。 DummyService有什么问题?

+0

此日志'主] c.c.o.test.DummyService:cancelIndexing:TRUE' – njzk2

+0

它来自这一行:'indexService.cancelIndexing();'。为什么? – njzk2

回答

2

你有3个线程,T0,T1和TM(主)。 操作的顺序是这样的:

tm starts t0 
t0 checks dataIndexing flag - false, goes into the loop, sets flag to true 
tm sleeps 
tm starts t1 
tm sets indexing flag to false 
t1 checks dataIndexing flag - false, goes into the loop, sets flag to true 
t0 continues the loop because it missed that brief period when indexing was cancelled 

如果您在主索引TM标志设置为false之前睡觉,那么T1会得到异常。 您需要同步对多个线程之间共享的变量的访问。即检查标志的状态并且改变它需要在持有互斥体时完成。

+0

谢谢!现在我明白测试是错误的,而不是服务(尽管应该使用“同步”)。我想知道为什么我没有看到它。为什么我的3个同事也没有看到它;-) – philosophman1978

0

看来你打的记录和实际执行之间的差异。线程可以想象地运行取消并在日志和异常之间的空间中创建索引,从而第二个线程滑倒并防止取消第一个和第二个线程。

这是不可取的,允许共享资源,即private boolean dataIndexing同时变化。有两个解决方案(至少):

1.A syncronized方法,以允许所述共享资源的改变(因此限制只访问一个线程时间)

private synchronized void setDataIndexing(boolean value) { 
    dataIndexing = value; 
} 

2.Guarding每个改变在syncronized部分(以及= true两个= false地)这个值:

syncronized (this) { 
    dataIndexing = /* the relevant value */; 
} 

我会建议一个单独的方法,但良好的知道的替代品。

+0

谢谢MK。为可能的场景:) – st2rseeker

+0

这不是一个可能的场景,它是发生了什么 –

+1

我会建议''AtomicBoolean'与'compareAndSet'方法的使用。 – LoganMzz

0

不回答你的问题,但是这是完全不同步:

if (dataIndexing) 
    throw new IllegalStateException("Service is busy!"); 
dataIndexing = true; 

业务繁忙,如果你的执行到达throw声明?不必要!另一个线程可能会在测试和抛出之间将dataIndexing的值从true更改为false。

更糟的是,也许差多了,是两个线程可能都扔在同一时间后到达的声明

Thread A       Thread B 

tests dataIndexing, finds it to 
be false. 

            Tests dataIndexing, finds it to be false. 

sets dataIndexing = true;   sets dataIndexing = true; 
...        ... 

此外,这是不可靠的,这需要时间。

Thread.sleep(sleepMillis); 
assertTrue(indexService.isDataIndexing()); 

更好地设计你的类可测性。如果你的测试需要等到isDataIndexing(),那么你的班级应该提供一个测试手段到wait() ......

另外,不要低估测试在尽可能少的时间内完成的重要性。当你有一个拥有数千或数万测试用例的系统时,秒数真的开始累加起来。