本文介绍了如何在Java多线程中锁定多个资源的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我需要在我的java类的一个方法中锁定多个对象.例如,请看下面的类:

I have a requirement of locking several objects in one method in my java class. For an example look at the following class:

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属性.如您在上面的代码中看到的,我已经使用互斥锁锁定了这两个资源.

In the above code, I have two resources that could access by a more than one thread. Those two resources are counter and the multiPlexer properties. As you can see in the above code I have locked both the resources using a mutex.

这种锁定方式正确吗?我是否需要使用嵌套的Synchronized语句将两个资源锁定在calculate方法内?

Is this way of locking is correct? Do I need to use nested Synchronized statements to lock both resources inside the calculate method?

推荐答案

因此,互斥量(和原子性)的想法是正确的.但是,Java内存模型还有一个额外的折痕,那就是必须考虑的 visibility .

So you've got the idea of mutex (and atomicity) correct. However there's an additional wrinkle in the Java memory model which is visibility that you have to take into consideration.

基本上,读取和写入都必须同步,否则不能保证读取可以看到写入.对于您的使用者来说,JIT将这些值提升到寄存器中而永远不会重新读取它们将非常容易,这意味着写入的值将永远不会被看到.之所以称为数据竞赛,是因为无法保证写入和读取的顺序.

Basically, both reads and writes must be synchronized, or the read is not guaranteed to see the write. For your getters, it would be very easy for the JIT to hoist those values into a register and never re-read them, meaning the value written would never be seen. This is called a data race because the order of the write and the read cannot be guaranteed.

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

To break the data race, you have to use memory ordering semantics. This boils down to synchronizing both the reads and the writes. And you have to do this every time you need to use synchronization anywhere, not just in the specific case you have above.

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

You could use almost any method (like AtomicInteger) but probably the easiest is either to re-use the mutex you already have, or to make the two primitive values volatile. Either works, but you must use at least one.

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并发实践,我强烈建议您这样做,因为他详细介绍了这类内容,并提供了简单的示例,使您很清楚地说明必须同步.总是在读写上都是如此.

So to get into this more, we have to read the spec. You can also get Brian Goetz's Java Concurrency in Practice which I highly recommend because he covers this sort of thing in detail and with simple examples that make it very clear that you must syncrhonize on both reads and writes, always.

规范的相关部分是第17章,尤其是第17.4节内存模型".

The relevant section of the spec is Chapter 17, and in particular section 17.4 Memory Model.

只引用相关部分:

这一点很重要. 检查每次读取.仅检查写入内容,然后假设读取内容可以看到写入内容,该模型就无法工作.

That bit is important. Each read is checked. The model doesn't work by checking the writes alone and then assuming the reads can see the write.

before-before是允许读取看到写入的内容.没有它,JVM可以自由地以可能无法看到写入的方式(例如将值提升到寄存器中)来优化程序.

The happens-before is what allows reads to see a write. Without it, the JVM is free to optimize your program in ways that might preclude seeing the write (like hoisting a value into a register).

如果一组同步边沿S是最小集,则该边沿就足够了,以使S的传递性闭包与程序顺序决定了执行中所有发生在边沿之前的边沿.这组是唯一的.

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.

在随后每次对该字段进行读取之前,都会对易失字段(第8.3.1.4节)进行写操作.

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

因此,before-before定义何时进行(或不进行)数据争用.我认为volatile的工作方式从上面的描述中显而易见.对于监视器(您的mutex),需要注意的是 happens-before 是通过 unlock 以及随后的 lock 建立的因此,要在读取之前建立 happens-before ,您确实需要在读取之前再次锁定监视器.

So happens-before defines when a data race does (or does not) take place. How volatile works I think is obvious from the description above. For a monitor (your mutex), it's important to note that happens-before is established by a unlock followed by a later lock, so to establish happens-before for the read, you do need to lock the monitor again just before the read.

r不在w之前排序(即,不是hb(r,w)),并且

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

没有向v写入w'(即没有向v写入w'使得hb(w,w')和hb(w',r))

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

非正式地,如果不发生任何事件,则允许在读取r之前查看写入w的结果-在命令阻止该读取之前.

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.

允许观察"表示读操作实际上将看到写操作.因此,发生-之前是我们需要查看写入内容的地方,并且锁(程序中的mutex)或volatile都可以使用.

"Allowed to observe" means the read actually will see the write. So happens-before is what we need to see the write, and either the lock (mutex in your program) or volatile will work.

还有更多(其他原因导致事前发生),并且java.utli.concurrent中的类也有API,这也会引起内存排序(和可见性)语义.但是程序上有很多细节.

There's lots more (other things cause happens-before) and there's the API too with classes in java.utli.concurrent that will also cause memory ordering (and visibility) semantics. But there's the gory details on your program.

这篇关于如何在Java多线程中锁定多个资源的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

09-14 09:10