zoukankan      html  css  js  c++  java
  • java多线程学习笔记(七)

    volatile关键字

    关键字volatile的主要作用是使变量在多个线程间可见。

    public class PrintString {
        private boolean isContinue = true;
        public boolean isContinue(){
            return isContinue;
        }
        public void setContinue(boolean isContinue){
            this.isContinue = isContinue;
        }
        public void PrintMethod(){
            try{
                while (isContinue ==true){
                    System.out.println("Name = "+ Thread.currentThread().getName());
                    Thread.sleep(1000);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
    public class Run {
        public static void main(String[] args) {
            PrintString printString = new PrintString();
            printString.PrintMethod();
            System.out.println("i'm stop_ing myself!");
            printString.setContinue(false);
        }
    }

    运行结果为:

    Name = main
    Name = main
    Name = main
    Name = main
    Name = main
    Name = main
    ...

     程序开始运行后,根本停不下来,主要原因是main线程的while循环停不下来,导致程序不能执行后续的代码,解决方法当然是使用多线程技术。

    解决同步的死循环:

    package page_3;
    
    public class PrintString_change implements Runnable{
        private boolean isContinue = true;
        public boolean isContinue(){
            return isContinue;
        }
        public void setContinue(boolean isContinue){
            this.isContinue = isContinue;
        }
        public void PrintMethod(){
            try{
                while (isContinue ==true){
                    System.out.println("Name = "+ Thread.currentThread().getName());
                    Thread.sleep(1000);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        @Override
        public void run(){
            PrintMethod();
        }
    }
    
    public class Run_change {
        public static void main(String[] args) {
            PrintString_change printString = new PrintString_change();
            new Thread(printString).start();
            System.out.println("i'm stop_ing myself!");
            printString.setContinue(false);
        }
    
    }

    运行的结果为:

    i'm stop_ing myself!
    Name = Thread-0

    关键字volatile的作用是强制从公共堆栈中取得变量的值,而不是从线程的私有数据栈中取得变量的值?

    下面这段内容摘自:风过无痕的博客

    先来看一段代码:

    package page_4;
    
    public class Task implements Runnable{
        boolean running = true;
        int i = 0;
        @Override
        public void run(){
            while (running){
                i++;
            }
        }
    
        public static void main(String[] args) throws InterruptedException {
            Task task = new Task();
            Thread th = new Thread(task);
            th.start();
            Thread.sleep(1000);
            task.running=false;
            Thread.sleep(1000);
            System.out.println(task.i);
            System.out.println("线程停止");
        }
    }

    这段代码想要他停止,结果无法停止成功,这个是因为JVM的特性,先来看看java的内存模型,如下图:

    java内存分为工作内存和主存
    工作内存:即java线程的本地内存,是单独给某个线程分配的,存储局部变量等,同时也会复制主存的共享变量作为本地
    的副本,目的是为了减少和主存通信的频率,提高效率。
    主存:存储类成员变量等

    可见性是指的是线程访问变量是否是最新值。
    局部变量不存在可见性问题,而共享内存就会有可见性问题,
    因为本地线程在创建的时候,会从主存中读取一个共享变量的副本,且修改也是修改副本,
    且并不是立即刷新到主存中去,那么其他线程并不会马上共享变量的修改。 
    因此,线程B修改共享变量后,线程A并不会马上知晓,就会出现上述死循环的问题。

    解决共享变量可见性问题,需要用volatile关键字修饰。
    如代码就不会出现死循环:

        volatile boolean running = true;
        int i = 0;
        @Override
        public void run(){
            while (running){
                i++;
            }
        }

    那么为什么能解决死循环的问题呢?
    可见性的特性总结为以下2点:
    1. 对volatile变量的写会立即刷新到主存
    2. 对volatile变量的读会读主存中的新值

    如此一来,就不会出现死循环了。

    为了能更深刻的理解volatile的语义,我们来看下面的时序图,回答这2个问题:

    问题1:t2时刻,如果线程A读取running变量,会读取到false,还是等待线程B执行完呢?
    答案是否定的,volatile并没有锁的特性。
    问题2:t4时刻,线程A是否一定能读取到线程B修改后的最新值
    答案是肯定的,线程A会从重新从主存中读取running的最新值。

    虽然running变量上没有volatile关键字修饰,但是读和写running都是同步方法

    同步块存在如下语义:
    1.进入同步块,访问共享变量会去读取主存
    2.退出同步块,本地内存对共享变量的修改会立即刷新到主存
    因此上述代码不会出现死循环。

    写在前面的结论:volatile 并不会有锁的特性

    1. 关键字volatile是线程同步的轻量级实现,所以volatile性能肯定比synchronized要好,并且volatile只能修饰于变量,而synchronized可以修饰方法以及代码块。
    2. 多线程访问volatile不会发生阻塞,而synchronized会出现阻塞
    3. volatile能保证数据的可见性,但不能保证原子性,synchronized可以保证原子性,也可以间接保证可见性,因为它会将私有内存和公共内存的数据做同步(如上面的那个代码案例所示)。
    4. 关键字volatile解决的是变量在多个线程之间的可见性,而synchronized关键字解决的是多线程之间访问资源的同步性。

    线程安全包括原子性和可见性两个方面,Java的同步机制都是围绕这两个方面来确保线程安全的。

    volatile变量的原子性
    我看了很多文章,有些文章甚至是出版的书籍都说volatile不是原子的,
    他们举的例子是i++操作,i++本身不是原子操作,是读并写,我这里要讲的原子性
    指的是写操作,原子性的特别总结为2点:
    1. 对一个volatile变量的写操作,只有所有步骤完成,才能被其它线程读取到。
    2. 多个线程对volatile变量的写操作本质上是有先后顺序的。也就是说并发写没有问题。
    这样说也许读者感觉不到和非volatile变量有什么区别,我来举个例子:
    //线程1初始化User
    User user;
    user = new User();
    //线程2读取user
    if(user!=null){
    user.getName();
    }
    在多线程并发环境下,线程2读取到的user可能未初始化完成
    具体来看User user = new User的语义:
    1:分配对象的内存空间
    2:初始化对线
    3:设置user指向刚分配的内存地址
    步骤2和步骤3可能会被重排序,流程变为
    1->3->2
    这些线程1在执行完第3步而还没来得及执行完第2步的时候,如果内存刷新到了主存,
    那么线程2将得到一个未初始化完成的对象。因此如果将user声明为volatile的,那么步骤2,3
    将不会被重排序。
    下面我们来看一个具体案例,一个基于双重检查的懒加载的单例模式实现:

    这个单例模式看起来很完美,如果instance为空,则加锁,只有一个线程进入同步块
    完成对象的初始化,然后instance不为空,那么后续的所有线程获取instance都不用加锁,
    从而提升了性能。
    但是我们刚才讲了对象赋值操作步骤可能会存在重排序,
    即当前线程的步骤4执行到一半,其它线程如果进来执行到步骤1,instance已经不为null,
    因此将会读取到一个没有初始化完成的对象。
    但如果将instance用volatile来修饰,就完全不一样了,对instance的写入操作将会变成一个原子
    操作,没有初始化完,就不会被刷新到主存中。
    修改后的单例模式代码如下:

    对volatile理解的误区

    很多人会认为对volatile变量的所有操作都是原子性的,比如自增i++
    这其实是不对的。
    看如下代码:

    如果i++的操作是线程安全的,那么预期结果应该是i=20000

    然而运行的结果是:11349
    说明i++存在并发问题
    i++语义是i=i+1
    分为2个步骤
    步骤1:读取i=0
    步骤2:计算i+1=1,并重新赋值给i
    那么可能存在2个线程同时读取到i=0,并计算出结果i=1然后赋值给i
    那么就得不到预期结果i=2。
    这个问题说明了2个问题:
    1.i++这种操作不是原子操作
    2.volatile 并不会有锁的特性

  • 相关阅读:
    表达式与运算符
    Python3 从零单排22_异常处理
    Python3 从零单排21_元类
    Python3 从零单排20_方法(绑定&内置)&反射
    Python3 从零单排19_组合&多态
    Python3 从零单排18_封装
    Python3 从零单排17_类的继承
    Python3 从零单排16_面向对象基础
    Python3 从零单排15_urllib和requests模块
    Python3 从零单排14_flask模块&mysql操作封装
  • 原文地址:https://www.cnblogs.com/samanian/p/11901786.html
Copyright © 2011-2022 走看看