简体   繁体   English

将对象添加到列表时的Java并发问题

[英]Java Concurrency Issue when add an object to a list

I have a question which seems very strange, probably because my lack of some specific knowledge. 我有一个看起来很奇怪的问题,可能是因为我缺乏一些特定的知识。

I have a simple class TestCon and a test class TestTest: 我有一个简单的类TestCon和一个测试类TestTest:

import java.util.*;
import java.util.concurrent.*;

public class TestCon {
    private volatile static TestCon def = null;
    private volatile static List<TestCon> list = Collections.synchronizedList(new ArrayList<TestCon>());

    public static synchronized TestCon getInstance() {
        if (def == null) {
            def = new TestCon();
            list.add(def);
        }
        return def;
    }
    private synchronized static TestCon addtolist(){
        return  new TestCon();
    }
    public static synchronized int getSize() {
        return list.size();
    }

    public synchronized void destroy() {
        def = null;
        list.clear();
    }
}


import org.testng.annotations.Test;
import org.testng.*;

public class TestTest {


  @Test(threadPoolSize=50, invocationCount=15000)
  public void test_getInstance() {
      TestCon tst=TestCon.getInstance();
      Assert.assertNotNull(tst);
      Assert.assertEquals(TestCon.getSize(), 1); //line 1
      tst.destroy(); //line 2
  }
}

So my question is why the test occasionally fails at //line 1 (the list size is 0 but expected 1). 所以我的问题是为什么测试偶尔会在//line 1失败(列表大小为0但预期为1)。 If I add synchronization into the testing method - everything is fine. 如果我将同步添加到测试方法中-一切都很好。 If I comment line 2 again test succeeds. 如果我再次评论第2行,则测试成功。

While your TestCon class is not rendering an explicit race condition, your test is containing one. 虽然您的TestCon类未呈现显式竞争条件,但您的测试却包含一个。 While the getInstance , getSize and destroy methods are atomic, any composition of these methods is not. 尽管getInstancegetSizedestroy方法是原子方法,但这些方法的任何组成都不是原子方法。

Looking at your code: 查看您的代码:

@Test(threadPoolSize = 50, invocationCount = 15000)
public void test_getInstance() {
  TestCon tst = TestCon.getInstance();
  Assert.assertNotNull(tst); // (1)
  Assert.assertEquals(TestCon.getSize(), 1); // (2)
  tst.destroy(); // (3)
}

Assume that you have two threads that end up at line (1) together. 假设您有两个线程一起终止在第(1)行。 Then thread one moves through to line (3) successfully. 然后线程一成功进入第(3)行。 This means, the list is now empty. 这意味着列表现在为空。 After this thread, thread two moves and checks the list size at (2) and finds the list empty: The test fails. 在该线程之后,线程2移动并检查(2)处的列表大小,并发现列表为空:测试失败。

synchronized is just protecting the code block that follows it (method body or a block of statements inside a method). synchronized只是保护其后的代码块(方法主体或方法内部的语句块)。

That means that other synchronized blocks which lock the same entity (the class in your case since you're using synchronized methods) can't execute at the same time but it doesn't "order" the individual blocks. 这意味着锁定相同实体的其他synchronized块(在您的情况下为类,因为您使用的是synchronized方法)不能同时执行,但不会“排序”单个块。

What you see is: 您看到的是:

  • [Thread 1] TestCon tst=TestCon.getInstance(); [线程1] TestCon tst=TestCon.getInstance();
  • [Thread 2] tst.destroy(); //line 2 [线程2] tst.destroy(); //line 2 tst.destroy(); //line 2
  • [Thread 1] Assert.assertEquals(TestCon.getSize(), 1); //line 1 [线程1] Assert.assertEquals(TestCon.getSize(), 1); //line 1 Assert.assertEquals(TestCon.getSize(), 1); //line 1

This is because your code only ever creates a single TestCon which all threads use. 这是因为您的代码只会创建一个所有线程都使用的TestCon

Instead, you need to make sure that each thread gets their own TestCon . 相反,您需要确保每个线程都有自己的TestCon Look at ThreadLocal for a field which has a different value for each thread. ThreadLocal查找每个线程具有不同值的字段。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM