我的课堂上有两个方法,将在并发环境中执行:
class Clazz {
private int counter = 0;
private volatile Map<..> map = new ConcurrentHashMap<>();
private int[] array;
public void concurrentMethod() {
...perform some actions with map...
}
public int nonConcurrentMethod() {
...reinitialize the map...change references...
counter++;
return array[counter];
}
}
问题如下:假设一次只能由一个线程调用nonConcurrentMethod
,我应该明确地将counter
和array
指定为volatile
字段?使计数器为atomic
?
我的想法是,最好确保一切都可以正常工作而不会出现故障。
答案 0 :(得分:1)
通常,整个类具有特定的线程安全语义,例如HashMap
不是线程安全的,而ConcurrentHashMap
是线程安全的。如果您正在构建库,这一点尤其重要,因为人们可能会通过从多个线程调用nonConcurrentMethod()
来进行设计。
IMO,如果您不能将Clazz
分为两个具有不同线程安全语义的单独类,那么将nonConcurrentMethod()
设为线程安全是谨慎的。如果从多个线程调用nonConcurrentMetho()
,性能会降低,但正确性会保留下来,希望避免难于发现错误。
您可以尝试使用内部锁,当从单个线程获取锁时,由于有偏锁对其进行了优化,希望内部锁不会太昂贵:
private final Object lock = new Object();
public int nonConcurrentMethod() {
synchronized(lock) {
...reinitialize the map...change references...
counter++;
return array[counter];
}
}
确保Clazz
字段中的至少一个为final
,以确保safe publication。
答案 1 :(得分:0)
annotations
的用户会看到您对map
进行了个别修改的信息,但是在方法调用内部进行的修改不会与任何其他方法的另一个方法调用隔离。因此,如果concurrentMethod
在地图nonConcurrentMethod
的某个点上,clear()
可能会看到在地图上调用方法之间,concurrentMethod
中的条目消失了。
例如:
map
可以输出:
concurrentMethod() {
System.out.println(map.size());
System.out.println(map.size());
}
如果10
0
清除了地图并在nonConcurrentMethod
运行时被调用。
如果要确保原子访问,请尝试使用普通的concurrentMethod
并通过使用ConcurrentHashMap
关键字保护方法或使用更细粒度的方法来保护它,而不是使用HashMap
通过synchronized
或使用Java并发包中提供的某些锁定工具(例如synchronized(lock) { }
接口)进行锁定。
答案 2 :(得分:0)
最好将共享对象移到可由多个线程访问的不同类中。
由于这样的混乱,它可能在更长的时间内造成严重的问题,并且对于从未使用此模块的开发人员来说,很难找到根本原因。
尝试使代码简洁,以免在代码实现中出现任何隐藏的错误或假设。
ConcurrentHashMap
中的所有方法都是线程安全的,但是当多个线程调用不同的线程安全方法时,我们仍然必须注意确保线程安全。
例如:
Map map = new ConcurrentHashMap();
Thread t1 = new Thread();
Thread t2 = new Thread();
public void putIfAbsent(String key, String value) {
if (!map.containsKey(key)) {
map.put(key, value);
}
}
在上面的示例示例中,我们共享了资源map
,如果没有,则两个线程将放置一些值。
为避免这种情况,我们可以使用以下两种技术;