ConcurrentHashMap 上的竞争条件问题
Problems with race conditions on ConcurrentHashMap
我有一个多线程应用程序,其中 n 个线程写入 ConcurrentHashMap
。另一个 n 线程从该映射中读取并将其值复制到副本列表中。
之后,原始列表将从地图中删除。
出于某种原因,我总是得到 ConcurrentModificationException
.
我什至尝试用一个可变布尔值创建我自己的锁机制,但它不起作用。当使用 Google Guava 和 Lists.newLinkedList()
时,我得到一个 ConcurrentModificationException
。当使用 StandardWay new LinkedList(list)
我得到一个 ArrayOutOfBoundsException
.
编译代码示例如下:
public class VolatileTest {
public static Map<String, List<String>> logMessages = new ConcurrentHashMap<String, List<String>>();
public static AtomicBoolean lock = new AtomicBoolean(false);
public static void main(String[] args) {
new Thread() {
public void run() {
while (true) {
try {
if (!VolatileTest.lock.get()) {
VolatileTest.lock.set(true);
List<String> list = VolatileTest.logMessages.get("test");
if (list != null) {
List<String> copyList = Collections.synchronizedList(list);
for (String string : copyList) {
System.out.println(string);
}
VolatileTest.logMessages.remove("test");
}
VolatileTest.lock.set(false);
}
} catch (ConcurrentModificationException ex) {
ex.printStackTrace();
System.exit(1);
}
}
};
}.start();
new Thread() {
@Override
public void run() {
while (true) {
if (!VolatileTest.lock.get()) {
VolatileTest.lock.set(true);
List<String> list = VolatileTest.logMessages.get("test");
if (list == null) {
list = Collections.synchronizedList(new LinkedList<String>());
}
list.add("TestError");
VolatileTest.logMessages.put("test", list);
VolatileTest.lock.set(false);
}
}
}
}.start();
}
ConcurrentHashMap 是安全的,这意味着您不会遇到 ConcurrentModificationException
。这是地图中的 List<String>
,其中一个线程试图读取数据,而另一个线程在迭代时试图删除数据。
我建议,您不要尝试锁定整个地图操作,而是注意使对列表的线程安全访问可能正在使用 Vector or SynchronizedList。
另请注意,您的两个线程的进入条件 if (!VolatileTest.lock) {
意味着它们可以同时 运行 因为最初默认布尔值将保持 false 值并且两者都可能尝试在同一时间工作同时列出。
如前所述,锁定模式看起来无效。最好使用同步。下面的代码对我有用
最终对象 obj = 新对象();
然后
synchronized (obj){....} 而不是 if (!VolatileTest.lock) {.....}
您有 ConcurrentModificationException,因为您的锁定被破坏,并且 reader 线程读取写入者同时写入的相同列表(通过 Iterator)。
您的代码看起来像是无锁编码的尝试。如果是这样,您必须像这样使用 CAS 操作:
while (!VolatileTest.lock.compareAndSet(false, true) { } // or while (VolatileTest.lock.getAndSet(true)) {} - try to get lock
try {
// code to execute under lock
} finally {
VolatileTest.lock.set(false); // unlock
}
你的
if (!VolatileTest.lock.get()) {
VolatileTest.lock.set(true);
...
}
不是原子的。或者您可以使用同步部分或任何其他标准锁定机制(例如 ReadWriteLock)
另外,如果你用一个锁来处理一个链表的读写,那你就不用用synchronized链表了。而且,你甚至不需要 ConcurrentHashMap。
所以:
- 使用一个全局锁和纯 HashMap/ArrayList OR
- 删除全局锁,在列表的每个特定实例上使用 ConcurrentHashMap 和带有 synchronized 的普通 ArrayList OR
- 使用队列(一些 BlockingQueue 或 ConcurrentLinkedQueue)代替所有当前的东西 OR
- 使用像 Disruptor 这样的东西(http://lmax-exchange.github.io/disruptor/) for inter-thread communication with many options. Also, here is a good example of how to build lock-free queues http://psy-lob-saw.blogspot.ru/2013/03/single-producerconsumer-lock-free-queue.html
我有一个多线程应用程序,其中 n 个线程写入 ConcurrentHashMap
。另一个 n 线程从该映射中读取并将其值复制到副本列表中。
之后,原始列表将从地图中删除。
出于某种原因,我总是得到 ConcurrentModificationException
.
我什至尝试用一个可变布尔值创建我自己的锁机制,但它不起作用。当使用 Google Guava 和 Lists.newLinkedList()
时,我得到一个 ConcurrentModificationException
。当使用 StandardWay new LinkedList(list)
我得到一个 ArrayOutOfBoundsException
.
编译代码示例如下:
public class VolatileTest {
public static Map<String, List<String>> logMessages = new ConcurrentHashMap<String, List<String>>();
public static AtomicBoolean lock = new AtomicBoolean(false);
public static void main(String[] args) {
new Thread() {
public void run() {
while (true) {
try {
if (!VolatileTest.lock.get()) {
VolatileTest.lock.set(true);
List<String> list = VolatileTest.logMessages.get("test");
if (list != null) {
List<String> copyList = Collections.synchronizedList(list);
for (String string : copyList) {
System.out.println(string);
}
VolatileTest.logMessages.remove("test");
}
VolatileTest.lock.set(false);
}
} catch (ConcurrentModificationException ex) {
ex.printStackTrace();
System.exit(1);
}
}
};
}.start();
new Thread() {
@Override
public void run() {
while (true) {
if (!VolatileTest.lock.get()) {
VolatileTest.lock.set(true);
List<String> list = VolatileTest.logMessages.get("test");
if (list == null) {
list = Collections.synchronizedList(new LinkedList<String>());
}
list.add("TestError");
VolatileTest.logMessages.put("test", list);
VolatileTest.lock.set(false);
}
}
}
}.start();
}
ConcurrentHashMap 是安全的,这意味着您不会遇到 ConcurrentModificationException
。这是地图中的 List<String>
,其中一个线程试图读取数据,而另一个线程在迭代时试图删除数据。
我建议,您不要尝试锁定整个地图操作,而是注意使对列表的线程安全访问可能正在使用 Vector or SynchronizedList。
另请注意,您的两个线程的进入条件 if (!VolatileTest.lock) {
意味着它们可以同时 运行 因为最初默认布尔值将保持 false 值并且两者都可能尝试在同一时间工作同时列出。
如前所述,锁定模式看起来无效。最好使用同步。下面的代码对我有用
最终对象 obj = 新对象();
然后
synchronized (obj){....} 而不是 if (!VolatileTest.lock) {.....}
您有 ConcurrentModificationException,因为您的锁定被破坏,并且 reader 线程读取写入者同时写入的相同列表(通过 Iterator)。
您的代码看起来像是无锁编码的尝试。如果是这样,您必须像这样使用 CAS 操作:
while (!VolatileTest.lock.compareAndSet(false, true) { } // or while (VolatileTest.lock.getAndSet(true)) {} - try to get lock
try {
// code to execute under lock
} finally {
VolatileTest.lock.set(false); // unlock
}
你的
if (!VolatileTest.lock.get()) {
VolatileTest.lock.set(true);
...
}
不是原子的。或者您可以使用同步部分或任何其他标准锁定机制(例如 ReadWriteLock)
另外,如果你用一个锁来处理一个链表的读写,那你就不用用synchronized链表了。而且,你甚至不需要 ConcurrentHashMap。
所以:
- 使用一个全局锁和纯 HashMap/ArrayList OR
- 删除全局锁,在列表的每个特定实例上使用 ConcurrentHashMap 和带有 synchronized 的普通 ArrayList OR
- 使用队列(一些 BlockingQueue 或 ConcurrentLinkedQueue)代替所有当前的东西 OR
- 使用像 Disruptor 这样的东西(http://lmax-exchange.github.io/disruptor/) for inter-thread communication with many options. Also, here is a good example of how to build lock-free queues http://psy-lob-saw.blogspot.ru/2013/03/single-producerconsumer-lock-free-queue.html