0. 前言

转载请注明出处:http://blog.csdn.net/seu_calvin/article/details/52370068

面试时很可能遇到这样一个问题:使用volatile修饰int型变量i,多个线程同时进行i++操作,这样可以实现线程安全吗?提到线程安全、线程同步,我们经常会想到两个关键字:volatile和synchronized,那么这两者有什么区别呢?

1. volatile修饰的变量具有可见性

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

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

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

例子请查看下面的3.1,帮助理解。

2. volatile禁止指令重排

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

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

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

例子请查看下面3.2,帮助理解。

3.  synchronized

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

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

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

例子请查看下面3.3,帮助理解。

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

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

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

3. volatile与synchronized的使用场景举例(结合第1部分进行理解学习)

3.1 volatile的使用举例

class MyThread extends Thread {
private volatile boolean isStop = false;
public void run() {
while (!isStop) {
System.out.println("do something");
}
}
public void setStop() {
isStop = true;
}
}

线程执行run()的时候我们需要在线程中不停的做一些事情,比如while循环,那么这时候该如何停止线程呢?如果线程做的事情不是耗时的,那么只需要使用一个标志即可。如果需要退出时,调用setStop()即可。这里就使用了关键字volatile,这个关键字的目的是如果修改了isStop的值,那么在while循环中可以立即读取到修改后的值。

如果线程做的事情是耗时的,那么可以使用interrupt方法终止线程 。如果在子线程“睡觉”时被interrupt,那么子线程可以catch到InterruptExpection异常,处理异常后继续往下执行。

3.2 volatile的使用举例

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

因为指令重排序,有可能语句2会在语句1之前执行,可能导致context还没被初始化,而线程2中就使用未初始化的context去进行操作,导致程序出错。

这里如果用volatile关键字对inited变量进行修饰,就不会出现这种问题了。

3.3 必须使用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并发——线程同步Volatile与Synchronized详解的更多相关文章

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

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

  2. java并发编程(七)synchronized详解

    Java语言的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码.     一.当两个并发线程访问同一个对象object中的这个synchronized( ...

  3. 线程同步Volatile与Synchronized(一)

    volatile 一.volatile修饰的变量具有内存可见性 volatile是变量修饰符,其修饰的变量具有内存可见性. 可见性也就是说一旦某个线程修改了该被volatile修饰的变量,它会保证修改 ...

  4. Java 并发 线程同步

    Java 并发 线程同步 @author ixenos 同步 1.异步线程本身包含了执行时需要的数据和方法,不需要外部提供的资源和方法,在执行时也不关心与其并发执行的其他线程的状态和行为 2.然而,大 ...

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

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

  6. 【java】java内存模型(2)--volatile内存语义详解

    多线程并发编程中synchronized和Volatile都扮演着重要的角色,Volatile是轻量级的synchronized,它在多处理器开发中保证了共享变量的“可见性”.可见性的意思是当一个线程 ...

  7. Jmeter系列(11)- 并发线程组Concurrency Thread Group详解

    如果你想从头学习Jmeter,可以看看这个系列的文章哦 https://www.cnblogs.com/poloyy/category/1746599.html Concurrency Thread ...

  8. Java并发笔记-未完待续待详解

    为什么需要并行? – 业务要求 – 性能 并行计算还出于业务模型的需要 – 并不是为了提高系统性能,而是确实在业务上需要多个执行单元. – 比如HTTP服务器,为每一个Socket连接新建一个处理线程 ...

  9. Java 并发编程(一) → LockSupport 详解

    开心一刻 今天突然收到花呗推送的消息,说下个月 9 号需要还款多少钱 我就纳了闷了,我很长时间没用花呗了,怎么会欠花呗钱? 后面我一想,儿子这几天玩了我手机,是不是他偷摸用了我的花呗 于是我找到儿子问 ...

随机推荐

  1. 20135202闫佳歆--week3 课本1-2章学习笔记

    第一章 Linux内核简介 一.Unix Unix是一个强大.健壮和稳定的操作系统. 简洁 绝大部分东西都被当做文件对待.这种抽象使对数据和对设备的操作都是通过一套相同的系统调用借口来进行的:open ...

  2. 作业六:分析Linux内核创建一个新进程的过程

    分析Linux内核创建一个新进程的过程 进程描述符PCB----task_struct数据结构 操作系统:1.进程管理 2.内存管理 3 文件系统 一.新进程如何创建和修改task_struct数据结 ...

  3. Daily Scrum - 11/16

    时间:午饭 今天小组例会主要是汇报了各自的进度.任烁那边主要为工程添加了单词的类(包含各个参数等成员变量),方便以后实现算法:拜重阳实现了一个简易的“点进-点出”UI,可谓迈出了艰难的第一步:章玮和罗 ...

  4. Visual Studio 2015的安装和简单的单元测试

    何为单元测试 绝大多数的软件都是由多人合作完成的,大家的工作相互有依赖关系.软件的很多错误都来源于程序员对模块功能的误解.疏忽或不了解其他模块的变化.如何能让自己负责的模块功能的定义尽量的明确,模块内 ...

  5. Alpha冲刺第10天

    Alpha第10天 1.团队成员 郑西坤 031602542 (队长) 陈俊杰 031602504 陈顺兴 031602505 张胜男 031602540 廖钰萍 031602323 雷光游 0316 ...

  6. 选择排序的C、C++实现

    一.选择排序 选择排序(Selection sort)是一种简单直观的排序算法.它的工作原理是每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完 ...

  7. CentOS下搭建Hive

    目录 下载解压hive mysql驱动 配置文件 hive-env.sh hive-site.xml 首次启动hive 使用schemaTool初始化mysql数据库 错误总结 警告汇总 参考:htt ...

  8. MyBatis分步查询的延迟加载

    延迟加载的概念只存在于分步查询时: 延迟加载的本质是为第一步查询返回的Java Bean创建了一个代理对象: 延迟加载的全局设置有两个: lazyLoadingEnabled,作用为设置select语 ...

  9. maven使用阿里镜像配置文件

    方法一: apache-maven-3.5.2\confsetting.xml,添加如下镜像配置: <mirrors> <mirror> <id>alimaven& ...

  10. PowerExchange实时抽取架构介绍

    工作原理 准实时抽取架构图: 以上共有核心业务系统数据库服务器.ETL服务器.BI数据库服务器[目标数据库服务器],三台服务器和ETL客户端(PowerCenter客户端).其中核心业务系统上有核心系 ...