3

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

4

5 回答 5

7

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

读取一个 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() 是同步的,它的原子性将得到保证,而上述情况是不可能的。

于 2012-08-17T00:20:58.137 回答
2

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

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

  • volatile 关键字的承诺。
  • 获取一个对象(或者静态方法的类)的锁,并且没有两个对象可以同时访问它。

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

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

您可能对java.util.concurrent.atomic图书馆感兴趣。我也不是这些问题的专家,所以我会推荐一本推荐给我的书:Java Concurrency in Practice

于 2012-08-17T00:22:15.423 回答
1

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

于 2012-08-16T23:51:17.203 回答
0

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

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

Volatile 只能在线程之间获取可见性,但不能是原子的。volatile 可以让你看到整数的变化,但不能让你看到变化中的积分。

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

于 2012-08-16T23:56:24.523 回答
0

原子操作和同步:

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

如果我们正在读/写一个 int 值,那么它就是一个原子操作。但通常如果它在一个方法内,那么如果该方法不同步,许多线程可以访问它,这可能导致值不一致。但是,int++ 不是原子操作。因此,当一个线程读取它的值并将其加一时,另一个线程已经读取了导致错误结果的旧值。

为了解决数据不一致,我们必须确保对 count 的递增操作是原子的,我们可以使用同步来做到这一点,但 Java 5java.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()以原子方式将当前值加一。如下图:

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中的原子操作

于 2017-03-31T06:15:30.770 回答