java中的易失性和原子操作

时间:2012-08-16 23:09:26

标签: java atomic volatile

我读过有关Java中原子操作的文章,但仍有一些疑问需要澄清:

int volatile num;
public void doSomething() {
  num = 10;  // write operation
  System.out.println(num)   // read
  num = 20;  // write
  System.out.println(num);  // read
}

所以我在1方法上完成了w-r-w-r 4操作,它们是原子操作吗?如果多个线程同时调用doSomething()方法会发生什么?

5 个答案:

答案 0 :(得分:6)

如果没有线程会看到中间状态,那么操作就是原子操作,即操作将完全完成,或者根本不完成。

读取int字段是原子操作,即一次读取所有32位。编写一个int字段也是原子的,该字段要么已完全写入,要么根本不写。

然而,方法doSomething()不是原子的;在执行该方法时,一个线程可能会将CPU输出到另一个线程,并且该线程可能会看到已执行了一些但不是所有操作。

也就是说,如果线程T1和T2都执行doSomething(),则可能发生以下情况:

T1: num = 10;
T2: num = 10;
T1: System.out.println(num); // prints 10
T1: num = 20;
T1: System.out.println(num); // prints 20
T2: System.out.println(num); // prints 20
T2: num = 20;
T2: System.out.println(num); // prints 20

如果doSomething()被同步,它的原子性将得到保证,并且上述场景是不可能的。

答案 1 :(得分:2)

volatile确保如果你有一个线程A和一个线程B,那么两者都会看到对该变量的任何改变。因此,如果它在某个时刻线程A改变了这个值,那么线程B将来可以看它。

原子操作确保执行所述操作“一步到位”。这有点混乱,因为查看代码'x = 10;'可能看起来是“一步到位”,但实际上需要在CPU上执行几个步骤。原子操作可以通过多种方式形成,其中一种方法是使用synchronized

进行锁定
  • volatile关键字承诺的内容。
  • 获取对象(或静态方法中的Class)的锁定,并且没有两个对象可以同时访问它。

正如您在前面的评论中所提到的,即使您有三个单独的原子步骤,线程A正在某个时刻执行,但是线程B有可能在这三个步骤的中间开始执行。为了确保对象的线程安全性,必须将所有三个步骤组合在一起以完成一个步骤。这是使用锁的部分原因。

需要注意的一件非常重要的事情是,如果要确保两个线程永远不能同时访问您的对象,则必须同步所有方法。您可以在对象上创建一个非同步方法来访问存储在对象中的值,但这会损害类的线程安全性。

您可能对java.util.concurrent.atomic库感兴趣。我也不是这方面的专家,所以我建议给我推荐一本书:Java Concurrency in Practice

答案 2 :(得分:1)

每个人对volatile变量的读写都是原子的。这意味着线程在读取时不会看到num的值发生变化,但它仍然可以在每个语句之间发生变化。因此,运行doSomething而其他线程正在执行相同操作的线程将打印10或20,然后再打印10或20.在所有线程完成调用doSomething之后,num的值将为20。

答案 3 :(得分:0)

我根据Brian Roach的评论修改了我的答案。

它是原子的,因为在这种情况下它是整数。

易变性只能提高线程间的可见性,但不能提供原子能见度。 volatile可以让你看到整数的变化,但不能保证变化中的整合。

例如,long和double会导致意外的中间状态。

答案 4 :(得分:0)

原子操作和同步:

原子执行在单个任务单元中执行,不会受到其他执行的影响。多线程环境中需要进行原子操作以避免数据不规则。

如果我们正在读/写一个int值,那么它是一个原子操作。但通常如果它在方法内部,那么如果方法未同步,则许多线程可以访问它,这可能导致值不一致。但是,int ++不是原子操作。因此,当一个线程读取它的值并将其递增1时,其他线程已读取较旧的值,从而导致错误的结果。

要解决数据不一致问题,我们必须确保count上的递增操作是原子的,我们可以使用Synchronization来实现,但是Java 5 java.util.concurrent.atomic提供了int和long的包装类,可用于实现此目的原子地不使用同步。

使用int可能会产生数据数据不一致,如下所示:

public class AtomicClass {

    public static void main(String[] args) throws InterruptedException {

        ThreardProcesing pt = new ThreardProcesing();
        Thread thread_1 = new Thread(pt, "thread_1");
        thread_1.start();
        Thread thread_2 = new Thread(pt, "thread_2");
        thread_2.start();
        thread_1.join();
        thread_2.join();
        System.out.println("Processing count=" + pt.getCount());
    }

}

class ThreardProcesing implements Runnable {
    private int count;

    @Override
    public void run() {
        for (int i = 1; i < 5; i++) {
            processSomething(i);
            count++;
        }
    }

    public int getCount() {
        return this.count;
    }

    private void processSomething(int i) {
        // processing some job
        try {
            Thread.sleep(i * 1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}
  

OUTPUT:计数值在5,6,7,8之间变化

我们可以使用java.util.concurrent.atomic来解决此问题,AtomicInteger始终将计数值输出为8,因为incrementAndGet()方法public class AtomicClass { public static void main(String[] args) throws InterruptedException { ThreardProcesing pt = new ThreardProcesing(); Thread thread_1 = new Thread(pt, "thread_1"); thread_1.start(); Thread thread_2 = new Thread(pt, "thread_2"); thread_2.start(); thread_1.join(); thread_2.join(); System.out.println("Processing count=" + pt.getCount()); } } class ThreardProcesing implements Runnable { private AtomicInteger count = new AtomicInteger(); @Override public void run() { for (int i = 1; i < 5; i++) { processSomething(i); count.incrementAndGet(); } } public int getCount() { return this.count.get(); } private void processSomething(int i) { // processing some job try { Thread.sleep(i * 1000); } catch (InterruptedException e) { e.printStackTrace(); } } } 会将当前值原子递增1。如下所示:

tooltip

来源:Atomic Operations in java