一、多线程的三大性质

原子性;可见性、有序性

二、原子性

原子性介绍

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

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

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. Squeeze-and-Excitation Networks(SENet)详解

    一.SENet简介 Squeeze-and-Excitation Networks(SENet)是由自动驾驶公司Momenta在2017年公布的一种全新的图像识别结构,它通过对特征通道间的相关性进行建 ...

  2. python基础--闭包、装饰器

    装饰器的详细使用 (1)小知识点补充 在这里我们先学一个简单的知识点. li = ['alex', '银角', '女神', 'egon', '太白'] for i in enumerate(li): ...

  3. Java 异常处理专题,从入门到精通

    内置异常和Throwable核心方法 Java内置异常 可查异常(必须要在方法里面捕获或者抛出) ClassNoFoundException 应⽤程序试图加载类,找不到对应的类 IllegalAcce ...

  4. Asp.net Core 3.1基于AspectCore实现AOP,实现事务、缓存拦截器

    最近想给我的框架加一种功能,就是比如给一个方法加一个事务的特性Attribute,那这个方法就会启用事务处理.给一个方法加一个缓存特性,那这个方法就会进行缓存. 这个也是网上说的面向切面编程AOP. ...

  5. Spring集成Quartz定时任务

    1.导入jar包 2.配置applicationContext.xml文件 <!-- 任务调度1 --> <!-- bean id="simpleJob" cla ...

  6. shell动态向sql传参

    一直在想有什么好方法可以实现,用shell动态给sql传参,自己写了一个简单,有什么好方法,欢迎留言补充,下面代码纯手打,可能有疏忽之处,请大佬批评指正指正. 实现方法如下: 1.新建一个文件02.t ...

  7. luogu P1784 数独 dfs 舞蹈链 DXL

    LINK:数独 这道题好难 比DXL模板题要难上不少. 首先 还是考虑将行当做决策 那么 一共有\(9*9*9=729\) 个决策. 考虑列用来填充 需要有的条件为 某个位置能能放一次\(9*9\) ...

  8. Python多线程-ThreadLocal

    参考:https://www.liaoxuefeng.com/wiki/1016959663602400/1017630786314240#0 ThreadLocal ThreadLocal变量虽然是 ...

  9. 面试被问:如果系统 CPU 突然飙升且 GC 频繁,你该如何排查?

    出自:开源中国 原文:系统运行缓慢,CPU 100%,以及Full GC次数过多问题的排查思路 处理过线上问题的同学基本上都会遇到系统突然运行缓慢,CPU 100%,以及Full GC次数过多的问题. ...

  10. 移动物体监控系统-sprint4嵌入式web服务器开发

    一.BOA嵌入式服务器的移植 step1:下载BOA服务器并解压,进入boa下面的src目录,执行./configure生成必须的配置文件以及Makefile step2:修改Makefile文件 c ...