• volatile关键字与内存可见性


    前言

    首先,我们使用多线程的目的在于提高程序的效率,但是如果使用不当,不仅不能提高效率,反而会使程序的性能更低,因为多线程涉及到线程之间的调度、CPU上下文的切换以及包括线程的创建、销毁和同步等等,开销比单线程大,因此需谨慎使用多线程。

    在jdk1.5以后,提供了一个强大的java.util.concurrent包,这个包中提供了大量的应用于线程的工具类。

    下面开始介绍volatile关键字和内存可见性,虽然volatile是在jdk1.5之前就有的,但还是想放在这里讲一下。

    举例说明

    首先,我们先看一段小程序。

     1 package com.ccfdod.juc;
     2 
     3 public class TestVolatile {
     4     public static void main(String[] args) {
     5         ThreadDemo td = new ThreadDemo();
     6         new Thread(td).start();
     7         
     8         while(true) {
     9             if (td.isFlag()) {
    10                 System.out.println("--------------");
    11                 break;
    12             }
    13         }
    14     }
    15 }
    16 
    17 class ThreadDemo implements Runnable {
    18     private boolean flag = false;
    19 
    20     @Override
    21     public void run() {
    22         try {
    23             Thread.sleep(200);
    24         } catch (InterruptedException e) {
    25             e.printStackTrace();
    26         }
    27         flag = true;
    28         System.out.println("flag = " + isFlag());
    29     }
    30 
    31     public boolean isFlag() {
    32         return flag;
    33     }
    34 
    35     public void setFlag(boolean flag) {
    36         this.flag = flag;
    37     }
    38 }

    程序运行结果:

    flag = true

    并且程序不会停止。

    按理来说,应该会在td线程修改flag值后,主线程会打印出“--------------”,但是为什么没有出现预期效果呢?下面来分析这段程序,涉及到内存可见性问题。

    内存可见性问题

    当程序运行时,JVM会为每一个执行任务的线程分配一个独立的缓存空间,用于提高效率。

    不难理解,程序开始执行时,由于线程td修改flag操作之前,sleep了200ms,td线程和main线程获取到的flag都为false,但为什么td线程将flag改为true后,main线程没有打印出“--------------”呢?原因在于:while(true)是执行效率很高,使得main线程没有时间再次从主存中获取flag的值,因此程序在td线程将flag修改为true后,没有停止运行的原因。其实在while(true)后面稍微延迟一点(比如说,打印一句话),都会使main线程将主存中的flag=true读取。

    产生这种情况的原因就在于,两个线程在操作共享数据时,对共享数据的操作是彼此不可见的。

    那么为了不让这种问题出现,怎么解决呢?

    一、使用synchronized同步锁

    while(true) {
        synchronized (td) {
            if (td.isFlag()) {
                System.out.println("--------------");
                break;        
            }
        }
    }

    使用synchronized同步锁能保证数据的及时更新。但是效率太低。

    二、使用volatile关键字

    当多个线程进行操作共享数据时,可以保证内存中的数据可见。底层原理:内存栅栏。使用volatile关键字修饰时,可理解为对数据的操作都在主存中进行。

    private volatile boolean flag = false;

    相较于synchronized是一种较为轻量级的同步策略。

    注意:

    • volatile不具备“互斥性”
    • volatile不能保证变量的“原子性”

    关于“原子性”的问题将在下一篇博客中讨论。

  • 相关阅读:
    C#中将全部代码一次性折叠
    C#中图片单击旋转事件
    块参照重命名
    补强圈设计
    c# winform 按名称取得控件
    获得某控件的父控件(容器)中的所有控件
    回车键当Tab键使用
    替换CAD中原有命令为开发人员自己开发的命令的方法
    窗体设置
    判断控件的tag是否为空的方法
  • 原文地址:https://www.cnblogs.com/ccfdod/p/6392343.html
Copyright © 2020-2023  润新知