这个类是如何保证线程安全的?(并发实践中)

huangapple go评论88阅读模式
英文:

How is this class thread safe? (Concurrency in Practice)

问题

我理解您的问题涉及到setLocation方法。我明白它正在利用ConcurrentMap,但它仍然执行一个“检查然后操作”的操作,这正如作者在下一页中承认的,即使使用并发集合,这也是不安全的。这不是一个原子操作。这个方法/类如何保证线程安全?

【翻译】
关于setLocation方法,尽管它使用了ConcurrentMap,但它仍然执行了一个“检查后操作”的操作,这正如作者在后续页面中所承认的,即使在使用并发集合时,这也是不安全的。这不是一个原子操作。那么这个方法/类如何确保线程安全呢?

英文:

I'm reading Concurrency in Practice and I came across this class:

@ThreadSafe
public class PublishingVehicleTracker {
    private final Map<String, SafePoint> locations;
    private final Map<String, SafePoint> unmodifiableMap;

    public PublishingVehicleTracker(Map<String, SafePoint> locations) {
        this.locations = new ConcurrentHashMap<String, SafePoint>(locations);
        this.unmodifiableMap = Collections.unmodifiableMap(this.locations);
    }

    public Map<String, SafePoint> getLocations() {
        return unmodifiableMap;
    }

    public SafePoint getLocation(String id) {
        return locations.get(id);
    }

    public void setLocation(String id, int x, int y) {
        if (!locations.containsKey(id))
            throw new IllegalArgumentException("invalid vehicle name: " + id);
        locations.get(id).set(x, y);
    }
}

My question involves setLocation. I understand it's utilizing a ConcurrentMap, but it's still performing a "check-then-act" operation, which the authors acknowledge (on the following page) is unsafe, event with concurrent collections. This isn't an atomic action. How is this method/class thread safe?

答案1

得分: 3

locations map 是只读的。该类中没有添加或移除 locations map 元素的操作。这使得类的最后部分设置位置。

setLocation 方法从 locations map 获取元素,然后更新其中已经存在的值。这意味着,如果 SafePoint.set 是线程安全的,整个类就是线程安全的。

请注意,这不是一种“检查然后操作”的情况。它检查值是否在映射中,但然后不修改映射本身,只修改值。没有其他线程可以从映射中移除该值,或者向映射中添加其他值。因此,线程安全取决于 set 是否是线程安全的。

英文:

The locations map is read only. There are no operations in the class that add to, or remove elements from the locations map. That leaves the last part of the class where it sets the location.

The setLocation method gets an element from the locations map, and then updates an already existing value in it. That means, if SafePoint.set is thread-safe, the whole class is thread-safe.

Note that this is not a check-then-act situation. It checks if the value is in the map, but then does not modify the map itself, it only modifies the value. There is no way another thread can remove that value from the map, or add another value to the map. So, thread-safety is depending on set being thread-safe.

答案2

得分: 0

大多数情况下,对象不是线程安全的,因为其状态变化没有得到适当的保护。

阅读下面这段摘自《Java并发实践》的文字:

> 无状态:它没有字段,也不引用其他类的字段。特定计算的临时状态仅存在于存储在线程堆栈上的局部变量中,这些局部变量仅对执行线程可访问。无状态对象始终是线程安全的。

第2章还介绍了一些使有状态对象线程安全的方法。

> 有三种修复有状态对象的方法:
> - 不要在线程之间共享状态变量;
> - 使状态变量成为不可变的;或者
> - 在访问状态变量时始终使用同步。

在上面的示例中,locations 是一个并发哈希映射,它是线程安全的集合。unmodifiableMap 在初始化后无法修改。因此,该类是线程安全的。

英文:

Most of the time, object is not thread-safe because its state changes are not properly guarded.

Read the following paragraph which I took from "Java Concurrency in Practice" :

> Stateless: it has no fields and references no fields from other classes. The transient state for a particular computation exists solely in local variables that are stored on the thread’s stack and are accessible only to the executing thread. Stateless objects are always thread-safe.

Chapter 2 also shares some ways to make stateful objects thread-safe.

> There are three ways to fix stateful objects:
> - Don’t share the state variable across threads;
> - Make the state variable immutable; or
> - Use synchronization whenever accessing the state variable.

In the above example, locations is a concurrent hashmap, it's a threadsafe collection. unmodifiableMap can't be mutated after initialization. Hence the class is thread-safe.

答案3

得分: -2

setLocation正在使用getLocation()。这会从locations中返回一个现有的条目。

两个不同的线程可以并行调用setLocation(),并且可以调用SafePoint.set()。
这取决于SafePoint.set()是否是线程安全的。如果SafePoint.set()需要更多时间并且不是原子操作,那么在线程1通过SafePoint.set()写入时,另一个线程也可能在意图之间写入。

如果你只能通过setLocation()访问SafePoint,你可以像这样做:

public void setLocation(String id, int x, int y) {  
   if (!locations.containsKey(id))
       throw new IllegalArgumentException("无效的车辆名称:" + id);
   SafePoint safePoint = locations.get(id);
   synchronized(safePoint){
     safePoint.set(x, y);
   }
}

每个调用setLocation的线程都会进入同步块。只有一个线程可以进入此块。

英文:

setLocation is using getLocation(). This is returning an existing entry from locations.

Two differnt threads could call setLocation() in parallel and can call SafePoint.set().
It depends on SafePoint.set() if this is Thread-Safe. If SafePoint.set() needs more time and is not atomic, it can happen while Thread 1 is writing via SafePoint.set() another is intending it, too.

If you can only access to SafePoint via setLocation() you could do it like that:

public void setLocation(String id, int x, int y) {  
   if (!locations.containsKey(id))
       throw new IllegalArgumentException("invalid vehicle name: " + id);
   SafePoint safePoint = locations.get(id);
   synchronized(safePoint){
     safePoint.set(x, y);
   }
}

Every thread calling setLocation will come to the synchronized block. Only one can enter this block.

huangapple
  • 本文由 发表于 2020年9月16日 21:44:48
  • 转载请务必保留本文链接:https://go.coder-hub.com/63921422.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定