java并发编程(2) --Synchronized与Volatile区别
Synchronized
在多线程并发中synchronized一直是元老级别的角色。利用synchronized来实现同步具体有一下三种表现形式:
- 对于普通的同步方法,锁是当前实例对象。
- 对于静态同步方法,锁是当前类的class对象。
- 对于同步方法块,锁是synchronized括号里配置的对象。
当一个代码,方法或者类被synchronized修饰以后。当一个线程试图访问同步代码块的时候,它首先必须得到锁,退出或抛出异常的时候必须释放锁。那么这样做有什么好处呢?
它主要确保多个线程在同一时刻,只能有一个线程处于方法或者同步块中,它保证了线程对变量的可见性和排他性。
如何实现排他性
如下图所示,一个普通的方法会有一个左右摆动的开关,可以连接到任意一个线程,如果该方法代码不是原子性的,可能会出现一个线程并没有将方法代码执行完毕就链接到另一个线程中去。而被synchronized修饰的方法,链接到一个线程后,除非这个线程将方法执行完毕或者抛出异常,开关才会链接至别的线程。就这样将一个并行的操作变了穿行操作。(同一时间保证只有一个线程在执行方法代码)
int i = 1;
public synchronized void increment(){
i++;
}
在前面并发基础及锁的原理中我们介绍过i++并不是原子操作,所有当多个线程同时操作i++的时候可能会出现多线程并发问题。而上诉代码块中i++是在synchronized修饰的方法中。其中一个线程进入该方法首先获得当前实例对象的锁,当另一个线程试图执行该方法的时候,由于前一个线程并没有执行完毕释放掉锁,所以该线程挂起等待锁的释放。
通过加锁的方式我们实现了将i++非原子操作的方法变成了原子操作的方法。从而实现了排他性。
如何实现可见性
因为在java内存模型中规定:在执行被synchronized修饰的代码时,线程首先获取锁→清空工作内存→在主内存中拷贝最新变量的副本到工作内存→执行完代码→将工作内存中更改后的共享变量的值刷新到主内存中→释放互斥锁。
这里有一个细节需要注意: 当一个线程A将最新的共享变量刷新到主内存的时候,会导致缓存在其他线程B的工作内存的这个共享变量失效。
当线程B下一次去访问这个变量的时候,会发现,工作缓存的这个变量已经失效。会强制从主内存中重新读取这个共享变量
Volatile
当声明共享变量为volatile后,对这个变量的读/写将会很特别。volatile可以说是java虚拟机提供的最轻量级的同步机制。他只能能只能保证变量的可见性与读/写的原子性。要理解volatile确实是不容易的,接下来我们进入深入的分析!
volatile的特性
下面有两个示例代码:
public class VolatileTest1 {
volatile long a = 0L; //使用volatile声明64位的long型变量
public void set(long b) {
a = b; //单个volatile变量的写
}
public void increment() {
a++; //复合(多个)volatile变量的读/写
}
public long get() {
return a; //的那个volatile变量的读
}
}
public class VolatileTest2 {
long a = 0L; //64位的普通long型变量
public synchronized void set(long b) { //单个普通变量的写使用同步锁
a = b;
}
public void increment() { //普通方法调用
long tmp = get(); //调用以同步的读方法
tmp += 1; //普通的写操作
set(tmp); //调用以同步的写方法
}
public synchronized long get() { //单个普通变量的读使用同步锁
return a;
}
}
上述两个示例代码所带来的的执行效果是相同的。
可以看到被volatile修饰的变量读与写操作是原子性的。如前面所述,被Synchronized修饰的变量每次写操作完成后,会强制将工作内存中缓存的共享变量强制刷新到主内存中。所以保证了volatile修饰变量的可见性。
从上述示例代码中我们也能看出,即便读与写是原子性,但是依旧不能保证 a++;是原子操作。这也是很多人对volatile字段理解困难的原因所在。
简而言之,volatile变量自身具有下列特征。
- 可见性:对一个volatile变量的读,总是能看到(任意线程)对这个volatile变量最后的写入。
- 原子性:对任意单个volat变量的读 / 写具有原子性,但类似volatile++这种复合操作不具有原子性。
这里插入一个以前楼主遇到过的一个面试题:请问对于double和long类型的读写是原子性的吗?
double和long类型是64位的,在一些32位的处理器上,可能会把一个64位的long/double型变量的写操作才分为两个32位的写操作来执行。所以此时对这个64位变量的写操作将不再具有原子性。 但是如果被volatile修饰的话,写64位的double和long的操作依旧是原子操作。
volatile的禁止重排序
除了前面内存可见性中讲到的volatile关键字可以保证变量修改的可见性之外,还有另一个重要的作用:在JDK1.5之后,可以使用volatile变量禁止指令重排序。
volatile关键字通过提供“内存屏障”的方式来防止指令被重排序,为了实现volatile的内存语义,编译器在生成字节码时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。
对于编译器来说,发现一个最优布置来最小化插入屏障的总数几乎不可能,为此,Java内存模型采取保守策略。下面是基于保守策略的JMM内存屏障插入策略:
- 在每个volatile写操作的前面插入一个StoreStore屏障。
- 在每个volatile写操作的后面插入一个StoreLoad屏障。
- 在每个volatile读操作的后面插入一个LoadLoad屏障。
- 在每个volatile读操作的后面插入一个LoadStore屏障
总结来说:
- volatile写操作之前的操作不会被编译器重排序到写操作之后。
- volatile读之后的操作不会被编译器重排序到volatile读操作之前。
- 第一个是volatile读操作,第二个是volatile写操作,不能重排序
volatile的使用场景
状态标志
用volatile修饰的boolean 变量来作为while循环的的判断条件:当这个变量被其他线程修改的时候能保证while循环能立即读到。
一次性安全发布
初始化对象的正确步骤为:
1、分配对象的内存空间
2、初始化对象
3、设置引用指向刚分配的内存地址
然而由于重排序机制,可能导致2、3步骤重排序,导致初始化对象的步骤变为 1-3-2。
著名的**双重检查锁**定存在的问题就是因为初始化对象的重排序,引用所指向的对象可能还没有完成初始化,而仅仅是指向了一个空的内存地址。
独立观察
这是第一种使用场景的引用。例如一种环境传感器能够感觉环境温度。一个后台线程可能会每隔几秒读取一次该传感器,并更新包含当前文档的 volatile 变量。然后,其他线程可以读取这个变量,从而随时能够看到最新的温度值。
开销较低的读-写锁策略
前面我们介绍过,因为 ++x 实际上是三种操作(读、添加、存储)的简单组合,如果多个线程凑巧试图同时对 volatile 计数器执行增量操作,那么它的更新值有可能会丢失。但是被volatile修饰变量的读 / 写却是原子操作。所以当共享变量被volatile修饰之后,我们只需要在复合操作的方法上加上synchronized比直接用synchronized修饰该变量效率高的多。
volatile总结
相对于synchronized块的代码锁,volatile应该是提供了一个轻量级的针对共享变量的锁,当我们在多个线程间使用共享变量进行通信的时候需要考虑将共享变量用volatile来修饰。
volatile是一种稍弱的同步机制,在访问volatile变量时不会执行加锁操作,也就不会执行线程阻塞,因此volatilei变量是一种比synchronized关键字更轻量级的同步机制。
synchronized和volatile的区别
1、 volatile不会进行加锁操作:
volatile变量是一种稍弱的同步机制在访问volatile变量时不会执行加锁操作,因此也就不会使执行线程阻塞,因此volatile变量是一种比synchronized关键字更轻量级的同步机制。
2、volatile变量作用类似于同步变量读写操作:
从内存可见性的角度看,写入volatile变量相当于退出同步代码块,而读取volatile变量相当于进入同步代码块。
3、volatile不如synchronized安全:
在代码中如果过度依赖volatile变量来控制状态的可见性,通常会比使用锁的代码更脆弱,也更难以理解。仅当volatile变量能简化代码的实现以及对同步策略的验证时,才应该使用它。一般来说,用同步机制会更安全些。
4、volatile无法同时保证内存可见性和原则性:
加锁机制(即同步机制)既可以确保可见性又可以确保原子性,而volatile变量只能确保可见性,原因是声明为volatile的简单变量如果当前值与该变量以前的值相关,那么volatile关键字不起作用,也就是说如下的表达式都不是原子操作:“count++”、“count = count+1”。
java并发编程(2) --Synchronized与Volatile区别的更多相关文章
- Java并发编程:Synchronized及其实现原理
Java并发编程系列: Java 并发编程:核心理论 Java并发编程:Synchronized及其实现原理 Java并发编程:Synchronized底层优化(轻量级锁.偏向锁) Java 并发编程 ...
- Java并发编程:Synchronized底层优化(偏向锁、轻量级锁)
Java并发编程系列: Java 并发编程:核心理论 Java并发编程:Synchronized及其实现原理 Java并发编程:Synchronized底层优化(轻量级锁.偏向锁) Java 并发编程 ...
- Java并发编程:synchronized
Java并发编程:synchronized 虽然多线程编程极大地提高了效率,但是也会带来一定的隐患.比如说两个线程同时往一个数据库表中插入不重复的数据,就可能会导致数据库中插入了相同的数据.今天我们就 ...
- 【转】Java并发编程:synchronized
一.什么时候会出现线程安全问题? 在单线程中不会出现线程安全问题,而在多线程编程中,有可能会出现同时访问同一个资源的情况,这种资源可以是各种类型的资源:一个变量.一个对象.一个文件.一个数据库表等,而 ...
- 4、Java并发编程:synchronized
Java并发编程:synchronized 虽然多线程编程极大地提高了效率,但是也会带来一定的隐患.比如说两个线程同时往一个数据库表中插入不重复的数据,就可能会导致数据库中插入了相同的数据.今天我们就 ...
- 【Java并发编程】11、volatile的使用及其原理
一.volatile的作用 在<Java并发编程:核心理论>一文中,我们已经提到过可见性.有序性及原子性问题,通常情况下我们可以通过Synchronized关键字来解决这些个问题,不过如果 ...
- Java并发编程(六)volatile关键字解析
由于volatile关键字是与Java的内存模型有关的,因此在讲述volatile关键之前,我们先来了解一下与内存模型相关的概念和知识. 一.内存模型的相关概念 Java内存模型规定所有的变量都是存在 ...
- java并发编程系列七:volatile和sinchronized底层实现原理
一.线程安全 1. 怎样让多线程下的类安全起来 无状态.加锁.让类不可变.栈封闭.安全的发布对象 2. 死锁 2.1 死锁概念及解决死锁的原则 一定发生在多个线程争夺多个资源里的情况下,发生的原因是 ...
- Java并发编程的艺术(三)——volatile
1. 并发编程的两个关键问题 并发是让多个线程同时执行,若线程之间是独立的,那并发实现起来很简单,各自执行各自的就行:但往往多条线程之间需要共享数据,此时在并发编程过程中就不可避免要考虑两个问题:通信 ...
随机推荐
- C/C++静态代码安全检查工具
静态代码安全检查工具是一种能够帮助程序员自动检测出源程序中是否存在安全缺陷的软件.它通过逐行分析程序的源代码,发现软件中潜在的安全漏洞.本文针对 C/C++语言程序设计中容易存在的多种安全问题,分别分 ...
- github page 配置hexo 博客 的常见错误
缘起 最近看到好多的公众号作者推荐大家搭建自己的博客,自己手痒也搭建了一个个人博客lumang,具体过程就是一开始上网搜索一番教程,按照教程开始搭建,由于是windows的环境,同时教程也有很多的老旧 ...
- python装饰器小计
1.装饰器:本质是函数,是用来给其他函数添加附加扩展功能的函数,其返回值是一个函数(函数指针) 2.装饰器作用:不改变函数源代码和函数调用方式的前提下添加函数的附加功能. 3.装饰器储备知识点: A. ...
- sqlserver两种分页方法比较
-- 3000 page(从1开始) 10 pagesize -- 方法1(效率不高): SELECT TOP 10 * FROM [xxx].[oooo] WHERE id NOT IN (SELE ...
- libevent库简单使用
一.libevent库简介 Libevent是一个用于开发可扩展性网络服务器的基于事件驱动(event-driven)模型的网络库.Libevent有几个显著的亮点: (1)事件驱动(event-dr ...
- linux线程及互斥锁
进程是资源管理的最小单元,线程是程序执行的最小单元.在操作系统的设计上,从进程演化出线程,最主要的目的就是更好的支持SMP以及减小(进程/线程)上下文切换开销. 就像进程有一个PID一样,每个线程也有 ...
- UIDatePicker在swift中的使用
在上一篇文章中,创建了UISegmentedControl控件并了解它的简单用法,这篇文章主要学习DatePicker的使用,将通过Swift语言创建一个简单的例子. UIDatePicker对象:是 ...
- Linux kernel的中断子系统之(七):GIC代码分析
返回目录:<ARM-Linux中断系统>. 总结: 原文地址:<linux kernel的中断子系统之(七):GIC代码分析> 参考代码:http://elixir.free- ...
- SSIS 检查点
在SSIS中,检查点实际上是一个记录系统,用于记录控制流中Task组件的执行状态.通过合理地配置Checkpoint,在Package运行出错之后,重新执行Package,可以跳过上一次已经成功执行的 ...
- SwipeListView 详解 实现微信,QQ等滑动删除效果
转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/28508769 今天看别人项目,看到别人使用了SwipeListView,Goog ...