代码编织梦想

        回顾之前Java内存模型特征可以了解到该模型是围绕着并发过程中如何处理原子性、可见性和有序性这三个特征来建立的。

        原子性:一个操作或多个操作要么全部执行完成且执行过程不被中断,要么就不执行。Java内存模型来直接保证的原子性变量操作包括read、load、assign、use、store和write这六个,如果应用场景需要更大范围的原子性保证,Java内存模型还提供了lock和unlock操作来满足需求,比如synchronize关键字。在synchronize块之间的操作就具备原子性。

        可见性:指当一个线程修改了共享变量的值,其它线程能够立即得知这个修改。Java内存模型是通过在变量修改后将新值同步回主内存,在变量读取前从主内存刷新变量值这种依赖主内存作为传递媒介的方式来实现可见性的。

        有序性:在本线程内,所有操作都是有序的,即按照代码先后顺序执行;如果在一个线程观察另一个线程,所有操作都是无序的,因为有“指令重排序”现象和“工作内存与主内存同步延迟”现象。

        了解上述并发处理三大特性之后,再看volatile关键字,该关键字满足可见性和有序性,所以在Java中能提供最轻量级的同步机制。

        volatile可见性:volatile的规则保证了新值能立即同步到主内存,以及每次使用前立即从主内存刷新。因此volatile保证了多线程操作变量的可见性。

        volatile有序性:使用volatile修饰的变量会禁止指令重排序,JVM中对不是原子性的操作会进行重排序的优化,只要不影响最终计算结果。

例如:

// 线程1中
{
    ... 
    obj= getObject();      // 步骤1
    isRegister = true;     // 步骤2
    ...
}

// 线程2中
{
    ...
    if (isRegister) {     // 步骤3,依赖步骤2的值
        fun(obj);         // 步骤4,依赖步骤1的值
    }
    ...
}

        上述代码中,线程1中的代码步骤1和步骤2可能出现重排序的情况,因为对线程1来说顺序打乱不影响线程1自己的运算结果,但是对线程2来说,如果线程1中的步骤2先执行,最终就无法得到正确的结果。

        volatile修饰的变量,在读取或者写入的前后都会插入内存屏障来达到禁止重排序的效果,进而保证有序性。

        volatile不能保证原子性,因此不能完全达到线程安全效果,除非满足以下条件:

  • 运算结果不依赖当前值,或者能够确保只有单一的线程修改该变量的值。
  • 变量不需要与其他状态变量共同参与不变约束。

        比如volatile修饰的变量count出现count++、count+1等非原子操作,就无法确保线程安全。

import java.io.*;
class test  
{
    private static final int THREAD_COUNT = 20;
    public static volatile int race = 0;
    public static void increase () {
        race++;
    }
	public static void main (String[] args) throws java.lang.Exception
	{
		System.out.println("hi");
		for (int i = 0; i < THREAD_COUNT; i++) {
		    new Thread(new Runnable() {
		        @Override
		        public void run() {
		            for (int j = 0; j < 1000; j++) {
		                increase();
		            }
		        }
		    }).start();
		}
		while (Thread.activeCount() > 1) {
            Thread.yield();
        }
		System.out.println("race = "+ race);
	}
}

运行结果:

hi
race = 19902

        注意,测试的结果跟环境有关,有的测试环境可能结果出现正确的情况,可以将测试数据改大一点。本人测试时,开始选择10个线程,然后只累加10次,测试下来发现结果都是正确的~

        另外大家可能会疑惑volatile不是保证变量的可见性了吗?一旦被修改会立即同步到主内存中,确保其它线程都拿到最新数据。这里可以这样理解,比如线程1和线程2都拿到最新volatile修饰的变量count = 10,当count++时,由于不是原子操作,当线程1还在执行加加指令时,线程2已经将count数据11更新到主存了,此时对于线程1来说,数据已经过时了,等线程1执行完时,又将11同步到主存,结果导致两个线程都执行count++,但是最终结果却小于正常值。

        Android中volatile经常跟单例模式一起使用,确保线程安全。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 本文链接: https://blog.csdn.net/yj_android_develop/article/details/111094154

Java-- synchronized--原理总结--偏向锁和自旋优化-爱代码爱编程

Java-- synchronized--原理总结--偏向锁和自旋优化 偏向锁自旋优化 偏向锁 首先我们了解一下对象头MarkWord结构 后面的lock 分别对应着对象的不同状态, 注意:正常状态和偏向锁都是01结尾,但是偏向锁的倒数第三位是1,正常的是0,偏向锁前面存储的偏向线程id和时间戳 下面是对象MarkWord不通状态存储的

java笔记之单例设计模式:饿汉式 vs 懒汉式详细说明-爱代码爱编程

单例设计模式: 所谓类的单例设计模式,就是采取一定的方法保证在整个软件系统中,对某个类只能存在一个实例对象。 那么如何实现上述单例设计模式呢? 实现步骤: 首先创建私有化构造器在内部创建对象,相当于这个类的属性使用公共的、静态的方法返回内部已创建的对象注:使用下述逻辑或许可帮助记忆 ∵ 单例模式,在外部只能有一个对象,即避免使用对象.方

Stanford Pintos Project2源代码分析&实现思路-爱代码爱编程

0. 引言 本文是对以下仓库中Pintos Project2源代码的思路分析,截取了重要的核心函数,以方便读者理解整个的实现思路。仅供参考,完成作业记得自己深入理解噢~ https://github.com/NicoleMayer/pintos_project2 1. 打印进程终结信息 1.1 process_exit void pro

Java并发编程:并发中死锁的形成条件及处理-爱代码爱编程

死锁是一种无限的互相等待的状态,两个或两个以上的线程或进程构成一个互相等待的环状。以两个线程为例,线程一持有A锁同时在等待B锁,而线程二持有B锁同时在等待A锁,这就导致两个线程互相等待无法往下执行。现实生活中一个经典的死锁情形就是四辆汽车通过没有红绿灯的十字路口,假如四辆车同时到达中心的,那么它们将形成一个死锁状态。每辆车拥有自己车道上的使用权,但同时也在

Linux下C语言验证多线程-分段排序-爱代码爱编程

知识点:用Linux下的多线程,对数组中元素分段排序 -------------------------------------------------------------------------------------- 参考: https://blog.csdn.net/skrskr66/article/details/90298470 h

Volatile关键字-爱代码爱编程

Volatile关键字 一、Java 内存模型中的可见性、原子性和有序性 可见性:是指线程之间的可见性,一个线程修改的状态对另一个线程是可见的 原子性:原子是世界上的最小单位,具有不可分割性 有序性:Java 语言提供了 volatile 和 synchronized 两个关键字来保证线程之间操作的有序性,volatile 是因为其本身包含“禁止指

并发编程 — 深入解析 volatile 关键字-爱代码爱编程

在上篇文章《缓存一致性问题》,今天就聊聊 volatile 关键字。 volatile 关键字规则 Java内存模型对volatile关键字定义了一些特殊的访问规则,当一个变量被volatile修饰后,它将具备两种特性,或者说volatile具有下列两层语义: 第一、保证了不同线程对这个变量进行读取时的可见性, 即一个线程修改了某个变量的值, 这新值

Java多线程(二)同步机制从Java内存模型(JMM)说起-爱代码爱编程

同步机制从Java内存模型(JMM)说起 问题: 了解一下 JVM 运行时数据区了解一下主内存与工作内存原子性与有序性volatile 的两语义先行发生原则Java 语言中的线程安全线程安全的实现方法 文章目录 同步机制从Java内存模型(JMM)说起1. JVM运行时数据区1.1 线程共享部分:1.1.1 方法区1.1.2 Java堆1.2 线