我应该在这个简单的情况下使用synchronized块吗?

时间:2013-12-05 12:13:05

标签: java multithreading java-ee synchronization

假设我有一个Resource类,其中包含Map类型resources对象:

public class Resource {
   private Map<Integer, MyResource> resources = new HashMap<Integer, MyResource>();

   public void addResource(MyResource r){
     synchronized(resources){
        resources.put(r.getId(), r);
     }
   }

   public void removeResource(int id){
      synchronized(resources){
            resources.remove(id);
      }
   }

}

由于多个线程可以访问resources,因此我总是使用synchronized块来添加新资源&amp;删除现有资源,如上面显示的代码。

现在,我还想添加一个函数来获取所有当前资源,我的问题非常简单,我是否应该使用synchronized块来返回所有当前资源,或者使用synchronized是否值得阻止?

public Map<Integer, MyResource> getResources(){
     return resources;
}

使用上面的代码返回当前资源是否足够,或者使用synchronized块仍然有用,如下所示:

public Map<Integer, MyResource> getResources(){
   synchronized(resources){
       return resources;
   }
}

就个人而言,我觉得使用synchronized块来获取资源并不值得,因为它总是返回当前时刻的资源。但我不确定。所以,如果您能向我解释是否使用synchronized阻止的原因,那将是很好的,谢谢。

5 个答案:

答案 0 :(得分:3)

  

使用上面的代码返回当前资源是否足够,或者使用同步块仍然有用,如下所示

都不够。无论是否在synchronized块内部将引用返回到共享映射,这一点都很重要:您必须在同步块中执行所有读取操作。事实上,你永远不应该允许对地图的引用转义你的对象,因为它将是松散的,没有可执行的同步策略。

答案 1 :(得分:1)

如果您只是返回resources,则意味着调用该方法的任何人都将获得对该方法的完全和非同步访问权限。也就是说,调用者可以在不告知您的情况下向地图添加和删除项目,也无需与其他线程同步访问。这可能是一个坏主意。

你可以做的最安全的事情就是在锁定resources的同时制作防御性副本:

public Map<Integer, MyResource> getResources(){
   synchronized(resources){
       return new HashMap<Integer, MyResource>(resources);
   }
}

更新:替代方法是使用 Collections.synchronizedMap ConcurrentHashMap来确保所有访问都是线程安全的,然后返回一个不可修改的视图以便代码调用getResources无法修改您在内部使用的地图。

更新2:请勿使用Collections.synchronizedMap:在地图上进行迭代需要对其进行同步,如果getResources返回不可修改的视图,则无法进行调用代码可以做到这一点。

// Don't use synchronizedMap
// private Map<Integer, MyResource> resources = Collections.synchronizedMap(new HashMap<Integer, MyResource>());

private Map<Integer, MyResource> resources = new ConcurrentHashMap<>();

public void addResource(MyResource r) {
    resources.put(r.getId(), r);
}

public void removeResource(int id) {
    resources.remove(id);
}

public Map<Integer, MyResource> getResources() {
    return Collections.unmodifiableMap(resources);
}

答案 2 :(得分:0)

由于您只需要读取数据,因此您也可以使用简单的方法,而不是synchronized。因为您已经将其定义为已同步,因此脏数据只能在地图中使用remove方法一次只有单个线程可以访问它但我认为如果reaDall方法不会同步,那么同时两个线程可以调用两个方法,即一个将调用remove,一个可以在这种情况下的所有read方法您的对象中可能存在脏数据。因此,使用synchronized方法

会更好

答案 3 :(得分:0)

我认为您应该使用synchonized块来放置新密钥。 总而言之,只需使用ConcurrentHashMap作为并发环境中的地图实现。

答案 4 :(得分:0)

public Map<Integer, MyResource> getResources(){
     return resources;
}


public Map<Integer, MyResource> getResources(){
   synchronized(resources){
       return resources;
   }
}

这两种方式都无法解决问题。当您将原始Map发送给您的消费者时,他们可以更改它,这将影响您的班级不变量。您使用防御性副本,或使用不可变地图,即 Collections.unmodifiableMap