上文说到了 synchronized,那么就不得不说下 volatile关键字了,它们两者经常协同处理多线程的安全问题。

volatile保证可见性

那么volatile的作用是什么呢?

在jvm运行时刻内存的分配中有一个内存区域是jvm虚拟机栈,每一个线程运行时都有一个线程栈,

线程栈保存了线程运行时候变量值信息。当线程访问某一个对象时候值的时候,首先通过对象的引用找到对应在堆内存的变量的值,

然后把堆内存变量的具体值load到线程本地内存中,建立一个变量副本,之后线程就不再和对象在堆内存变量值有任何关系,而是直接修改副本变量的值,

在修改完之后的某一个时刻(线程退出之前),自动把线程变量副本的值回写到对象在堆中变量。这样在堆中的对象的值就产生变化了。

read and load 从主存复制变量到当前工作内存
use and assign  执行代码,改变共享变量值 
store and write 用工作内存数据刷新主存相关内容
其中use and assign 可以多次出现

注意这些操作并不是原子性,也就是 在read load之后,如果主内存count变量发生修改之后,线程工作内存中的值由于已经加载,

不会产生对应的变化,所以计算出来的结果会和预期不一样,对于volatile修饰的变量,jvm虚拟机只是保证从主内存加载到线程工作内存的值是最新的。

如何解决缓存不一致的问题?

在早期的CPU当中,是通过在总线上加LOCK#锁的形式来解决缓存不一致的问题。

因为CPU和其他部件进行通信都是通过总线来进行的,如果对总线加LOCK#锁的话,也就是说阻塞了其他CPU对其他部件访问(如内存),

从而使得只能有一个CPU能使用这个变量的内存。比如一个线程在执行 i = i +1,如果在执行这段代码的过程中,在总线上发出了LCOK#锁的信号,

那么只有等待这段代码完全执行完毕之后,其他CPU才能从变量i所在的内存读取变量,然后进行相应的操作。

这样就解决了缓存不一致的问题。但是上面的方式会有一个问题,由于在锁住总线期间,其他CPU无法访问内存,导致效率低下。

所以就出现了缓存一致性协议。最出名的就是Intel 的MESI协议,MESI协议保证了每个缓存中使用的共享变量的副本是一致的。

它核心的思想是:当CPU写数据时,如果发现操作的变量是共享变量,即在其他CPU中也存在该变量的副本,会发出信号通知其他CPU将该变量的缓存行置为无效状态,

因此当其他CPU需要读取这个变量时,发现自己缓存中缓存该变量的缓存行是无效的,那么它就会从内存重新读取。

看一个例子:

public class MyThread15 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 == true){}
System.out.println("线程被停止了");
}
}

  

@Test
public void test15() throws InterruptedException { try{
MyThread15 a = new MyThread15();
a.start();
Thread.sleep(1000);
a.setRunning(false);
System.out.println("已赋值为false");
}
catch (InterruptedException e){
e.printStackTrace();
}
a.join();
}

  执行结果:

进入run了
已赋值为false

  可以看待,明明已经将 isRunning设置为false了,但是“线程被停止了”还是没有被执行。

当我们给isRunning加上关键字volatile后,执行结果:

进入run了
已赋值为false
线程被停止了

  可见volatile关键字可以保证共享变量在多线程之间的可见性。

volatile不保证原子性


下面看个demo,定义一个线程类,将count值++  10000次。

public class MyDomain16 {

    private volatile int count = 0;

    public MyDomain16() {
} public void count() {
for (int i=0; i<10000; i++) {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
count++;
}
System.out.println(Thread.currentThread().getName() + ": count=" + count);
}
} 
public class Mythread16 extends Thread {

    private MyDomain16 myDomain16;

    public Mythread16(MyDomain16 myDomain16) {
this.myDomain16 = myDomain16;
} public void run() {
myDomain16.count();
}
} @Test
public void test16() throws InterruptedException {
MyDomain16 myDomain16 = new MyDomain16(); Mythread16 a = new Mythread16(myDomain16);
Mythread16 b = new Mythread16(myDomain16);
a.start();
b.start();
a.join();
b.join();
}

  执行结果:

Thread-1: count=17913
Thread-0: count=17919

  

  可以看出两个线程总共应该增加2万次,但是count值并没有等于20000,说明自增操作不是原子性操作,而且volatile也无法保证对变量的任何操作都是原子性的。

当我们给count方法加上synchronized之后

public class MyDomain16 {

    private volatile int count = 0;

    public MyDomain16() {
} public synchronized void count() {
for (int i=0; i<10000; i++) {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
count++;
}
System.out.println(Thread.currentThread().getName() + ": count=" + count);
}
}

  执行结果:

Thread-0: count=10000
Thread-1: count=20000

  

总结:

关键字synchronized和volatile进行一下比较:

1)关键字volatile是线程同步的轻量级实现,所以volatile性能肯定比synchronized要好,并且volatile只能修饰于变量,而synchronized可以修饰方法,以及代码块。

随着JDK新版本的发布,synchronized关键字在执行效率上得到很大提升,在开发中使用synchronized关键字的比率还是比较大的。

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

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

4)关键字volatile解决的是变量在多个线程之间的可见性;而synchronized关键字解决的是多个线程之间访问资源的同步性。

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

参考文献

1:《Java并发编程的艺术》

2:《Java多线程编程核心技术》

java多线程4:volatile关键字的更多相关文章

  1. Java多线程编程——volatile关键字

    (本篇主要内容摘自<Java多线程编程核心技术>) volatile关键字的主要作用是保证线程之间变量的可见性. package com.func; public class RunThr ...

  2. 【java多线程】volatile 关键字

    在java线程并发处理中,有一个关键字volatile的使用目前存在很大的混淆,以为使用这个关键字,在进行多线程并发处理的时候就可以万事大吉. Java语言是支持多线程的,为了解决线程并发的问题,在语 ...

  3. Java多线程技术-Volatile关键字解析

    分析volatile关键字可以从这三个方面分析,什么是程序的原子性,什么是程序的可见性,什么是程序的有序性 什么是程序的原子性 以下语句那些是原子操作? public class ThreadCoun ...

  4. Java多线程:volatile 关键字

    一.内存模型的相关概念 大家都知道,计算机在执行程序时,每条指令都是在CPU中执行的,而执行指令过程中,势必涉及到数据的读取和写入.由于程序运行过程中的临时数据是存放在主存(物理内存)当中的,这时就存 ...

  5. java 轻量级同步volatile关键字简介与可见性有序性与synchronized区别 多线程中篇(十二)

    概念 JMM规范解决了线程安全的问题,主要三个方面:原子性.可见性.有序性,借助于synchronized关键字体现,可以有效地保障线程安全(前提是你正确运用) 之前说过,这三个特性并不一定需要全部同 ...

  6. Java并发编程 Volatile关键字解析

    volatile关键字的两层语义 一旦一个共享变量(类的成员变量.类的静态成员变量)被volatile修饰之后,那么就具备了两层语义: 1)保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了 ...

  7. Java 并发:volatile 关键字解析

    摘要: 在 Java 并发编程中,要想使并发程序能够正确地执行,必须要保证三条原则,即:原子性.可见性和有序性.只要有一条原则没有被保证,就有可能会导致程序运行不正确.volatile关键字 被用来保 ...

  8. Java中的volatile关键字的功能

    Java中的volatile关键字的功能 volatile是java中的一个类型修饰符.它是被设计用来修饰被不同线程访问和修改的变量.如果不加入volatile,基本上会导致这样的结果:要么无法编写多 ...

  9. 深入理解Java中的volatile关键字

    在再有人问你Java内存模型是什么,就把这篇文章发给他中我们曾经介绍过,Java语言为了解决并发编程中存在的原子性.可见性和有序性问题,提供了一系列和并发处理相关的关键字,比如synchronized ...

  10. java并发系列(六)-----Java并发:volatile关键字解析

    在 Java 并发编程中,要想使并发程序能够正确地执行,必须要保证三条原则,即:原子性.可见性和有序性.只要有一条原则没有被保证,就有可能会导致程序运行不正确.volatile关键字 被用来保证可见性 ...

随机推荐

  1. 大爽Python入门教程 3-4 实践例题

    大爽Python入门公开课教案 点击查看教程总目录 1. 求和 使用循环,计算列表所有项的和,并输出这个和. 列表示例 lst = [8, 5, 7, 12, 19, 21, 10, 3, 2, 11 ...

  2. [loj6498]农民

    对每一个节点用二元组$(p,v)$表示,其中$p$是其是父亲的左(0)还是右(1)儿子,$v$是其父亲的点权 $x$合法当且仅当:对于其到根路径上所有$(0,v)$都有$a_{x}<v$.$(1 ...

  3. Spring Cloud Gateway限流实战

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...

  4. 【JavaSE】集合

    Java集合 2019-07-05  12:39:09  by冲冲 1. 集合的由来 通常情况下,程序直到运行时,才知道需要创建多少个对象.但在开发阶段,我们根本不知道到底需要多少个数量的对象,甚至不 ...

  5. 你有没有觉得邮件发送人固定配置在yml文件中是不妥当的呢?SpringBoot 动态设置邮件发送人

    明月当天,不知道你有没有思念的人 前言 之前其实已经写过SpringBoot异步发送邮件,但是今天在一个小项目中要用到发送邮件时,我突然觉得邮件发送人只有一个,并且固定写在yml文件中,就是非常的不妥 ...

  6. 解决ip和域名都能够ping通但是启动nginx无法访问网页的问题

    解决思路 最近双11逛西部数码的官网看看有没有什么服务器优惠的时候,发现了可以申请一个一块钱用一整年的SSL证书,立马心动下单了,想想俺也可以用https装装X了哈哈 不过在部署完证书,并调整ngin ...

  7. P6973 [NEERC2016]List of Primes

    题目传送门. 题意简述:将质数集合的所有子集按照子集和为第一关键字,字典序为第二关键字从小到大排序,求最终形成的字符串的第 \(l\sim r\) 个字符. 在 cnblogs 内查看. 又是一道妙妙 ...

  8. [ARC101C] Ribbons on Tree

    神仙的容斥题与神仙的树形DP题. 首先搞一个指数级的做法:求总的.能够覆盖每一条边的方案数,通过容斥可以得到\(\text{ans}=\sum\limits_E{(-1)^{|E|}F(E)}\).其 ...

  9. MYSQL5.8---1

    主键不能为空,唯一键可以为空且可以多个唯一键 外键必须为另一个表中的主键 外键的用途是确保数据的完整性.它通常包括以下几种: 1 实体完整性,确保每个实体是唯一的(通过主键来实施) 2 域完整性,确保 ...

  10. Django向数据库批量插入数据

    # 如何向数据库一次性插入多条数据 # 方法一:效率极低,不推荐使用 for i in range(1000): models.Book.objects.create(title=f'第{i}本书') ...