繁体   English   中英

java中的易失性和原子操作

[英]Volatile and atomic operation in java

我已阅读有关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方法上做了4次操作,它们是原子操作吗? 如果多个线程同时调用doSomething()方法会发生什么?

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

读取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()被同步,其原子性将得到保证,并且上述场景是不可能的。

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

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

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

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

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

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

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

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

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

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

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

原子操作和同步:

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

如果我们正在读/写一个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();
        }
    }

}

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

我们可以使用java.util.concurrent.atomic来解决这个问题,它始终将计数值输出为8,因为AtomicInteger方法incrementAndGet()以原子方式将当前值递增1。 如下所示:

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();
        }
    }

}

来源: java中的原子操作

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM