Java易失性,同步,原子示例

问题描述 投票:0回答:2

嗨,我正在阅读实践中的java并发,我读了有趣的声明说明

锁定可以保证可见性和原子性; volatile变量只能保证可见性。

任何人都可以解释一下,如果将变量声明为volatile,所有其他读取线程都会获得更新值,那么我为什么要关注语句中的原子性,如:counter = counter + 1;

提前致谢。

java multithreading
2个回答
2
投票

volatile关键字的作用大致是对该变量的每个单独的读或写操作都是原子的。

然而,值得注意的是,需要多个读/写的操作 - 例如i ++,相当于i = i + 1,它执行一次读取和一次写入 - 不是原子的,因为另一个线程可能写入i在读和写之间。

Atomic类,如AtomicInteger和AtomicReference,以原子方式提供更多种类的操作,特别是包括AtomicInteger的增量。

这就是为什么你需要关心像counter = counter + 1这样的语句中的原子性

请查看此帖子Volatile Vs Atomic


2
投票

这是一个独立的示例可执行应用程序,它演示了volatile本身是不够的。四个线程每个增加一个计数器10,000次,所以你期望计数器最后是40,000。它使用原始int变量和AtomicInt,并且每次尝试练习5次。

import java.util.Collections;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;

class AtomicDemo {
    interface Demo extends Callable<Void> {
        int getCounter();
    }

    static class UsePrimitive implements Demo {
        private volatile int counter = 0;

        public Void call() throws Exception {
            for (int i = 1; i <= 10000; ++i) {
                ++counter;
            }
            return null;
        }

        public int getCounter() {
            return counter;
        }
    }

    static class UseAtomic implements Demo {
        final AtomicInteger counter = new AtomicInteger(0);

        public Void call() throws Exception {
            for (int i = 1; i <= 10000; ++i) {
                counter.incrementAndGet();
                System.out.print("");
            }
            return null;
        }

        public int getCounter() {
            return counter.get();
        }
    }

    public static void main(String[] args) throws Exception {
        ExecutorService exec = Executors.newFixedThreadPool(4);
        for (int i = 1; i <= 5; ++i) {
            Demo demo = new UsePrimitive();
            exec.invokeAll(Collections.nCopies(4, demo));
            System.out.println("Count to 40000 using primitive, attempt number " + i + ": " + demo.getCounter());
        }
        for (int i = 1; i <= 5; ++i) {
            Demo demo = new UseAtomic();
            exec.invokeAll(Collections.nCopies(4, demo));
            System.out.println("Count to 40000 using atomic, attempt number " + i + ": " + demo.getCounter());
        }
        exec.shutdownNow();
    }
}

典型输出:

Count to 40000 using primitive, attempt number 1: 39711
Count to 40000 using primitive, attempt number 2: 39686
Count to 40000 using primitive, attempt number 3: 39972
Count to 40000 using primitive, attempt number 4: 39840
Count to 40000 using primitive, attempt number 5: 39865
Count to 40000 using atomic, attempt number 1: 40000
Count to 40000 using atomic, attempt number 2: 40000
Count to 40000 using atomic, attempt number 3: 40000
Count to 40000 using atomic, attempt number 4: 40000
Count to 40000 using atomic, attempt number 5: 40000

你看,只有使用AtomicInt,你才能获得预期的结果。

© www.soinside.com 2019 - 2024. All rights reserved.