JUC学习之Volatile和原子性问题

Volatile 关键字

当多个线程操作共享数据时,可保存线程内存之间数据可见,还可防止指令重排序。相对于synchronized 是一种更为轻量级的同步策略。

public class TestVolatile {
    public static void main(String[] args) {
        ThreadDemo td = new ThreadDemo();
        new Thread(td).start();
        while(true){
            if(td.isFlag()){
                System.out.println("----hello world----");
                break;
            }
        }
    }
}
class ThreadDemo implements Runnable {
    // 问题变量
    private volatile boolean flag = false;
    @Override
    public void run() {
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
        }
        flag = true;
        System.out.println("flag=" + isFlag());
    }
    public boolean isFlag() {
        return flag;
    }
    public void setFlag(boolean flag) {
        this.flag = flag;
    }
}

当flag不用Volatile关键字修饰输出:
    flag=true

当flag使用Volatile关键字修饰输出:
    ----hello world----
    flag=true

上面代码可以看出,如果有多条线程同时操作同享数据,如果不使用Volatile关键字修饰,就可能出现内存可见性问题。

注意:

  • 1、volatile 不具备“互斥性”,不像synchronized关键字:多线程情况下当其中一条线程占据CPU资源之后,其它线程需要阻塞。
  • 2、volatile 不保证变量的“原子性”。

原子性问题

原子性问题,例如:i++

int i = 10;
i = i++;
System.out.println(i);

输出结果:10

上述代码实际的底层实现:
int temp = i;
i = i + 1;
i = temp

Volatile 关键字不保证“原子性”测试

public class TestAtomicDemo {
    public static void main(String[] args) {
        AtomicDemo ad = new AtomicDemo();   
        for (int i = 0; i < 10; i++) {
            new Thread(ad).start();
        }
    }   
}
class AtomicDemo implements Runnable{
    // 这里变量使用Volatile关键字修饰
    private volatile int serialNumber = 0;
    @Override
    public void run() {
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
        }
        System.out.println(getSerialNumber());
    }
    public int getSerialNumber(){
        return serialNumber++;
    }
}
输出结果:(两个相同的结果)
0
8
2
7
6
5
3
1
1
4

由上述结果
可以看到,即使变量使用Volatile关键字修饰之后,多线程环境下仍然有出现问题的可能性。

如何解决原子性问题

原子变量:JDK1.5以后 java.util.concurrent.atomic 包下提供有原子变量。

  • 1、使用Volatile关键字保证变量内存可见性
  • 2、使用CAS(Compare-And-Swap)算法保证变量原子性。CAS算法是硬件对于并发操作共享数据的支持。
    CAS 包含三个操作数:

    • 内存值 V
    • 预估值 A
    • 更新值 B

    当且仅当 V == A 时,V = B。否则,则不做任何操作。

public class Test {
    public static void main(String[] args) {
        AtomicDemo ad = new AtomicDemo();
        for (int i = 0; i < 10; i++) {
            new Thread(ad).start();
        }
    }
}
class AtomicDemo implements Runnable{
    // 使用原子变量包装类操作
    private AtomicInteger serialNumber = new AtomicInteger();
    @Override
    public void run() {
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
        }
        System.out.println(getSerialNumber());
    }
    public int getSerialNumber(){
        // 自增
        return serialNumber.getAndIncrement();
    }
}
输出结果:(无论多少次操作结果都不会出现有相同值的情况)
6
9
0
2
3
5
4
7
1
8

模拟CAS算法:

public class TestCompareAndSwap {
    public static void main(String[] args) {
        final CompareAndSwap cas = new CompareAndSwap();
        for (int i = 0; i < 10; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    // 设置之前先获取一下内存值
                    int expectedValue = cas.get();
                    boolean b = cas.compareAndSet(expectedValue, (int)(Math.random() * 101));
                    System.out.println(b);
                }
            }).start();
        }   
    }
}

class CompareAndSwap{
    // 内存值
    private int value;
    /** * 获取内存值 * @return */
    public synchronized int get(){
        return value;
    }
    /** * 比较 * @param expectedValue 预估值 * @param newValue 新值 * @return */
    public synchronized int compareAndSwap(int expectedValue, int newValue){
        int oldValue = value;
        // 内存值和预估值比较
        if(oldValue == expectedValue){
            // 如果内存值和预估值相同,就把新值赋值给内存值
            this.value = newValue;
        }
        // 返回旧内存值
        return oldValue;
    }
    /** * 设置新值 * @param expectedValue 预估值 * @param newValue 新值 * @return 设置结果 */
    public synchronized boolean compareAndSet(int expectedValue, int newValue){
        return expectedValue == compareAndSwap(expectedValue, newValue);
    }
}

输出结果:(有成功有失败)
true
false
true
false
false
true
false
false
true
false
    原文作者:JUC
    原文地址: https://blog.csdn.net/qq_34560242/article/details/81120511
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞