Java高阶语法---Volatile

背景:听说Volatile Java高阶语法亦是挺进BAT的必经之路。

Volatile

volatile同步机制又涉及Java内存模型中的可见性、原子性和有序性,恶补基础一波。

可见性:

可见性简单的说是线程之间的可见性,一个线程修改的状态对另一个线程是可见对,也就是一个线程的修改结果另一个线程可以马上看到;但通常,我们无法确保执行读操作的线程能够时时的看到其他线程写入的值,So为了确保多个线程之间对内存写入操作可见性,必须使用同步机制;如用volatile修饰的变量就具有可见性,volatile修饰的变量不允许线程内部缓存和重排序,而是直接修改内存,所以对其他线程来说是可见的;但volatile只能保证被修饰的内容具有可见性,并不具备原子性,如volatile int vipNumber = 100,之后有一个vipNumber++ 的操作,这个变量vipNumber具有可见性,但是vipNumber++ 依然是一个非原子操作,也就是说这个操作同样存在线程安全问题。

原子性:

原子具有不可分割的特性,如int age = 22,这个操作是不可分割的,那么称其为原子操作,具有原子性;再比如age++,这个操作实际是age = age + 1,其是可分割的,So它不是一个原子操作;而非原子操作都会存在线程安全问题,需要我们使用同步技术(synchronized)来让它变成一个原子操作;Java的concurrent包下提供了一些原子类,如:AtomicLongMap、AtomicDouble、AtomicReference 等;在Java中用synchronized、lock和unlock来保证原子性。

有序性:

Java语言提供了volatile和synchronized两个关键字来保证线程之间操作的有序性,volatile是因为本身包含“禁止指令重排序”的语义,synchronized是由“一个变量在同一时刻只允许一条线程对其进行lock操作”这条规则获得有序性的,此规则决定了持有同一个对象锁的两个同步块只能串行执行。

Volatile原理:

volatile是一种稍弱的同步机制,其用来确保将变量的更新操作通知到其他线程;当变量声明为volatile类型后,编译器与JVM运行时都会注意到这个变量时共享的,因此不会将此变量上的操作与其他操作一起重排序;volatile修饰后变量不会缓存在寄存器或者对其他处理器不可见的地方,因此在单曲volatile类型的变量时总会返回最新写入的值;除此之外,在访问volatile变量时不会执行加锁操作,也就不会执行线程阻塞,因此volatile变量是一种比synchronized关键字更轻量级的同步机制;当对非volatile变量进行读写时,每个线程从内存拷贝变量到CPU缓存中,如果计算机有多个CPU则每个线程可能在不同的CPU上被处理,这就意味着每个线程都可以拷贝到不同的CPU缓存cache中,而不是像volatile变量那样直接读内存,JVM保证其每次读变量都从内存中读,跳过了CPU cache这一步骤。

当一个变量定义为volatile之后,其具备的两种特征:

1、保证此变量对所有的线程的可见性;当一个线程修改了此变量的值,volatle保证新值能够立即同步到主内存,以及每次使用前立即从主每次刷新;

2、禁止指令重排序优化;被volatile修饰的变量赋值后多执行了一个“load”操作,此操作相当于一个内存屏障(指令重排序时不能把后面的指令重排序到内存屏障之前到位置),只有一个CPU访问内存时,不需要内存屏障;(指令重排序:指CPU采用了允许将多条指令不按程序规定的顺序分开发送给各相应电路单元处理)

另外:在性能方面,volatile的读操作性能消耗与普通变量基本无异,但是写操作稍慢,因为它需要在本地代码中插入许多内存屏障来保证处理器不发生乱序执行。

这里撸了一个例子用volatile保证线程间的同步,如果变量author不经volatile修饰,线程2中对author的值做了修改并未同步到线程1中,其一直存在缓存中。

FYI:
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class TestVolatile {
    private volatile  String author = "tjt";  // volatile修饰author保证两个线程到可见性,即不存在缓存cache中
    //private String author = "tjt";  // 不用volatile修饰变量author则author修改值后在线程之间并不可见
    private boolean enable = false;
    public static void main(String[] args) throws Exception {
        TestVolatile testVolatile = new TestVolatile();
        log.info("原始定义的author: "+testVolatile.author);
        Thread thread = new Thread( new Runnable() {
            @Override
            public void run() {
                testVolatile.testMethodOne();
            }
        });
        thread.start();
        thread.sleep(2000l);
        testVolatile.testMethodTwo();
    }
    public void testMethodOne() {
        while(true) {
            if ("detect_tjt".equals(author) && enable == false) {
                log.info("线程testMethodOne中检测到来author修改为: "+author);
                enable = true;
                System.exit(0);
            }
        }        }
    public void testMethodTwo() {
        author = "detect_tjt";
        log.info("线程testMethodTwo中把author修改为: "+author);
    }
}
用volatile修饰author执行结果:
- 原始定义的author: tjt
- 线程testMethodTwo中把author修改为: detect_tjt
- 线程testMethodOne中检测到来author修改为: detect_tjt
无volatile修饰author执行结果:- 原始定义的author: tjt
- 线程testMethodTwo中把author修改为: detect_tjt

原文地址:https://www.cnblogs.com/taojietaoge/p/10260888.html

时间: 2024-10-25 15:53:12

Java高阶语法---Volatile的相关文章

Java高阶语法---static

背景:听说static Java高阶语法是挺进BAT必经之路. static: 静态static,很多时候会令我望文生义,但是get到了static最重要的一点,其他的理解都还ok. static最重要的一点就是:类属性中被static所引用的变量会被当作Gabage Collection的root根节点,而root就意味着着这一类变量基本上是不会被GC回收的, 即使System.gc() 都不会被回收,So,static很容易引起内存泄漏. 官方的解释就是:static可以修饰属性.方法和内部

Java高阶语法---transient

背景:听说transient Java高阶语法是挺进BAT必经之路. transient: Java中transient 关键字的作用,简单的说就是让某些被修饰的成员属性变量不被序列化. 这又扯到了序列化和反序列化: Java中对象的序列化是指将对象转换成以字节序列的形式来表示,这些字节序列包含了对象的数据和信息,一个序列化后的对象可以被写到数据库或文件中,也可以用网络传输:就比如我们使用缓存cache/redis或远程调用RPC(网络传输)如一些密码等不想让人看到的敏感信息的时候,都要让缓存或

Markdown 高阶语法

记录一些 Markdown 的高阶语法,想起来什么,再更新 分割线 两条分割线 *** *** 插入表格 Column Column Column Row Content Content | Column | Column | Column | |--|--|--| | Row | Content | Content | 行内代码 CTRL + ALL `CTRL + ALL` 反转符号 `CTRL + ALL` \`CTRL + ALL` 增加空行 <br> 删除线 00000000 ~~0

Java高并发同步Volatile的使用

引言: 在多线程并发编程中synchronized和Volatile都扮演着重要的角色,Volatile是轻量级的synchronized,它在多处理器开发中保证了共享变量的“可见性”. 可见性的意思是当一个线程修改一个共享变量时,另外一个线程能读到这个修改的值. Volatile的官方定义 Java语言规范第三版中对volatile的定义如下: java编程语言允许线程访问共享变量,为了确保共享变量能被准确和一致的更新,线程应该确保通过排他锁单独获得这个变量.Java语言提供了volatile

函数——基本语法,嵌套、匿名、高阶、递归函数

定义 函数是指将一组语句的集合通过一个名字(函数名)封装起来,要想执行这个函数,只需调用其函数名即可 特性 减小重复代码 使程序变的可扩展 易维护 函数参数 定义函数可以带参数,也可不带参数 形参:只有在被调用时才分配内存单元,在调用结束时,即刻释放所分配的内存单元.因此,形参只在函数内部有效.函数调用结束返回主调用函数则不能再使用该形参变量 实参:可以是常量.变量.表达式.函数等,无论实参是何种类型的量,在进行函数调用时,它们都必须有确定的值,以便把这些值传送给形参.因此应预先用赋值,输入等办

java关键字transient与volatile小结

本文转自:http://heaven-arch.iteye.com/blog/1160693 transient和volatile两个关键字一个用于对象序列化,一个用于线程同步,都是Java中比较高阶的话题,简单总结一下. transient transient是类型修饰符,只能用来修饰字段.在对象序列化的过程中,标记为transient的变量不会被序列化. 示例: class Test {transient int a; // 不会被持久化int b; // 持久化} 当类Test的实例对象被

Java语言基本语法

Java语言基本语法 一.标识符和关键字 标识符 在java语言中,用来标志类名.对象名.变量名.方法名.类型名.数组名.包名的有效字符序列,称为“标识符”: 标识符由字母.数字.下划线.美元符号组成,且第一个字符不能是数字: java语言区分大小写: 标志符命名规则:类名首字母大写,变量名和方法名采用驼峰标志法,常量全大写,多个单词之间用“_”隔开,包名全小写: 关键字 在java语言中,有一些专门的词汇已经被赋予了特殊的含义,不能再使用这些词汇来命名标识符,这些专有词汇,称为“关键字”: j

聊聊React高阶组件(Higher-Order Components)

使用 react已经有不短的时间了,最近看到关于 react高阶组件的一篇文章,看了之后顿时眼前一亮,对于我这种还在新手村晃荡.一切朝着打怪升级看齐的小喽啰来说,像这种难度不是太高同时门槛也不是那么低的东西如今可不多见了啊,是个不可多得的 zhuangbility的利器,自然不可轻易错过,遂深入了解了一番. 概述 高阶组件的定义 React 官网上对高阶组件的定义: 高阶部件是一种用于复用组件逻辑的高级技术,它并不是 React API的一部分,而是从React 演化而来的一种模式. 具体地说,

第23讲: Scala高阶函数实战详解

高阶函数主要有两种: 一.将一个函数当做另外一个函数的参数(即函数参数) 普通函数的定义语法如下: def funName(para1:Type1,para2:Type2):Type = { do some things } 我们想象一下,如果将函数作为参数,那么函数的类型该怎么写呢? 函数的类型,其实就是输入输出的类型. 假设,我们需要将函数ceil作为参数,而ceil的定义如下   def ceil(x: Double): Double  = java.lang.Math.ceil(x) 那