注意:即使以下情况无效且违反同步阻止的概念,我仍然试图了解它是如何工作的
创建了两个线程,两个线程都尝试执行相同的关键部分,令人惊讶的是,即使通过更改监视器,两个线程都会进入临界区。
public class MultiThreadTest {
final static ConcurrentHashMap<String,Object> objMap = new ConcurrentHashMap<String, Object>();
public static void main(String[] args) {
Thread t1 = new Thread(new MyThread(objMap,"1","T1"));
Thread t2 = new Thread(new MyThread(objMap,"1","T2"));
t1.start();
try {
Thread.sleep(1000);
} catch (InterruptedException ex) {
Logger.getLogger(MultiThreadTest.class.getName()).log(Level.SEVERE, null, ex);
}
t2.start();
}
}
class MyThread implements Runnable{
private final ConcurrentHashMap<String,Object> objMap;
private final String id;
private final String name;
public MyThread(ConcurrentHashMap<String,Object> objMap, String id, String name){
this.objMap = objMap;
this.id =id;
this.name = name;
}
@Override
public void run() {
Object monitor = getMonitor(id);
synchronized(monitor){
System.out.println("Thread Entered Critica section is:"+id+" and name is:"+name);
try {
Thread.sleep(10000);
} catch (InterruptedException ex) {
Logger.getLogger(MyThread.class.getName()).log(Level.SEVERE, null, ex);
}
System.out.println("Thread Exiting Critical section is:"+id+" and name is:"+name);
}
}
private Object getMonitor(String id){
if(objMap.contains(id)){
return objMap.get(id);
}else{
objMap.put(id,new Object());
return objMap.get(id);
}
}
}
以下是输出:
Thread Entered Critica section is:1 and name is:T1
Thread Entered Critica section is:1 and name is:T2
Thread Exiting Critical section is:1 and name is:T1
Thread Exiting Critical section is:1 and name is:T2
即使监视器已更改,似乎两个线程都会进入。
感谢任何帮助..
答案 0 :(得分:1)
正如我在评论中提到的,你的getMonitor
方法是一个很大的竞争条件,因为你没有在地图对象上进行同步,所以在检查密钥是否存在以及你放置密钥的时间之间在新对象中,其他线程也可以这样做。
但是,由于你在开始第二个线程之前等了一秒钟,这不是问题所在。
问题在于您使用ConcurrentHashMap.contains(Object)
方法检查值是否存在,而不是键是否存在。您需要将方法更改为:
private Object getMonitor(String id){
synchronized (objMap) {
if (objMap.containsKey(id)) { // <---- containsKey(...), not contains(...)
return objMap.get(id);
} else {
objMap.put(id, new Object());
return objMap.get(id);
}
}
}
另外,您可以通过实际检查锁定的监视器来避免错误的结论,即您的监视器被不同的线程锁定了两次:
System.out.println(
"Thread Entered Critica section is:" + id + " and name is:"
+ name + " and monitor is: " + monitor);
答案 1 :(得分:0)
问题在于您的getMonitor
方法。更改它有以下将解决问题。
private Object getMonitor(String id){
objMap.putIfAbsent(id, new Object());
return objMap.get(id);
}
原因是您的原始getMonitor
方法容易出现竞争条件问题。常见的误解是使用线索安全集合(如Vector
,ConcurrentHashMap
)本身会使您的代码线程安全,但它不会成功。
您的getMonitor
具有经典的Check-Then-Act编码风格(如果没有),您的objMap
被定义为静态变量,因此所有线程都将访问同一个实例。
根据我提议的更改(objMap.putIfAbsent
),可以避免竞争条件,因为Check-Then-Act现在将在objMap
更改将打印以下内容
Thread Entered Critica section is:1 and name is:T1
Thread Exiting Critical section is:1 and name is:T1
Thread Entered Critica section is:1 and name is:T2
Thread Exiting Critical section is:1 and name is:T2