volatile

一、volatile修饰的变量具有内存可见性

volatile是变量修饰符,其修饰的变量具有内存可见性。

可见性也就是说一旦某个线程修改了该被volatile修饰的变量,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,可以立即获取修改之后的值。

在Java中为了加快程序的运行效率,对一些变量的操作通常是在该线程的寄存器或是CPU缓存上进行的,之后才会同步到主存中,而加了volatile修饰符的变量则是直接读写主存

实例讲解:

class FlagThread extends Thread{
private boolean flag = false;
@Override
public void run() {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
flag = true; System.out.println("flag : " + flag);
}
public boolean isFlag() {
return flag;
}
}
public class TestVolatile {
public static void main(String[] args) {
FlagThread flagThread = new FlagThread(); flagThread.start();
while (true){
if (flagThread.isFlag()){
System.out.println("------------------------");
break;
}
}
}
}

程序输出:(main线程结束不了)

问题分析:

在操作共享数据的时候,系统会将共享数据放置在主存中。

流程分析:

步骤一:

线程1:读取到的主存数据,flag = true;

main线程:读取到的主存数据,flag = true;

步骤二:

线程1:修改自身线程缓存空间的值,令flag = false;并将其更新到主存中,此时主存中的flag = false.

但是,main线程中的值,仍然是,flag = false,并一直循环下去。

因此,main线程始终在循环中,无法检测到falg已经变化的值。

二、volatile禁止指令重排

volatile可以禁止进行指令重排。

指令重排是指处理器为了提高程序运行效率,可能会对输入代码进行优化,它不保证各个语句的执行顺序同代码中的顺序一致,但是它会保证程序最终执行结果和代码顺序执行的结果是一致的。指令重排序不会影响单个线程的执行,但是会影响到线程并发执行的正确性。

程序执行到volatile修饰变量的读操作或者写操作时,在其前面的操作肯定已经完成,且结果已经对后面的操作可见,在其后面的操作肯定还没有进行。

volatile的使用举例

//线程1:
context = loadContext(); //语句1 context初始化操作
inited = true; //语句2 //线程2:
while(!inited ){
sleep()
}
doSomethingwithconfig(context);

因为指令重排序,有可能语句2会在语句1之前执行,可能导致context还没被初始化,而线程2中,

// 此处判断为false,则不会进入循环
while(!inited ){
sleep()
}

因而,线程2使用未初始化的context去进行操作,导致程序出错。

这里如果用volatile关键字对inited变量进行修饰,就不会出现这种问题了,这是因为volatile禁止指令重排:程序执行到volatile修饰变量的读操作或者写操作时,在其前面的操作肯定已经完成,且结果已经对后面的操作可见,在其后面的操作肯定还没有进行。

synchronized

三、synchronized

synchronized可作用于一段代码或方法,既可以保证可见性,又能够保证原子性。

可见性体现在:通过synchronized或者Lock能保证同一时刻只有一个线程获取锁然后执行同步代码,并且在释放锁之前会将对变量的修改刷新到主存中

原子性表现在:要么不执行,要么执行到底。

实例讲解:必须使用synchronized而不能使用volatile的场景

public class Test {
public volatile int inc = 0;
public void increase() {
inc++;
}
public static void main(String[] args) {
final Test test = new Test();
for(int i=0;i<10;i++){
new Thread(){
public void run() {
for(int j=0;j<1000;j++)
test.increase();
};
}.start();
}
while(Thread.activeCount()>1) //保证前面的线程都执行完
Thread.yield();
System.out.println(test.inc);
}
}

程序分析:

结果:例子中用new了10个线程,分别去调用1000次increase()方法,每次运行结果都不一致,都是一个小于10000的数字。

问题分析:自增操作不是原子操作,volatile 是不能保证原子性的。回到文章一开始的例子,使用volatile修饰int型变量i,多个线程同时进行i++操作。比如有两个线程A和B对volatile修饰的i进行i++操作,i的初始值是0,A线程执行i++时刚读取了i的值0,就切换到B线程了,B线程(从内存中)读取i的值也为0,然后就切换到A线程继续执行i++操作,完成后i就为1了,接着切换到B线程,因为之前已经读取过了,所以继续执行i++操作,最后的结果i就为1了。同理可以解释为什么每次运行结果都是小于10000的数字。

但是使用synchronized对部分代码进行如下修改,就能保证同一时刻只有一个线程获取锁然后执行同步代码。运行结果必然是10000。

public  int inc = 0;
public synchronized void increase() {
inc++;
}

内存可见性

在Java中,我们都知道关键字synchronized可以用于实现线程间的互斥,但我们却常常忘记了它还有另外一个作用,那就是确保变量在内存的可见性 - 即当读写两个线程同时访问同一个变量时,synchronized用于确保写线程更新变量后,读线程再访问该 变量时可以读取到该变量最新的值。

即当ThreadA释放锁M时,它所写过的变量(比如,x和y,存在它工作内存中的)都会同步到主存中,而当ThreadB在申请同一个锁M时,ThreadB的工作内存会被设置为无效,然后ThreadB会重新从主存中加载它要访问的变量到它的工作内存中(这时x=1,y=1,是ThreadA中修改过的最新的值)。通过这样的方式来实现ThreadA到ThreadB的线程间的通信。

//线程A,B共同访问的代码
Object lock = new Object();
int a=0;
int b=0;
int c=0; //线程A,调用如下代码
synchronized(lock){
a=1; //1
b=2; //2
} //3
c=3; //4 //线程B,调用如下代码
synchronized(lock){ //5
System.out.println(a); //6
System.out.println(b); //7
System.out.println(c); //8
}

我们假设线程A先运行,分别给a,b,c三个变量进行赋值(注:变量a,b的赋值是在同步语句块中进行的),然后线程B再运行,分别读取出这三个变量的值并打印出来。那么线程B打印出来的变量a,b,c的值分别是多少?

输出结果:

线程B里,打印的a,b肯定是1和2. 但是,访问的到c变量有可能还是0,而不是3.

四、总结

(1)从而我们可以看出volatile虽然具有可见性但是并不能保证原子性。

(2)性能方面,synchronized关键字是防止多个线程同时执行一段代码,就会影响程序执行效率,而volatile关键字在某些情况下性能要优于synchronized。

但是要注意volatile关键字是无法替代synchronized关键字的,因为volatile关键字无法保证操作的原子性。

参考资料:

Java并发——线程同步Volatile与Synchronized详解

线程同步Volatile与Synchronized(一)的更多相关文章

  1. 剑指Offer——线程同步volatile与synchronized详解

    (转)Java面试--线程同步volatile与synchronized详解 0. 前言 面试时很可能遇到这样一个问题:使用volatile修饰int型变量i,多个线程同时进行i++操作,这样可以实现 ...

  2. Java并发——线程同步Volatile与Synchronized详解

    0. 前言 转载请注明出处:http://blog.csdn.net/seu_calvin/article/details/52370068 面试时很可能遇到这样一个问题:使用volatile修饰in ...

  3. 线程同步(使用了synchronized)和线程通讯(使用了wait,notify)

    线程同步 什么是线程同步? 当使用多个线程来访问同一个数据时,非常容易出现线程安全问题(比如多个线程都在操作同一数据导致数据不一致),所以我们用同步机制来解决这些问题. 实现同步机制有两个方法:1.同 ...

  4. 线程、volatile与synchronized、Lock

    目录 线程 1.概念: 2.线程生命周期: 3.线程调度 4.线程实现 4.1.实现方式 4.2.之间的区别: 5.线程安全 5.1.volatile与synchronized 5.1.synchro ...

  5. Java多线程:线程同步与关键字synchronized

    一.同步的特性1. 不必同步类中所有的方法, 类可以同时拥有同步和非同步方法.2. 如果线程拥有同步和非同步方法, 则非同步方法可以被多个线程自由访问而不受锁的限制. 参见实验1:http://blo ...

  6. 多线程学习-基础( 九)线程同步Synchronized关键字

    一.线程同步1.synchronized关键字的作用域有二种:(1)某个对象实例内:synchronized aMethod(){}可以防止多个线程同时访问这个对象的synchronized方法(如果 ...

  7. Java并发编程(一):线程基础知识以及synchronized关键字

    1.线程与多线程的概念:在一个程序中,能够独立运行的程序片段叫作“线程”(Thread).多线程(multithreading)是指从软件或者硬件上实现多个线程并发执行的技术. 2.多线程的意义:多线 ...

  8. Java 多线程基础(五)线程同步

    Java 多线程基础(五)线程同步 当我们使用多个线程访问同一资源的时候,且多个线程中对资源有写的操作,就容易出现线程安全问题. 要解决上述多线程并发访问一个资源的安全性问题,Java中提供了同步机制 ...

  9. Java线程(二):线程同步synchronized和volatile

    上篇通过一个简单的例子说明了线程安全与不安全,在例子中不安全的情况下输出的结果恰好是逐个递增的(其实是巧合,多运行几次,会产生不同的输出结果),为什么会产生这样的结果呢,因为建立的Count对象是线程 ...

随机推荐

  1. 用return关键字实现1——100累加求和,返回总和并接收输出

    package com.Summer_0419.cn; /** * @author Summer * 用return关键字实现1——100累加求和,返回总和并接收输出 */ public class ...

  2. 环境部署(一):Linux下安装JDK

    自动化测试的主要目的是为了执行回归测试.当然,为了模拟真实的用户操作,一般都是在UAT或者生产环境进行回归测试. 为了尽量避免内网和外网解析对测试结果的影响,将自动化测试服务部署在外网的服务器是比较好 ...

  3. Django:安装和启动

    最近在学习利用python语言进行web站点开发,使用的框架是Django.这篇博客主要介绍Django的安装和简单使用. 一.Django介绍 Django是一个开源的Web应用框架,由Python ...

  4. /dev/mem可没那么简单

    这几天研究了下/dev/mem.发现功能非常奇妙,通过mmap能够将物理地址映射到用户空间的虚拟地址上.在用户空间完毕对设备寄存器的操作,于是上网搜了一些/dev/mem的资料. 网上的说法也非常统一 ...

  5. 负载(Load)分析及问题排查

    平常的工作中,在衡量服务器的性能时,经常会涉及到几个指标,load.cpu.mem.qps.rt等.每个指标都有其独特的意义,很多时候在线上出现问题时,往往会伴随着某些指标的异常.大部分情况下,在问题 ...

  6. Docker存储卷(V18.X)

    简介 介绍 Docker的存储卷称之为volume,本质上容器上的一个或者多个目录,而这些目录绕过了联合文件系统,与宿主机中的目录或者其他容器目录进行了绑定关系,这种绑定关系可以看作Linux的mou ...

  7. mongodb .explain('executionStats') 查询性能分析(转)

    mongodb性能分析方法:explain() 为了演示的效果,我们先来创建一个有200万个文档的记录.(我自己的电脑耗了15分钟左右插入完成.如果你想插更多的文档也没问题,只要有耐心等就可以了.) ...

  8. Windows OpenSSH 基本用法

    笔者在前文<Windows 支持 OpenSSH 了!>中介绍了 Windows 对 OpenSSH 支持的基本内容,本文在前文的基础上介绍一些 OpenSSH Server 的配置和常见 ...

  9. 断路器(Curcuit Breaker)模式

    在分布式环境下,特别是微服务结构的分布式系统中, 一个软件系统调用另外一个远程系统是非常普遍的.这种远程调用的被调用方可能是另外一个进程,或者是跨网路的另外一台主机, 这种远程的调用和进程的内部调用最 ...

  10. 腾讯 Omi 5.0 发布 - Web 前端 MVVM 王者归来,mappingjs 强力加持

    写在前面 腾讯 Omi 框架正式发布 5.0,依然专注于 View,但是对 MVVM 架构更加友好的集成,彻底分离视图与业务逻辑的架构. 你可以通过 omi-cli 快速体验 MVVM: $ npm ...