一、多线程的三大性质

原子性;可见性、有序性

二、原子性

原子性介绍

原子性是指:一个操作时不可能中断的,要么全部执行成功要么全部执行失败,有着同生共死的感觉。即使在多线程一起执行的时候,一个操作一旦开始,就不会被其他线程所干扰。

先看看哪些是原子操作,哪些不是原子操作:

int a=10;  //

a++;  //

int b=a;  //

a=a+1;  //

上面这四个语句中只有第1个语句是原子操作,将10赋值给线程工作内存的变量a,而语句2 a++,实际上包含了三个操作:读取变量a的值;对进行加1的操作,将计算后的值在赋值给变量a,而这三个操作都无法构成原子操作。对语句3,4的分析同理,这两条语句不具备原子性。当然,Java内存模型中定义了8中操作都是原子的,不可再分的。

lock(锁定):作用于主内存中的变量,它把一个变量标示为一个线程独占的状态;

unlock(解锁):作用于主内存中的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定;

read(读取):作用于主内存的变量,它把一个变量的值从主内存传输到线程的工作内存,以便后面的load动作使用;

load(载入):作用于工作内存中的变量,它把read操作从主内存中得到的变量值放入工作内存中的变量副本;

use(使用):作用于工作内存中的变量,它把工作内存中一个变量的值传递给执行引擎,每当虚拟机遇到一个需要使用到变量的值得字节码指令时将会执行这个操作;

assign(赋值):作用于工作内存中的变量,它把一个执行引擎接受到的值赋给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作;

store(存储):作用于工作内存的变量,它把工作内存中一个变量的值传递给主内存中以便随后的write操作使用;

write(操作):作用于主内存的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中。

上面的这些操作时相当底层的。那么如何理解这些指令呢?比如:把一个变量从主内存复制到工作内存中就需要执行read,load操作,将工作内存同步到主内存中就需要执行store,write操作。注意的是:Java内存模型只是要求上述两个操作时顺序执行的并不是连续执行的。也就是说read和load之间可以插入其他指令,store和write也可以插入其他指令。比如对主内存中的a,b进行访问就可以出现这样的操作顺序:read a,read b,load b,load a

由原子性操作变量read,load,use,assign,store,write可以大致认为基本数据类型的访问读写具备原子性(例外的就是long和double的非原子性协定)

三、synchronized和volatile的原子性

synchronized

上面一共有八条原子操作,其中六条可以满足基本数据类型的访问读写具备原子性,还剩下lock和unlock两条原子操作。

如果我们需要大范围的原子操作就可以使用lock和unlock原子操作。尽管JVM没有吧lock和unlock开放给我们,但JVM以更高层次的指令monitorenter和monitorexit开放给我们使用,反映到Java代码中就是---synchronized关键字,也就是说synchronized满足原子性.

volatile

package passtra;

public class VolatileExample{

    private static volatile int count=0;

    public static void main(String[] args) {
for(int i=0;i<10;i++){
new Thread(new Runnable() { @Override
public void run() {
for(int i=0;i<10000;i++){
count++;
}
}
}).start();
} try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.err.println(count);
}
}

开启10个线程,每个线程都自加10000次,如果不出现线程安全的问题最终的结果应该是10*10000,可是运行多次都是小于100000。

问题在于volatile并不能保证原子性,count++并不是一个院子操作,包含了三个步骤:1、读取变量count的值;2、对count加1;3、将新值赋给变量count。如果线程A读取count到工作内存中,其他线程对这个值已经做了自增操作后,那么线程A的值自然而然就是一个过期的值,因此,总结过必然会是小于100000的。

如果让volatile保证原子性,就必须符合以下两个原则:

运算结果并不依赖变量的当前的值,或者能够确保只有一个线程修改变量的值;

变量不需要与其他的状态变量共同参与不变约束。

四、synchronized和volatile的有序性

synchronized

synchronized语义表示锁在同一时刻只能由一个线程进行获取,当锁被占用后,其他线程只能等待。

因此,synchronized语义就要求线程在访问读写操作共享变量时只能串行执行,因此synchronized具有有序性。

volatile

在JMM 中,为了性能优化,编译器和处理器会进行指令重排序,也就是说Java程序天然的有序性,可以总结为:如果在本线程内观察,所有的操作都是有序的,如果在一个线程观察另一个线程,所有的操作都是无序的。

在单例模式的实现上有一种双重检验锁定的方式DCL(Double-checked Locking)

public class Singleton{

    private static volatile Singleton singleton;

    private Singleton(){}

    public static Singleton getsingleton(){

        if(singleton==null){
synchronized (Singleton.class) {
if(singleton==null){
singleton=new Singleton();
}
}
}
return singleton;
}
}

这里为什么要加volatile?先分析下不加volatile的情况,有问题的语句是这条:singleton=new Singleton();这条语句实际上包含了三个操作:1、分配对象的内存空间;2、初始化对象;3、设置singleton指向刚分配的内存地址。但由于存在重排序的问题,可能有以下的执行顺序:如果2和3进行了重排序的话,线程B进行判断if(singleton==null)时就会出现true,而实际撒花姑娘这个singleto并没有初始化成功,显而易见对B线程来说之后的操作就会是错的。

而用volatile修饰的话就可以禁止2和3操重排序,从而避免这种情况

volatile包含禁止指令重排序的语义,其具有有序性。

五、synchronized和volatile的可见性

可见性是指:当一个线程修改了共享变量后,其他线程能够立即得知这个修改。

synchronized:当线程获取锁时或从主内存中获取共享变量的最新值,释放锁的时候会将共享变量同步到主内存中。所以,synchronized具有可见性

volatile:同样在volatile分析中,会通过在指令中天机lock指令,一实现内存可见性,因此,volatile具有可见性

所以:synchronized具有原子性,有序性和可见性

volatile具有有序性和可见性

Java并发--三大性质的更多相关文章

  1. Java三大性质总结:原子性、可见性以及有序性

    本人免费整理了Java高级资料,涵盖了Java.Redis.MongoDB.MySQL.Zookeeper.Spring Cloud.Dubbo高并发分布式等教程,一共30G,需要自己领取.传送门:h ...

  2. java并发 - 自底向上的原理分析

    [TOC] 事先声明,我只是java并发的新手,这篇文章也只是我阅读<java并发编程的艺术>一书(内容主要涉及前3章)的一些总结和感悟.希望大家能多多讨论,对于错误的地方还请指出. 0. ...

  3. 面渣逆袭:Java并发六十问,快来看看你会多少道!

    大家好,我是老三,面渣逆袭 继续,这节我们来盘一盘另一个面试必问知识点--Java并发. 这篇文章有点长,四万字,图文详解六十道Java并发面试题.人已经肝麻了,大家可以点赞.收藏慢慢看!扶我起来,我 ...

  4. 多线程的通信和同步(Java并发编程的艺术--笔记)

    1. 线程间的通信机制 线程之间通信机制有两种: 共享内存.消息传递.   2. Java并发 Java的并发采用的是共享内存模型,Java线程之间的通信总是隐式执行,通信的过程对于程序员来说是完全透 ...

  5. Java并发编程:同步容器

    Java并发编程:同步容器 为了方便编写出线程安全的程序,Java里面提供了一些线程安全类和并发工具,比如:同步容器.并发容器.阻塞队列.Synchronizer(比如CountDownLatch). ...

  6. 《Java并发编程实战》第十一章 性能与可伸缩性 读书笔记

    造成开销的操作包含: 1. 线程之间的协调(比如:锁.触发信号以及内存同步等) 2. 添加�的上下文切换 3. 线程的创建和销毁 4. 线程的调度 一.对性能的思考 1 性能与可伸缩性 执行速度涉及下 ...

  7. java并发面试

    1.在java中守护线程和本地线程区别? java中的线程分为两种:守护线程(Daemon)和用户线程(User). 任何线程都可以设置为守护线程和用户线程,通过方法Thread.setDaemon( ...

  8. Java并发编程75道面试题及答案

    1.在java中守护线程和本地线程区别? java中的线程分为两种:守护线程(Daemon)和用户线程(User). 任何线程都可以设置为守护线程和用户线程,通过方法Thread.setDaemon( ...

  9. 《Java并发编程的艺术》Java并发机制的底层实现原理(二)

    Java并发机制的底层实现原理 1.volatile volatile相当于轻量级的synchronized,在并发编程中保证数据的可见性,使用 valotile 修饰的变量,其内存模型会增加一个 L ...

随机推荐

  1. Python源码剖析|百度网盘免费下载|Python新手入门|Python新手学习资料

    百度网盘免费下载:Python源码剖析|新手免费领取下载 提取码:g78z 目录  · · · · · · 第0章 Python源码剖析——编译Python0.1 Python总体架构0.2 Pyth ...

  2. 在ASP.NET Core中创建自定义端点可视化图

    在上篇文章中,我为构建自定义端点可视化图奠定了基础,正如我在第一篇文章中展示的那样.该图显示了端点路由的不同部分:文字值,参数,动词约束和产生结果的端点: 在本文中,我将展示如何通过创建一个自定义的D ...

  3. sourceTree安装、跳过bitbucket注册免登陆方法

    下载好以后,点击安装运行,会出现下面这个窗口 关掉这个窗口,打开C:\Users\{users}\AppData\Local\Atlassian\SourceTree(users是计算机的名字),新建 ...

  4. Webpack 原理浅析

    作者: 凹凸曼 - 风魔小次郎 背景 Webpack 迭代到4.x版本后,其源码已经十分庞大,对各种开发场景进行了高度抽象,阅读成本也愈发昂贵.但是为了了解其内部的工作原理,让我们尝试从一个最简单的 ...

  5. FPGA内部IP核DDS

    项目当中需要正弦信号与余弦信号,首先想到了DDS芯片,例如AD9833.AD9834.由于还需要用FPGA   做一些数据处理,后来干脆直接用FPGA 内部的DDSIP核,同时根据IP核内部的相位累加 ...

  6. 时间序列ARIMA模型

    时间序列ARIMA模型 1.数据的平稳性与差分法 让均值和方差不发生明显的变化(让数据变平稳),用差分法 2.ARIMA模型-----差分自回归平均移动模型 求解回归的经典算法:最大似然估计.最小二乘 ...

  7. PHP 5 echo 和 print 语句

    PHP 5 echo 和 print 语句 在 PHP 中有两个基本的输出方式: echo 和 print. 本章节中我们会详细讨论两个语句的用法,并在实例中演示如何使用 echo 和 print. ...

  8. PHP 表单和用户输入讲解

    PHP 表单和用户输入 PHP 中的 $_GET 和 $_POST 变量用于检索表单中的信息,比如用户输入. PHP 表单处理 有一点很重要的事情值得注意,当处理 HTML 表单时,PHP 能把来自 ...

  9. PHP vsprintf() 函数

    实例 把格式化字符串写入变量中: <?php高佣联盟 www.cgewang.com$number = 9;$str = "Beijing";$txt = vsprintf( ...

  10. P4221 [WC2018]州区划分 无向图欧拉回路 FST FWT

    LINK:州区划分 把题目中四个条件进行规约 容易想到不合法当前仅当当前状态是一个无向图欧拉回路. 充要条件有两个 联通 每个点度数为偶数. 预处理出所有状态. 然后设\(f_i\)表示组成情况为i的 ...