java - java多线程中如何锁定多个资源

标签 java multithreading

我需要在我的 java 类中的一个方法中锁定多个对象。有关示例,请查看以下类:

public class CounterMultiplexer {

    private int counter =0;
    private int multiPlexer =5;
    private Object mutex = new Object();

    public void calculate(){

        synchronized(mutex){
            counter ++;
            multiPlexer = multiPlexer*counter;
        }
     }

   public int getCounter(){
      return counter;
   }
   public int getMux(){
      return multiPlexer;
   }
}

在上面的代码中,我有两个资源可以被多个线程访问。这两个资源是 counter 和 multiPlexer 属性。正如您在上面的代码中看到的,我已经使用互斥锁锁定了这两个资源。

这种锁定方式是否正确?我是否需要使用嵌套的 Synchronized 语句来锁定计算方法中的两个资源?

最佳答案

所以您对互斥锁(和原子性)的理解是正确的。但是,Java 内存模型中还有一个额外的问题,即您必须考虑的可见性

基本上读写都要同步,否则读不保证能看到写。对于您的 getter,JIT 很容易将这些值提升到寄存器中并且永远不会重新读取它们,这意味着永远不会看到写入的值。这称为数据竞争,因为无法保证写入和读取的顺序。

要打破数据竞争,您必须使用内存排序语义。这归结为同步读取和写入。每次你需要在任何地方使用同步时,你都必须这样做,而不仅仅是在上面的特定情况下。

您几乎可以使用任何方法(如 AtomicInteger),但最简单的方法可能是重新使用您已经拥有的 mutex,或者使两个原始值 volatile 。两种都有效,但您必须至少使用一种。

public class CounterMultiplexer {

    private int counter =0;
    private int multiPlexer =5;
    private Object mutex = new Object();

    public void claculate(){

        synchronized(mutex){
            counter ++;
            multiPlexer = multiPlexer*counter;
        }
     }

   public int getCounter(){
      synchronized(mutex){
        return counter;
     }
   }

   public int getMux(){
      synchronized(mutex){
        return multiPlexer;
      }
   }
}

因此,要深入了解这一点,我们必须阅读规范。您还可以阅读 Brian Goetz 的Java 并发实践,我强烈推荐这本书,因为他详细介绍了这类内容,并通过简单的示例非常清楚地表明您必须同步始终在读取和写入上。

规范的相关部分是第 17 章,特别是 section 17.4 Memory Model.

仅引用相关部分:

The Java programming language memory model works by examining each read in an execution trace and checking that the write observed by that read is valid according to certain rules.

这一点很重要。 每次读取都会被检查。该模型无法通过单独检查写入然后假设读取可以看到写入来工作。

Two actions can be ordered by a happens-before relationship. If one action happens-before another, then the first is visible to and ordered before the second.

happens-before 是允许读取看到写入的。没有它,JVM 可以自由地优化您的程序,而这些方式可能会阻止看到写入(例如将值提升到寄存器中)。

The happens-before relation defines when data races take place.

A set of synchronization edges, S, is sufficient if it is the minimal set such that the transitive closure of S with the program order determines all of the happens-before edges in the execution. This set is unique.

It follows from the above definitions that:

An unlock on a monitor happens-before every subsequent lock on that monitor.

A write to a volatile field (§8.3.1.4) happens-before every subsequent read of that field.

So happens-before 定义数据竞争何时发生(或不发生)。 volatile 是如何工作的我认为从上面的描述中是显而易见的。对于监视器(您的 mutex),请务必注意 happens-before 是由 unlock 和随后的 lock 建立的, 因此要为读取建立happens-before,您确实需要在读取之前再次锁定监视器。

We say that a read r of a variable v is allowed to observe a write w to v if, in the happens-before partial order of the execution trace:

r is not ordered before w (i.e., it is not the case that hb(r, w)), and

there is no intervening write w' to v (i.e. no write w' to v such that hb(w, w') and hb(w', r)).

Informally, a read r is allowed to see the result of a write w if there is no happens-before ordering to prevent that read.

“允许观察”意味着读实际上会看到写。所以 happens-before 是我们需要看到的写入,锁(mutex 在你的程序中)或 volatile 都可以工作。

还有很多(其他事情会导致先行发生)并且还有 API 和 java.utli.concurrent 中的类,这也会导致内存排序(和可见性)语义。但是你的程序有血淋淋的细节。

关于java - java多线程中如何锁定多个资源,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53120361/

相关文章:

java - 字符串到数字转换的性能问题

java - JSONGenerator 如何输出 JSON 中的对象

java - 为什么 ScheduledExecutorService 没有按预期工作?

java - 取消 Future 并仍然检索其返回值

Java程序无限循环,没有任何错误信息

java - Eclipse 挂起一个特定的线程,同时让其他线程运行

c++ - 委托(delegate)和线程 : CallbackContext parameter

java - Java 中的通用数组比较

java - Netbeans - 设计 View 不可用

java - 无法在 Netbeans 中的 Tomcat 服务器上部署 web 应用程序