一、多线程的三大性质

原子性;可见性、有序性

二、原子性

原子性介绍

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

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

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. 附002.Nginx全系列大总结

    Nginx全系列总结如下,后期不定期更新. 欢迎基于学习.交流目的的转载和分享,禁止任何商业盗用,同时希望能带上原文出处,尊重ITer的成果,也是尊重知识. 若发现任何错误或纰漏,留言反馈或右侧添加本 ...

  2. 001.Nginx简介

    一 Nginx概述 1.1 Nginx简介 Nginx是一个高性能的HTTP和反向代理web服务器,Nginx是一款轻量级的Web服务器/反向代理服务器及电子邮件(IMAP/POP3)代理服务器,在B ...

  3. 利用74HC595实现的流水灯 Arduino

    int big = 2; int push = 3; int datain = 4; void setup() { Serial.begin(9600); pinMode(big, OUTPUT); ...

  4. Head First HTML与CSS(第2版)PDF高清完整版免费下载|百度云盘

    百度云盘:Head First HTML与CSS(第2版)PDF高清完整版免费下载 提取码:i8q4 内容简介 是不是已经厌倦了那些深奥的HTML书?你可能在抱怨,只有成为专家之后才能读懂那些书.那么 ...

  5. time strptime()方法 时间操作

    Python time strptime()方法 时间操作   描述 Python time strptime() 函数根据指定的格式把一个时间字符串解析为时间元组. 语法 strptime()方法语 ...

  6. animation动画汇总(一阶段项目)

    animation 属性 动画属性: 1.animation-name:规定需要绑定到选择器的 keyframe 名称. 2.animation-duration:规定完成动画所花费的时间,以秒或毫秒 ...

  7. 重置spyder 解决 gbk 编码不能读取问题

    重置spyder 解决 gbk 编码不能读取问题 2020-06-18

  8. MySQL数据库常见命令

    数据库的操作 MySQL服务器的常见命令 开启:sudo service mysql start关闭:sudo service mysql stop重启:sudo service mysql rest ...

  9. Jmeter 中 CSV 如何参数化测试数据并实现自动断言

    当我们使用Jmeter工具进行接口测试,可利用CSV Data Set Config配置元件,对测试数据进行参数化,循环读取csv文档中每一行测试用例数据,来实现接口自动化.此种情况下,很多测试工程师 ...

  10. PHP usort() 函数

    ------------恢复内容开始------------ 实例 使用用户自定义的比较函数对数组 $a 中的元素进行排序:Sort the elements of the $a array usin ...