多线程可见性和原子性

时间:2014-03-15 08:33:03

标签: java multithreading

我正在浏览volatile关键字,阅读它,在那里我读到volatile关键字保证可见性而不是原子性,现在可见性是一个线程中的更改立即可见另一个线程,所以为什么我们需要使用atomic Integer或者原子布尔...它是什么需要它,有人可以解释我一个常见的例子使用volatile,以及可见性和原子性的差异

3 个答案:

答案 0 :(得分:3)

定义

如果任何其他线程认为它完全发生或根本没有发生,则操作是 atomic

一个操作可见到另一个线程,该线程认为它已经发生。

原子性有用性的典型例子是将钱存入银行账户的操作:

synchronized void deposit(int dollars) {
    balance = balance + dollars;
} 

如果该操作不是原子操作,那么想要同时将钱存入同一账户的两个线程(T1和T2)可以按如下方式执行

T1 reads balance and adds dollars
T2 reads balance and adds dollars
T1 writes the result to balance
T2 writes the result to balance

这是不正确的,因为T1存入的钱无助于余额。

可见性有用性的典型示例是将信息从一个线程传递到另一个线程。例如,用户界面线程可能会将命令传递给后台线程。

易变的含义

volatile使得写入(或读取)变量原子,即使它们是longdouble类型。

随后从该变量读取的所有线程都可以看到写入volatile变量。

也就是说,我们可以使用volatile将信息传递给另一个线程,例如:

volatile boolean shouldBeRunning = true;

void stop() { // invoked by T1
    shouldBeRunning = false;
}

void run() { // invoked by T2
    while (shouldBeRunning) {
        doWork();
    }
}

AtomicBoolean的优势

正如我们上面所看到的,我们可以通过声明boolean来写入(或读取)volatile原子。但是,如果我们想要进行更大的操作,例如

void pauseOrResume() {
    paused = !paused;
}

原子,声明paused volatile是不够的,因为两个线程T1和T2可以同时执行pauseOrResume:

T1 reads paused (false) and negates it (true)
T2 reads paused (false) and negates it (true)
T1 writes its result (true)
T2 writes its result (true)

使用原子布尔值,我们可以防止这种情况发生:

void pauseOrResume() {
    boolean pausedBefore = paused.get();
    if (!paused.compareAndSet(pausedBefore, !pausedBefore)) {
        pauseOrResume();
    }
}

答案 1 :(得分:1)

让我们分别讨论这两个概念。

<强>可见性

保证变量对变量的所有线程都是可见的。可见性可防止缓存变量

的本地副本的线程

<强>原子性

对可变的变化是原子的,一次发生或根本不发生。在所有操作完成之前,线程不可能看到变量。

需要可见性但JLS自动保证原子性的示例:

private static volatile boolean run;

public static void main(final String[] args) throws Exception {
    final ExecutorService executorService = Executors.newSingleThreadExecutor();
    executorService.submit(new Worker());
    TimeUnit.MINUTES.sleep(1);
    run = false;
    executorService.awaitTermination(1, TimeUnit.DAYS);
}

private static final class Worker implements Runnable {

    @Override
    public void run() {
        while (run) {
            try {
                //do some long running task
                TimeUnit.SECONDS.sleep(10);
            } catch (InterruptedException ex) {
                //oh well                    
            }
            System.out.println("Working really hard");
        }
    }

}

我们在这里使用run作为volatile boolean标志。我们要求运行Worker的线程看到对boolean的值的更改,但由于assignemnt已经是原子的,我们不需要以任何方式进行同步。

可见度不足的原因,还需要原子性:

private static volatile boolean run;
private static AtomicInteger counter = new AtomicInteger(0);

public static void main(final String[] args) throws Exception {
    final ExecutorService executorService = Executors.newCachedThreadPool();
    run = true;
    executorService.submit(new Decrementor());
    executorService.submit(new Incremetror());
    TimeUnit.MINUTES.sleep(1);
    run = false;
    executorService.awaitTermination(1, TimeUnit.DAYS);
}

private static final class Incremetror implements Runnable {

    @Override
    public void run() {
        while (run) {
            while (counter.get() < 10) {
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException ex) {
                    //oh well
                }
                System.out.println(counter.incrementAndGet());
            }
        }
    }

}

private static final class Decrementor implements Runnable {

    @Override
    public void run() {
        while (run) {
            while (counter.get() >= 10) {
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException ex) {
                    //oh well
                }
                System.out.println(counter.decrementAndGet());
            }
        }
    }
}

这个有点人为的例子显示了两个读取设置计数器变量的线程。线程执行一些长时间运行的任务然后设置计数器。一旦第一个线程将计数器取为10,则第二个线程开始递减它。结果是计数器通常在9到10之间切换,但是由于线程调度,计数器可能会被第二个线程一直递减。

该示例使用忙等待来强调对原子性的需求。这绝不应该在实践中使用。

重点是i++ 不是原子。它涉及读取,分配和写入。这里变量的可见性不足以保证可以看到正确的值。另一个线程可以在上述任何操作之间看到变量。

在这里你可以使用synchroized块,但这非常昂贵。这就是Java AtomicXXX结构的用武之地。

答案 2 :(得分:0)

AtomicInterger其他Atomic...类保证原子性。即它保证变量不能被2个或多个线程同时更改,例如这个代码不是线程安全的,它有race condition个问题:

private volatile int a;

private void myMethod() {
   a++;
}

因为线程A可以读取a的值并且它将为0,所以线程B读取a的值 - 它也等于0.但是两个线程都会递增此读取值并且它将保持值1,但应为2

即。原子性保证read-change-write操作将正确完成,而volatil仅保证&#34;读取&#34;操作将正确完成。