多线程(六):volatile

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/vbirdbest/article/details/81707129

一:线程无法终止

public class MyThread extends Thread {
    private static boolean flag = true;

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
        while (flag) {

        }

        System.out.println("线程停止了");
    }

    public static void main(String[] args) throws InterruptedException {
        System.out.println(Thread.currentThread().getName());
        new MyThread().start();
        Thread.sleep(1000);
        flag = false;
    }
}

这里写图片描述

为什么线程无法终止?

每个线程都有自己的工作内存,线程对变量的所有操作都必须在自己的工作内存中进行,而不能直接对主内存进行操作,并且每个线程不能访问其他线程的工作内存。当线程访问全局变量的时候,会将全局变量拷贝一份放在自己的工作内存当中。主线程先运行,会将全局变量falg = true拷贝到自己的工作内存中,然后运行Thread-0线程,同样Thread-0也会将全局变量拷贝到自己的工作内存中,Thread-0 读取到自己的工作内存中的flag为true会一直循环,当CPU切换到主线程main时将flag=true的值修改为false,此时main线程的工作内存中的falg=false,但是main线程对全局变量的更改不会立即同步到全局变量的值,即虽然main的值为false,但全局变量的值扔为true,main修改了值很快就结束了,mian线程对flag的修改并没有影响到Thread-0线程工作内存中的值,Thread-0工作内存的值一直为true,一直循环,不会结束。

二:线程终止结束

public class MyThread extends Thread {
    private static volatile boolean flag = true;

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
        while (flag) {

        }

        System.out.println("线程停止了");
    }

    public static void main(String[] args) throws InterruptedException {
        System.out.println(Thread.currentThread().getName());
        new MyThread().start();
        Thread.sleep(1000);
        flag = false;
    }
}

这里写图片描述

  1. main线程将全局变量flag=true拷贝到自己的工作内存中(flag=true)
  2. main线程开始睡眠,此时CPU会切换到Thread-0线程执行,Thread-0线程同样也会将全局变量拷贝到自己的工作内存中(flag=true)
  3. Thread-0线程读取自己的工作内存获取flag的值为true,进入循环,一直循环
  4. 循环过程中CPU会切换到main线程执行,主线程修改了自己工作内存的值flag=false,因全局变量使用了volatile, 此时JVM会使得其它引用该全局变量的值失效,并立刻更改全局变量的值,此时全局变量的值为flag=false
  5. main线程修改完就结束了,此时又切换到Thread-0线程执行循环体,每循环一次就会读取自己的工作内存中的flag值,当读取时会发现自己工作内存中的flag值失效了,会重新从全局变量中获取新的值false缓存到自己的工作区,此时因flag=false了,while循环就结束了,整个线程就结束了

三:volatile

一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义:

  • 保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
  • 禁止进行指令重排序。

四:线程的三种特性

1.原子性

在Java中,对基本数据类型的变量的【读取】和【赋值】操作是原子性操作,即这些操作是不可被中断的,一次性必须执行完成的,要么执行,要么不执行。

// 原子操作
x = 10;

// 非原子操作: 1. 先读取x的值; 2.将读取的值赋值给y值; 总共分两步操作
y = x;

// 非原子操作: 1. 先读取x的值; 2.对x加1; 3. 将步骤2的结果赋值给x
x++;

// 和x++完全一样,分三步
x = x + 1;

Java内存模型只保证了基本读取和赋值是原子性操作,如果要实现更大范围操作的原子性,可以通过synchronized和Lock来实现。由于synchronized和Lock能够保证任一时刻只有一个线程执行该代码块,那么自然就不存在原子性问题了,从而保证了原子性。

2. 可见性

使用volatile关键字增加了实例变量在多个线程之间的可见性。但是volatile关键字最致命的缺点是不支持原子性。

synchronized和volatile比较:

  • 关键字volatile是线程同步的轻量级实现,所以volatile性能肯定比synchronized要好,并且volatile只能修饰于变量,而synchronized可以修饰方法,以及代码块。随着JDK新版本的发布,synchronized关键字在执行效率上得到很大提升,在开发中使用synchronized关键字的比率还是比较大的。

  • 多线程访问volatile不会发生阻塞,而synchronized会出现阻塞。

  • volatile能保证数据的可见性,但不能保证原子性;而synchronized可以保证原子性,也可以间接保证可见性,因为它将私有内存和公共内存中的数据做同步。

  • 再次重申一下,关键字volatile解决的是变量在多个线程之间的可见性;而synchronized关键字解决的是多个线程之间访问资源的同步性。

3. 有序性

相关文章(嘟嘟独立博客):http://tengj.top/2016/05/06/threadvolatile4/

猜你喜欢

转载自blog.csdn.net/vbirdbest/article/details/81707129