惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

酷 壳 – CoolShell
酷 壳 – CoolShell
H
Hacker News: Front Page
P
Palo Alto Networks Blog
T
ThreatConnect
Apple Machine Learning Research
Apple Machine Learning Research
博客园_首页
T
True Tiger Recordings
P
Privacy & Cybersecurity Law Blog
B
Blog
IT之家
IT之家
Last Week in AI
Last Week in AI
F
Full Disclosure
Hacker News: Ask HN
Hacker News: Ask HN
C
Comments on: Blog
Microsoft Azure Blog
Microsoft Azure Blog
C
Cybersecurity and Infrastructure Security Agency CISA
Microsoft Security Blog
Microsoft Security Blog
博客园 - 【当耐特】
N
News and Events Feed by Topic
NISL@THU
NISL@THU
腾讯CDC
雷峰网
雷峰网
Security Latest
Security Latest
李成银的技术随笔
M
Microsoft Research Blog - Microsoft Research
L
LangChain Blog
L
Lohrmann on Cybersecurity
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
C
Check Point Blog
Y
Y Combinator Blog
Recent Announcements
Recent Announcements
博客园 - Franky
N
News | PayPal Newsroom
V
V2EX
A
About on SuperTechFans
The Register - Security
The Register - Security
月光博客
月光博客
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Google Online Security Blog
Google Online Security Blog
MyScale Blog
MyScale Blog
Cisco Talos Blog
Cisco Talos Blog
Vercel News
Vercel News
WordPress大学
WordPress大学
C
Cyber Attacks, Cyber Crime and Cyber Security
The Hacker News
The Hacker News
IntelliJ IDEA : IntelliJ IDEA – the Leading IDE for Professional Development in Java and Kotlin | The JetBrains Blog
IntelliJ IDEA : IntelliJ IDEA – the Leading IDE for Professional Development in Java and Kotlin | The JetBrains Blog
爱范儿
爱范儿
A
Arctic Wolf
L
LINUX DO - 最新话题
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More

博客园 - MOBIN

播面--八股文利器:播客形式讲解八股文,文本题库永久免费! Docker部署Apollo配置中心 Actor模型原理 java并发编程--Runnable Callable及Future Spark Accumulators Spark Yarn-cluster与Yarn-client Hive集成HBase详解 Hive MapJoin Hive2.0函数大全(中文版) 深度剖析JDK动态代理机制 HBase二级索引的设计 通过BulkLoad的方式快速导入海量数据 Phoenix二级索引(Secondary Indexing)的使用 java并发编程--Executor框架 Spark常用函数讲解之Action操作 Spark常用函数讲解之键值RDD转换 图解堆排序 Spark函数详解系列之RDD基本转换 深入理解Scala的隐式转换
Java并发编程--Volatile详解
MOBIN · 2016-04-19 · via 博客园 - MOBIN

摘要

     Volatile是Java提供的一种弱同步机制,当一个变量被声明成volatile类型后编译器不会将该变量的操作与其他内存操作进行重排序。在某些场景下使用volatile代替锁可以减少代码量和使代码更易阅读。

Volatile特性

  1.可见性:当一条线程对volatile变量进行了修改操作时,其他线程能立即知道修改的值,即当读取一个volatile变量时总是返回最近一次写入的值

  2.原子性:对于单个voatile变量其具有原子性(能保证long double类型的变量具有原子性),但对于i ++ 这类复合操作其不具有原子性(见下面分析)

使用volatile变量的前提

  1.对变量的写入操作不依赖变量的当前值,或者能够确保只有单一的线程修改变量的值

  2.该变量不会与其他状态变量一起纳入不变性条件中

  3.在访问变量时不需要加锁

volatile可见性

volatile的可见性正是基于happend -before(先行发生)关系实现的。

happend-before:java内存模型有八条可以保证happend-before的规则(详见《深入理解Java虚拟机》P376),如果两个操作之间的关系无法从这八条规则中推导出来的话,它们就没有顺序保障,虚拟机就可以对它们随意地进行重排序.

其中就包含”volatile变量规则“:对一个volatile变量的写操作先行发生于后面对这个变量的读操作,此规则保证虚拟机不会对volatile读/写操作进行重排序。

通过一个例子来了解vloative的可见性

例1:

public class VolatileTest extends  Thread{
    private boolean isRunning = true;
    public boolean isRunning(){
        return isRunning;
    }
    public void setRunning(boolean isRunning){
        this.isRunning= isRunning;
    }
    public void run(){
        System.out.println("进入了run...............");
        while (isRunning){}
        System.out.println("isRunning的值被修改为为false,线程将被停止了");
    }
    public static void main(String[] args) throws InterruptedException {
        VolatileTest volatileThread = new VolatileTest();
        volatileThread.start();
        Thread.sleep(1000);
        volatileThread.setRunning(false);   //停止线程
    }
}

输出:

进入了run...............

发现并没有输出”isRunning的值被修改为为false,线程将被停止了”这一句,说明通过setRunning来修改isRunning的值对于该程序是不可见的,也就是说程序不知道自己的值被修改了,为什么?

     原因:Java内存模型(JMM)规定了所有的变量都存储在主内存中,主内存中的变量为共享变量,而每条线程都有自己的工作内存,线程的工作内存保存了从主内存拷贝的变量,所有对变量的操作都在自己的工作内存中进行,完成后再刷新到主内存中,回到例1,第18行号代码主线程(线程main)虽然对isRunning的变量进行了修改且有刷新回主内存中(《深入理解java虚拟机》中关于主内存与工作内存的交互协议提到变量在工作内存中改变后必须将该变化同步回主内存),但volatileThread线程读的仍是自己工作内存的旧值导致出现多线程的可见性问题,解决办法就是给isRunning变量加上volatile关键字。

      当变量被声明成volatile类型后,线程对该变量进行修改后会立即刷新回主内存,而其他线程读取该变量时会先将自己工作内存中的变量置为无效,再从主内存重新读取变量到自己的工作内存,这样就避免发生线程可见性问题。

volatile内存语义总结如下

1.当线程对volatile变量进行写操作时,会将修改后的值刷新回主内存

2.当线程对volatile变量进行读操作时,会先将自己工作内存中的变量置为无效,之后再通过主内存拷贝新值到工作内存中使用。

volatile原子性

   volatile并不完全具有原子性,对于复合操作其仍存在线程不安全的问题,如

例2

public class VolatileTest1{
    private volatile  int value;  //将value变量声明成volatile类型
    public  void increment(){
         value ++;   
         System.out.println(value);   
    }
    public static void main(String[] args) {
        final VolatileTest1 volatileTest1 = new VolatileTest1();
        for(int i = 0; i < 10; i ++){
            new Thread(new Runnable() {
                public void run() {
                     volatileTest1.increment();
                }
            }).start();
        }
    }
}

输出:

1
6
5
4
2
3
8
7
9
10

线程每次对value进行自增操作,显然输出结果不是我们想要的那种,这里就出现了线程安全问题,为什么?

  像value ++这样的操作并不具有原子性,其实际的过程如下:

当线程1在步骤2对value进行计算时,刚好其他线程也对value进行了修改,这时线程1返回的值就不是我们期望的值了,于是出现线程安全问题,所以volatile不能保证复合操作具有原子性;解决办法就是给increment方法加锁(lock/synchronized)或将变量声明为原子类类型。

Synchronized与volatile区别 

1.volatile只能修饰变量,而synchronized可以修改变量,方法以及代码块

2.volatile在多线程中不会存在阻塞问题,synchronized会存在阻塞问题

3.volatile能保证数据的可见性,但不能完全保证数据的原子性,synchronized即保证了数据的可见性也保证了原子性

4.volatile解决的是变量在多个线程之间的可见性,而sychroized解决的是多个线程之间访问资源的同步性