原子性

[高并发Java 三] Java内存模型和线程安全

不想你离开。 提交于 2020-02-29 06:33:01
网上很多资料在描述Java内存模型的时候,都会介绍有一个主存,然后每个工作线程有自己的工作内存。数据在主存中会有一份,在工作内存中也有一份。工作内存和主存之间会有各种原子操作去进行同步。 下图来源于 这篇Blog 但是由于Java版本的不断演变,内存模型也进行了改变。本文只讲述Java内存模型的一些特性,无论是新的内存模型还是旧的内存模型,在明白了这些特性以后,看起来也会更加清晰。 1. 原子性 原子性是指一个操作是不可中断的。即使是在多个线程一起执行的时候,一个操作一旦开始,就不会被其它线程干扰。 一般认为cpu的指令都是原子操作,但是我们写的代码就不一定是原子操作了。 比如说i++。这个操作不是原子操作,基本分为3个操作,读取i,进行+1,赋值给i。 假设有两个线程,当第一个线程读取i=1时,还没进行+1操作,切换到第二个线程,此时第二个线程也读取的是i=1。随后两个线程进行后续+1操作,再赋值回去以后,i不是3,而是2。显然数据出现了不一致性。 再比如在32位的JVM上面去读取64位的long型数值,也不是一个原子操作。当然32位JVM读取32位整数是一个原子操作。 2. 有序性 在并发时,程序的执行可能就会出现乱序。 计算机在执行代码时,不一定会按照程序的顺序来执行。 class OrderExample { int a = 0; boolean flag = false;

关于JVM中long和double的读取原子性

为君一笑 提交于 2019-12-17 14:21:47
【推荐】2019 Java 开发者跳槽指南.pdf(吐血整理) >>> 今天看《Java并发编程实战》的书中,关于long和double的原子性有这么一段话,意思就是在JVM中,对于32位(或者以下)的数值变量都是原子性读写,但是对于long和double这种64位的操作是非原子性。分成两次32位的操作。 在以下这种操作中就会出现读取的数值错误。 线程A先写高位32位操作, 线程B读高位32位, 线程B读地位32位, 线程A写地位32位。 那么解决办法就是对变量加上volatile关键字,volatile关键字有3个主要功能,第一个就是可见性,每次直接从内存读写。第二个就是禁止指令重排序,在JVM中会对指令进行优化,优化的时候可能会对指令进行排序。第三个就是对volatile修饰的变量读取都是原子性的。 但是书上没有说操作系统的位数和JVM的位数。 所以我猜想,只有64位JVM+64位操作系统+64位硬件才能实现对64位long和double的原子性读取。 来源: oschina 链接: https://my.oschina.net/u/2250599/blog/533108

关于Java自增操作的原子性分析

旧街凉风 提交于 2019-12-17 13:00:07
【推荐】2019 Java 开发者跳槽指南.pdf(吐血整理) >>> 刚接触编程的时候写的是C/C++,转java后,一直都认为java自增操作是原子性的操作,昨天看到一篇关于ArrayList多线程不安全的文章,里边有个Demo,发现新增操作(add)次数和List的size怎么也对不上,按照自增是原子性操作的话是应该不会出现这个问题的,但现实确实是残酷的。几经查看ArrayList的源码,都没有发现问题,于是终于怀疑到自增操作上,经验证:Java自增操作并非原子性操作。证明如下: 1、测试代码 package com.hjr.back16.common.util; import static java.lang.System.out; /** * 自增操作原子性测试 * @author scuechjr * @date 2016-4-24 1:29:48 */ public class IncrementTestDemo { public static int count = 0; public static Counter counter = new Counter(); public static void main(String[] args) { for (int i = 0; i < 10; i++) { new Thread() { public void run

Java多线程之内存可见性

一曲冷凌霜 提交于 2019-12-06 03:31:30
Java内存模型( JMM ) : 1) 所有的变量都存储在主内存中 2) 每个线程都有自己独立的工作内存, 里面保存该线程使用到的变量的副本 ( 主内存中该变量的一份拷贝 ) JMM 两条规定: 1) 线程对共享变量的所有操作都必须在自己的工作内存中进行 2) 不同线程之间无法直接访问其他线程工作内存中的共享变量, 线程间共享变量值的传递必须通过主内存 线程间共享变量可见性实现的原理: 线程A 对共享变量的修改想被线程B 及时看到, 必须要经过以下2个步骤: 1) 把线程A 工作内存中更新过的共享变量刷新到主内存中 ( store ) 2) 将主内存中最新的共享变量的值共享到线程B 工作内存中 ( load ) Java 语言层面支持的可见性实现方式: 1) synchronized 2) volatile JUC 包下的类也可以实现可见性 1) Atomic 2) ReentrantLock 3) Semaphore 1. synchronized 实现可见性 JMM 关于 synchronized 的两条规定: 1) 线程释放锁前, 必须把共享变量的最新值从该线程的工作内存刷新到主内存中 2) 线程持有锁时, 将清空该线程工作内存中共享变量的值, 从主内存中读取最新的值 synchronized 实现可见性的原因: 线程释放锁前对共享变量的修改在下次持有锁时对其他线程可见