线程同步Volatile与Synchronized(一)
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(一)的更多相关文章
- 剑指Offer——线程同步volatile与synchronized详解
(转)Java面试--线程同步volatile与synchronized详解 0. 前言 面试时很可能遇到这样一个问题:使用volatile修饰int型变量i,多个线程同时进行i++操作,这样可以实现 ...
- Java并发——线程同步Volatile与Synchronized详解
0. 前言 转载请注明出处:http://blog.csdn.net/seu_calvin/article/details/52370068 面试时很可能遇到这样一个问题:使用volatile修饰in ...
- 线程同步(使用了synchronized)和线程通讯(使用了wait,notify)
线程同步 什么是线程同步? 当使用多个线程来访问同一个数据时,非常容易出现线程安全问题(比如多个线程都在操作同一数据导致数据不一致),所以我们用同步机制来解决这些问题. 实现同步机制有两个方法:1.同 ...
- 线程、volatile与synchronized、Lock
目录 线程 1.概念: 2.线程生命周期: 3.线程调度 4.线程实现 4.1.实现方式 4.2.之间的区别: 5.线程安全 5.1.volatile与synchronized 5.1.synchro ...
- Java多线程:线程同步与关键字synchronized
一.同步的特性1. 不必同步类中所有的方法, 类可以同时拥有同步和非同步方法.2. 如果线程拥有同步和非同步方法, 则非同步方法可以被多个线程自由访问而不受锁的限制. 参见实验1:http://blo ...
- 多线程学习-基础( 九)线程同步Synchronized关键字
一.线程同步1.synchronized关键字的作用域有二种:(1)某个对象实例内:synchronized aMethod(){}可以防止多个线程同时访问这个对象的synchronized方法(如果 ...
- Java并发编程(一):线程基础知识以及synchronized关键字
1.线程与多线程的概念:在一个程序中,能够独立运行的程序片段叫作“线程”(Thread).多线程(multithreading)是指从软件或者硬件上实现多个线程并发执行的技术. 2.多线程的意义:多线 ...
- Java 多线程基础(五)线程同步
Java 多线程基础(五)线程同步 当我们使用多个线程访问同一资源的时候,且多个线程中对资源有写的操作,就容易出现线程安全问题. 要解决上述多线程并发访问一个资源的安全性问题,Java中提供了同步机制 ...
- Java线程(二):线程同步synchronized和volatile
上篇通过一个简单的例子说明了线程安全与不安全,在例子中不安全的情况下输出的结果恰好是逐个递增的(其实是巧合,多运行几次,会产生不同的输出结果),为什么会产生这样的结果呢,因为建立的Count对象是线程 ...
随机推荐
- mysql 查看当前使用的配置文件my.cnf的方法
my.cnf是mysql启动时加载的配置文件,一般会放在mysql的安装目录中,用户也可以放在其他目录加载. 安装mysql后,系统中会有多个my.cnf文件,有些是用于测试的. 使用locate m ...
- 2018-2019-2 20175310实验一《Java开发环境的熟悉》实验报告
2018-2019-2 20175310实验一<Java开发环境的熟悉>实验报告 一.实验步骤及内容 (一).Java开发环境的熟悉-1 1.建立20175310exp1的目录 2.在20 ...
- iis 网页HTTP 错误 404.3 - Not Found解决方案
一. 1.依次打开控制面板→程序和功能→打开或关闭Windwos功能 2.在打开的Windows功能窗口中依次展开Internet信息服务→万维网服务→应用程序开发功能,将需要的功能选项前面的勾上,确 ...
- C语言中__attribute__ ((at())绝对定位的应用
C语言中的关键字__attribute__ ,当时大一学C语言中没有接触过,后来工作中搞RFID的蓝牙标签卡开发,用的是MSP430G2332,直接用的是绝对定位: 1 const uint8_t f ...
- IntelliJ IDEA 高效率配置
之前学习和开发的时候一直用Eclipse,现在转战IDEA,记录一下IDEA的个性化设置,有助于提高效率.(参考:http://www.cnblogs.com/huaxingtianxia/p/586 ...
- CF741 D Arpa’s letter-marked tree and Mehrdad’s Dokhtar-kosh paths
题目意思很清楚了吧,那么我们从重排回文串的性质入手. 很容易得出,只要所有字符出现的次数都为偶数,或者有且只有一个字符出现为奇数就满足要求了. 然后想到什么,Hash?大可不必,可以发现字符\(\in ...
- zookeeper-如何修改源码-《每日五分钟搞定大数据》
本篇文章仅仅是起一个抛砖迎玉的作用,举一个如何修改源码的例子.文章的灵感来自 ZOOKEEPER-2784. 提一个问题先 之前的文章讲过zxid的设计,我们先复习下: zxid有64位,分成两部分: ...
- Linq to XML操作XML文件
LINQ的类型 在MSDN官方文件中,LINQ分为几种类型: . LINQ to Objects(或称LINQ to Collection),这是LINQ的基本功能,针对集合对象进行查询处理,包括基本 ...
- ASP.NET MVC5+EF6+EasyUI 后台管理系统-WebApi的用法与调试
1:ASP.NET MVC5+EF6+EasyUI 后台管理系统(1)-WebApi与Unity注入 使用Unity是为了使用我们后台的BLL和DAL层 2:ASP.NET MVC5+EF6+Easy ...
- 了解可执行的NPM包
NPM是Node.js的包管理工具,随着Node.js的出现,以及前端开发开始使用gulp.webpack.rollup以及其他各种优秀的编译打包工具(大多数采用Node.js来实现),大家都开始接触 ...