Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/java/384.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java 多线程可见性和原子性_Java_Multithreading - Fatal编程技术网

Java 多线程可见性和原子性

Java 多线程可见性和原子性,java,multithreading,Java,Multithreading,我读了volatile关键字,读到了它,我读到volatile关键字保证可见性而不是原子性,现在可见性是一个线程中所做的更改对另一个线程是立即可见的,所以为什么我们需要使用原子整数或原子布尔..它需要什么,有人能解释一下volatile的一个常见用法,以及可见性和原子性的差异吗。也就是说,它保证变量不能被2个或更多线程同时更改,例如,该代码不是线程安全的,它有竞态条件问题: private volatile int a; private void myMethod() { a++; }

我读了volatile关键字,读到了它,我读到volatile关键字保证可见性而不是原子性,现在可见性是一个线程中所做的更改对另一个线程是立即可见的,所以为什么我们需要使用原子整数或原子布尔..它需要什么,有人能解释一下volatile的一个常见用法,以及可见性和原子性的差异吗。也就是说,它保证变量不能被2个或更多线程同时更改,例如,该代码不是线程安全的,它有
竞态条件
问题:

private volatile int a;

private void myMethod() {
   a++;
}
因为线程A可以读取
A
的值,它将是0,那么线程B读取
A
的值-它也等于0。但是,两个线程都会增加这个读取值,它将保持值
1
,但应该是
2


即原子性保证
读-改-写操作将正确执行,而
volatil
仅保证“读”操作将正确执行。

定义

如果任何其他线程认为某个操作已经完全发生,或者根本没有发生,那么该操作就是原子操作

一个操作对另一个线程是可见的,如果该线程认为它已经发生了

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

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使写入(或读取)变量成为原子变量,即使它们是
long
double
类型

写入volatile变量对随后读取该变量的所有线程都是可见的

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

volatile boolean shouldBeRunning = true;

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

void run() { // invoked by T2
    while (shouldBeRunning) {
        doWork();
    }
}
原子布尔的优势

如上所述,我们可以通过声明
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();
    }
}

让我们分别介绍这两个概念

可见性

对变量的更改保证对访问该变量的所有线程都可见。可见性防止线程缓存变量的本地副本

原子性

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

需要可见性但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
的线程看到
布尔值的更改,但由于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());
            }
        }
    }
}
私有静态易失性布尔运行;
私有静态AtomicInteger计数器=新的AtomicInteger(0);
公共静态void main(最终字符串[]args)引发异常{
final ExecutorService ExecutorService=Executors.newCachedThreadPool();
run=true;
executorService.submit(新的递减器());
executorService.submit(新的增量器());
时间单位。分钟。睡眠(1);
运行=错误;
执行人服务。等待终止(1,时间单位。天);
}
私有静态最终类Incremetor实现Runnable{
@凌驾
公开募捐{
while(运行){
while(counter.get()<10){
试一试{
时间单位。秒。睡眠(1);
}捕获(中断异常例外){
//哦,好吧
}
System.out.println(counter.incrementAndGet());
}
}
}
}
私有静态最终类递减器实现可运行{
@凌驾
公开募捐{
while(运行){
while(counter.get()>=10){
试一试{
时间单位。秒。睡眠(1);
}捕获(中断异常例外){
//哦,好吧
}
System.out.println(counter.decrementAndGet());
}
}
}
}
这个稍微做作的示例显示两个线程读取一个计数器变量的设置。线程执行一些长时间运行的任务,然后设置计数器。一旦第一个线程将计数器设置为10,则第二个线程开始递减计数器。结果是计数器通常在9和10之间切换,但可能会出现由于线程调度而导致计数器在第二个线程中一直递减的情况

该示例使用繁忙的等待来强调原子性的需要。这在实践中永远不应该使用

突出的一点是,
i++
不是原子的。它包括一次读,一次分配,然后一次写。这里是变量的可见性不足以保证看到正确的值。另一个线程可以看到上述任何操作之间的变量

在这里,您可以使用同步化的
块,但是