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屏障

总结来说:

  1. volatile写操作之前的操作不会被编译器重排序到写操作之后。
  2. volatile读之后的操作不会被编译器重排序到volatile读操作之前。
  3. 第一个是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区别的更多相关文章

  1. Java并发编程:Synchronized及其实现原理

    Java并发编程系列: Java 并发编程:核心理论 Java并发编程:Synchronized及其实现原理 Java并发编程:Synchronized底层优化(轻量级锁.偏向锁) Java 并发编程 ...

  2. Java并发编程:Synchronized底层优化(偏向锁、轻量级锁)

    Java并发编程系列: Java 并发编程:核心理论 Java并发编程:Synchronized及其实现原理 Java并发编程:Synchronized底层优化(轻量级锁.偏向锁) Java 并发编程 ...

  3. Java并发编程:synchronized

    Java并发编程:synchronized 虽然多线程编程极大地提高了效率,但是也会带来一定的隐患.比如说两个线程同时往一个数据库表中插入不重复的数据,就可能会导致数据库中插入了相同的数据.今天我们就 ...

  4. 【转】Java并发编程:synchronized

    一.什么时候会出现线程安全问题? 在单线程中不会出现线程安全问题,而在多线程编程中,有可能会出现同时访问同一个资源的情况,这种资源可以是各种类型的资源:一个变量.一个对象.一个文件.一个数据库表等,而 ...

  5. 4、Java并发编程:synchronized

    Java并发编程:synchronized 虽然多线程编程极大地提高了效率,但是也会带来一定的隐患.比如说两个线程同时往一个数据库表中插入不重复的数据,就可能会导致数据库中插入了相同的数据.今天我们就 ...

  6. 【Java并发编程】11、volatile的使用及其原理

    一.volatile的作用 在<Java并发编程:核心理论>一文中,我们已经提到过可见性.有序性及原子性问题,通常情况下我们可以通过Synchronized关键字来解决这些个问题,不过如果 ...

  7. Java并发编程(六)volatile关键字解析

    由于volatile关键字是与Java的内存模型有关的,因此在讲述volatile关键之前,我们先来了解一下与内存模型相关的概念和知识. 一.内存模型的相关概念 Java内存模型规定所有的变量都是存在 ...

  8. java并发编程系列七:volatile和sinchronized底层实现原理

    一.线程安全 1.  怎样让多线程下的类安全起来 无状态.加锁.让类不可变.栈封闭.安全的发布对象 2. 死锁 2.1 死锁概念及解决死锁的原则 一定发生在多个线程争夺多个资源里的情况下,发生的原因是 ...

  9. Java并发编程的艺术(三)——volatile

    1. 并发编程的两个关键问题 并发是让多个线程同时执行,若线程之间是独立的,那并发实现起来很简单,各自执行各自的就行:但往往多条线程之间需要共享数据,此时在并发编程过程中就不可避免要考虑两个问题:通信 ...

随机推荐

  1. ArcGis SOE(server object extensions)之REST Template初体验

    一.安装vs和arcgis server for .net(本例是vs2010.as 10),然后打开vs新建一个项目

  2. 魔咒,90%未学满三个月Python编程的朋友都会出错!

    Python语言虽然优美,简洁和强大,但是也有很多坑,一不小心就会掉进去.我学Python的时候也遇到过,今天总结一下,希望对大家能有收获! Python的默认参数就被创建 一次,而不是每次调用函数的 ...

  3. hadoop中setup,cleanup,run和context讲解

    hadoop 执行中的setup run cleanup context的作用1.简介1) setup(),此方法被MapReduce框架仅且执行一次,在执行Map任务前,进行相关变量或者资源的集中初 ...

  4. Vimium快捷键记录

    作为一个Geek必备的技能 从今天起在这里仅记录下使用过的快捷键和心得(翻译自 ?) version: 1.59 版本不同,快捷键有所不同 1. 下载安装地址(自备梯子) Chrome商店:https ...

  5. Java 8系列之重新认识HashMap

    摘要 HashMap是Java程序员使用频率最高的用于映射(键值对)处理的数据类型.随着JDK(Java Developmet Kit)版本的更新,JDK1.8对HashMap底层的实现进行了优化,例 ...

  6. 微信小程序开发资源汇总 💯

    目录 官方文档 工具 插件 讨论 教程 视频教程 文章 代码 轮子 置顶 WePY:组件化的小程序开发框架 

  7. C++相关:部分标准库特殊设施

    C++ tuple(元组) tuple是C++11新标准里的类型.它是一个类似pair类型的模板.pair类型是每个成员变量各自可以是任意类型,但是只能有俩个成员,而tuple与pair不同的是它可以 ...

  8. npm安装项目出错

    F:\ajax>npm install iview --savenpm WARN saveError ENOENT: no such file or directory, open 'F:\aj ...

  9. 监督学习——K邻近算法及数字识别实践

    1. KNN 算法 K-近邻(k-Nearest Neighbor,KNN)是分类算法,是一个理论上比较成熟的方法,也是最简单的机器学习算法之一.该方法的思路是:如果一个样本在特征空间中的k个最相似( ...

  10. 第五章——支持向量机(Support Vector Machines)

    svm可用于线性或非线性分类.回归.甚至异常检测. svm尤其适用于中小数据集的复杂分类问题. 5.1 Linear SVM Classification svm对feature scales敏感,如 ...