Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

why java concurrency test fails?

I have a simple class:

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;
        }
    }
}

and a unit test, with which i want to test object behavior:

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());
    }
}

The behaviour of object must be: If the method createIndexCorp or createIndexEntr is called by one thread, then another thread must get exception by trying to call one of this methods. But this does not happens! Here is the log:

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

You can see that second thread can start process, but it should get the exception. Also the last assertion in the test code fails. How can that happen ? I dont understand this behavior. I tried to use volatile and synchronized keyword, but nothing helps. What is wrong with DummyService ?

like image 236
philosophman1978 Avatar asked Jun 28 '26 20:06

philosophman1978


1 Answers

You have 3 threads, t0, t1 and tm (main). The order of operations is like this:

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

If you sleep in the main tm before setting indexing flag to false, then t1 will get the exception. You need to synchronize access to variables shared between multiple threads. I.e. checking the state of the flag and changing it needs to be done while holding a mutex.

like image 98
MK. Avatar answered Jun 30 '26 09:06

MK.



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!