关于volatile

时间:2024-01-27 19:32:21

1.volatile

volatile是java虚拟机提供的轻量级同步机制

2.特性

保证可见性,不保证原子性,禁止指令重排(有序性)

2.1 可见性

首先要知道JMM,就是java内存模型(可见性、原子性、有序性)
这是一个抽象概念;内存分为主内存和工作内存。主内存主要存放共享变量等等,用于数据共享的
而工作内存是线程操作资源的一个区域,每个线程都有自己的工作内存。
资源的操作流程主要分为以下三步:

  1.线程从主内存中copy取出需要操作的资源  
  2.线程操作资源  
  3.写回主内存(在CAS中,需要比较此时主内存的内容和之前拿到的内容是否相同)  

而在线程写回主内存后,需要通知其他线程这个共享变量已经被修改,如果修改了,其他线程就重
新从主内存中获取,这就叫做可见性

下面是一段代码演示:

/**
 * volatile的可见性
 */
public class TestVolatile {
    public static void main(String[] args) {
        SourceTest sourceTest = new SourceTest();

        new Thread(() -> {
            System.out.println(Thread.currentThread().getName());
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {e.printStackTrace();}
            sourceTest.addNum();
            System.out.println(Thread.currentThread().getName() + "修改a后: " + sourceTest.a);
        },"sub thread").start();

        while (sourceTest.a == 0){
            //进入循环
            System.out.println("主线程进入循环取得a的值:" + sourceTest.a);
        }

        System.out.println(Thread.currentThread().getName() + "得到a的值: " + sourceTest.a);
    }
}

class SourceTest{
    //int a = 0;//此时不加volatile主线程进入死循环,进程无法结束
    volatile int a = 0;

    void addNum(){
        a = 60;
    }
}

输出:
...
主线程进入循环取得a的值:0
主线程进入循环取得a的值:0
sub thread修改a后: 60
main得到a的值: 60

2.2 不保证原子性

原子性:不可分割,完整性,即线程在处理某个业务时,中间不允许被加塞

public class TestAtomic {
    public static void main(String[] args) throws InterruptedException {
        DataSource dataSource = new DataSource();

        for (int i = 1; i <= 20; i++) {
            new Thread(() -> {
                for (int j = 1; j <= 1000 ; j++) {
                    dataSource.getAndIncre();
                }
            },String.valueOf(i)).start();
        }

        //等待以上线程执行完成,main线程取值
        //TimeUnit.SECONDS.sleep(5);
        while (Thread.activeCount() > 2){
            Thread.yield();
        }

        System.out.println(Thread.currentThread().getName() + "\t 最终number: " + dataSource.number);
    }
}

class DataSource{
    volatile int number = 0;

    void getAndIncre(){
        number++;
    }
}

输出结果:
main 最终number: 19081

以此可看出volatile不保证原子性(i++线程不安全=>主要是i++先自增,再返回自增之前的值,导致某时通知判断错误)

解决无法保证原子性

1.加sync
2.AtomicInteger

AtomicInteger atomicInteger = new AtomicInteger();

    void getAndIncre(){
        //number++;
        atomicInteger.getAndIncrement();
    }

2.3 禁止指令重排(有序性)

计算机在执行程序时,为了提高性能,编译器和处理器会对指令进行重排:
源代码>(编译器优化的重排>指令并行的重排>内存系统的重排)>最终执行的指令
在单线程环境中,可以确保最终执行的结果与代码顺序一致
处理器在重排时必须考虑指令之间的数据依赖性
而多线程环境中线程交替执行,两个线程使用的变量就无法确定能保持一致性