简体   繁体   English

java中的易失性和原子操作

[英]Volatile and atomic operation in java

I have read article concerning atomic operation in Java but still have some doubts needing to be clarified: 我已阅读有关Java中原子操作的文章,但仍有一些疑问需要澄清:

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

So i have done wrwr 4 operations on 1 method, are they atomic operations? 所以我在1方法上做了4次操作,它们是原子操作吗? What will happen if multiple threads invoke doSomething() method simultaneously ? 如果多个线程同时调用doSomething()方法会发生什么?

An operation is atomic if no thread will see an intermediary state, ie the operation will either have completed fully, or not at all. 如果没有线程将看到中间状态,则操作是原子的,即操作将完全完成,或者根本不完成。

Reading an int field is an atomic operation, ie all 32 bits are read at once. 读取int字段是原子操作,即一次读取所有32位。 Writing an int field is also atomic, the field will either have been written fully, or not at all. 编写一个int字段也是原子的,该字段要么已完全写入,要么根本不写入。

However, the method doSomething() is not atomic; 但是,方法doSomething()不是原子的; a thread may yield the CPU to another thread while the method is being executing, and that thread may see that some, but not all, operations have been executed. 一个线程可能在执行该方法时将CPU输出到另一个线程,并且该线程可能会看到已执行了一些但不是所有操作。

That is, if threads T1 and T2 both execute doSomething(), the following may happen: 也就是说,如果线程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

If doSomething() were synchronized, its atomicity would be guaranteed, and the above scenario impossible. 如果doSomething()被同步,其原子性将得到保证,并且上述场景是不可能的。

volatile ensures that if you have a thread A and a thread B, that any change to that variable will be seen by both. volatile确保如果你有一个线程A和一个线程B,那么两者都会看到对该变量的任何更改。 So if it at some point thread A changes this value, thread B could in the future look at it. 因此,如果它在某个时刻线程A改变了这个值,那么线程B将来可能会看到它。

Atomic operations ensure that the execution of the said operation happens "in one step." 原子操作确保所述操作的执行“一步到位”。 This is somewhat confusion because looking at the code 'x = 10;' 这有点混乱,因为查看代码'x = 10;' may appear to be "one step", but actually requires several steps on the CPU. 可能看起来是“一步到位”,但实际上需要在CPU上执行几个步骤。 An atomic operation can be formed in a variety of ways, one of which is by locking using synchronized : 可以通过多种方式形成原子操作,其中一种方法是使用synchronized进行锁定:

  • What the volatile keyword promises. volatile关键字承诺的内容。
  • The lock of an object (or the Class in the case of static methods) is acquired, and no two objects can access it at the same time. 获取对象的锁(或在静态方法的情况下为Class),并且没有两个对象可以同时访问它。

As you asked in a comment earlier, even if you had three separate atomic steps that thread A was executing at some point, there's a chance that thread B could begin executing in the middle of those three steps. 正如您之前在评论中提到的那样,即使您有三个单独的原子步骤,线程A在某个时刻执行,但线程B有可能在这三个步骤的中间开始执行。 To ensure the thread safety of the object, all three steps would have to be grouped together to act like a single step. 为了确保对象的线程安全性,必须将所有三个步骤组合在一起以完成一个步骤。 This is part of the reason locks are used. 这是使用锁的部分原因。

A very important thing to note is that if you want to ensure that your object can never be accessed by two threads at the same time, all of your methods must be synchronized. 需要注意的一件非常重要的事情是,如果要确保两个线程永远不能同时访问您的对象,则必须同步所有方法。 You could create a non-synchronized method on the object that would access the values stored in the object, but that would compromise the thread safety of the class. 您可以在对象上创建一个非同步方法来访问存储在对象中的值,但这会损害类的线程安全性。

You may be interested in the java.util.concurrent.atomic library. 您可能对java.util.concurrent.atomic库感兴趣。 I'm also no expert on these matters, so I would suggest a book that was recommended to me: Java Concurrency in Practice 我也不是这方面的专家,所以我建议给我推荐一本书: Java Concurrency in Practice

Each individual read and write to a volatile variable is atomic. 每个人对volatile变量的读写都是原子的。 This means that a thread won't see the value of num changing while it's reading it, but it can still change in between each statement. 这意味着线程在读取时不会看到num的值发生变化,但它仍然可以在每个语句之间发生变化。 So a thread running doSomething while other threads are doing the same, will print a 10 or 20 followed by another 10 or 20. After all threads have finished calling doSomething , the value of num will be 20. 因此,当其他线程执行相同操作时,运行doSomething线程将打印10或20,然后再打印10或20.在所有线程完成调用doSomething ,num的值将为20。

My answer modified according to Brian Roach's comment. 我根据Brian Roach的评论修改了我的答案。

It's atomic because it is integer in this case. 它是原子的,因为在这种情况下它是整数。

Volatile can only ganrentee visibility among threads, but not atomic. 易失性只能提高线程间的可见性,但不能提供原子能见度。 volatile can make you see the change of the integer, but cannot ganrentee the integration in changes. volatile可以让你看到整数的变化,但不能在变化中进行整合。

For example, long and double can cause unexpected intermediate state. 例如,long和double可能会导致意外的中间状态。

Atomic Operations and Synchronization: 原子操作和同步:

Atomic executions are performed in a single unit of task without getting affected from other executions. 原子执行在单个任务单元中执行,不会受到其他执行的影响。 Atomic operations are required in multi-threaded environment to avoid data irregularity. 多线程环境中需要进行原子操作以避免数据不规则。

If we are reading/writing an int value then it is an atomic operation. 如果我们正在读/写一个int值,那么它就是一个原子操作。 But generally if it is inside a method then if the method is not synchronized many threads can access it which can lead to inconsistent values. 但通常如果它在方法内部,那么如果方法未同步,则许多线程可以访问它,这可能导致值不一致。 However, int++ is not an atomic operation. 但是,int ++不是原子操作。 So by the time one threads read it's value and increment it by one, other thread has read the older value leading to wrong result. 因此,当一个线程读取它的值并将其递增1时,其他线程已读取较旧的值,从而导致错误的结果。

To solve data inconsistency, we will have to make sure that increment operation on count is atomic, we can do that using Synchronization but Java 5 java.util.concurrent.atomic provides wrapper classes for int and long that can be used to achieve this atomically without usage of Synchronization. 为了解决数据不一致问题,我们必须确保count上的递增操作是原子的,我们可以使用Synchronization来实现,但Java 5 java.util.concurrent.atomic提供了int和long的包装类,可用于原子地实现不使用同步。

Using int might create data data inconsistencies as shown below: 使用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: count value varies between 5,6,7,8 输出:计数值在5,6,7,8之间变化

We can resolve this using java.util.concurrent.atomic that will always output count value as 8 because AtomicInteger method incrementAndGet() atomically increments the current value by one. 我们可以使用java.util.concurrent.atomic来解决这个问题,它始终将计数值输出为8,因为AtomicInteger方法incrementAndGet()以原子方式将当前值递增1。 shown below: 如下所示:

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

}

Source: Atomic Operations in java 来源: java中的原子操作

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

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