易失性关键字原子性

时间:2018-01-27 02:34:58

标签: java multithreading synchronization volatile atomicity

我正在尝试在多线程中学习volatile field modifier。我发现了这句话:

  

当一个线程读取和写入共享变量而其他线程读取相同时,首选易失性。然而,如果共享变量上有两个以上的线程执行读写操作,那么只有volatile是不够的,你也需要同步。

我知道volatile提供了可见性和发生之前的保证,但是是否可以提供一个简单的小代码示例来演示上面需要同步块的语句?

3 个答案:

答案 0 :(得分:3)

public class TwoInts {
    private volatile int i1; 
    private volatile int i2;  

    public void set(int i1, int i2) {
        this.i1 = i1;
        this.i2 = i2;
    }

    public boolean same() {
        return i1 == i2;
    }
}

现在,如果你有一个线程这样做:

while (true) {
    twoInts.set(i, i); 
    i++;
}

和第二个线程这样做:

while (true) {
    if (!twoInts.same()) {
        System.out.println("Ooops!!");
    }
}

然后您将观察引用文本正在讨论的问题。如果你重写TwoInts类来制作方法synchronized那么“Oooops !!”消息将停止。

答案 1 :(得分:2)

有很多这样的例子......这里有一个:

volatile int i = 0;

// Thread #1
while (true) {
    i = i + 1;
}

// Thread #2
while (true) {
    Console.WriteLine(i);
}

在这种情况下,线程#1和线程#2都在读取变量i,但只有线程#1正在写入它。线程#2将始终看到i的递增值。

如果没有volatile关键字,您偶尔会看到奇怪的行为,通常是在多处理器计算机或多核CPU上。发生了什么(这里稍微简化)是线程#1和#2都在自己的CPU上运行,每个都获得它自己的i副本(在它的CPU缓存中和/或寄存器)。如果没有volatile关键字,他们可能永远不会互相更新已更改的值。

与此示例形成对比:

static volatile int i = 0;

// Thread #1
while (true) {
    i = i + 1;
}

// Thread #2
while (true) {
    if (i % 2 == 0) 
        i == 0;
    else
        Console.WriteLine(i);
}

所以在这里,线程#1试图单调递增i,而线程#2要么将i设置为0(如果i是偶数)或将其打印到控制台,如果我是奇怪的。你会期望Thread#2永远不会在控制台上输出偶数,对吗?

事实证明事实并非如此。因为您对i的访问没有同步,所以Thread#2可能看到一个奇数值,移动到else分支,然后Thread#1递增i的值。 },导致线程#2打印出偶数。

在这种情况下,解决问题的一种方法是使用基本锁定作为同步的一种形式。因为我们无法锁定基元,所以我们引入一个空白对象来锁定:

static volatile int i = 0;
static Object lockOnMe = new Object();

// Thread #1
while (true) {
    lock (lockOnMe) {
        i = i + 1;
    }
}

// Thread #2
while (true) {
    lock (lockOnMe) {
        if (i % 2 == 0) 
            i == 0;
        else
            Console.WriteLine(i);
    }
}

答案 2 :(得分:2)

假设您有int i和两个帖子,您希望每个人都阅读i并设置i = i + 1

像这样:

public class Main {

    private static volatile int i = 0;

    public static void main(String[] args) throws Exception{

        Runnable  first = new Runnable() {
            @Override
            public void run() {
                System.out.println("Thread_1 see i = " + i);
                i++;
                System.out.println("Thread_1 set i = " + i);
            }
        };

        Runnable second = new Runnable() {
            @Override
            public void run() {
                System.out.println("Thread_2 see i = " + i);
                i++;
                System.out.println("Thread_2 set i = " + i);
            }
        };
        new Thread(first).start();
        new Thread(second).start();
    }
}

结果是:

Thread_1 see i = 0
Thread_2 see i = 0
Thread_1 set i = 1
Thread_2 set i = 2

如您所见,Thread_2获得0并设置2(因为Thread_1已将i更新为1),这是不期望的。

添加同步后,

public class Main {

    private static volatile int i = 0;

    public static void main(String[] args) throws Exception{

        Runnable  first = new Runnable() {
            @Override
            public void run() {
                synchronized (Main.class) {
                    System.out.println("Thread_1 see i = " + i);
                    i++;
                    System.out.println("Thread_1 set i = " + i);
                }
            }
        };

        Runnable second = new Runnable() {
            @Override
            public void run() {
                synchronized (Main.class) {
                    System.out.println("Thread_2 see i = " + i);
                    i++;
                    System.out.println("Thread_2 set i = " + i);
                }
            }
        };
        new Thread(first).start();
        new Thread(second).start();
    }
}

有效:

Thread_2 see i = 0
Thread_2 set i = 1
Thread_1 see i = 1
Thread_1 set i = 2