深入理解static、volatile关键字
static
意思是静态的,全局的。被修饰的东西在一定范围内是共享的,被类的所有实例共享,这时候需要注意并发读写的问题。
只要这个类被加载,Java虚拟机就能根据类名在运行时数据区的方法区内找到他们。所以,static对象可以在他的任何对象创建之前访问,无需引用任何对象。
static可以修饰变量、方法和代码块。
当static修饰类变量的时候,被修饰的变量叫做静态变量或者类变量;如果该变量的访问权限是public的话,表示该变量,可以被任何类调用,无需初始化。直接使用 类名.static变量名这种形式访问即可。
static和final一起修饰的变量可以理解为 “全局常量”,一旦赋值就不可更改,并且可以通过类名访问。对于static final修饰的方法不可以覆盖,并且可以通过类名直接访问。
这时候我们非常需要注意的就是线程安全问题了,当多个线程同时对共享变量进行读写时,很可能会出现并发问题
一般有两种解决方法:
- 把线程不安全的变量,例如修饰的是ArrayList替换成线程安全的CopyOnWriteArrayList;
- 每次访问这个变量,手动加锁。
静态变量和实例变量的区别
对于静态变量,在内存中只有一个拷贝,JVM只为静态分配一次内存,在加载类的过程中完成静态变量的内存分配,可以用类名直接访问,可以用对象来访问(不推荐)。
对于实例变量,每次创建一个实例,JVM就会为其分配一次内存,它在内存中有多个拷贝,互不影响。
当static修饰方法时,该方法内部只能调用static方法,不能调用普通方法,但是普通方法可以调用static方法,也可以在普通方法中访问静态变量。我们常用的util类里面的方法,大多数是被static修饰的方法,在调用的时候很方便。
因为任何实例对象都可以调用静态方法,所以静态方法中不能出现this或者super关键字。
当static修饰代码块时,我们叫做静态代码块或者静态方法块,一个类中可以有多个静态代码块,他不在任何方法的内部,JVM加载类的时候会执行这些静态代码块,如果静态代码块有多个,JVM将按照他们在类中的顺序依次执行他们,每个代码块只会执行一次。他常常在类启动之前初始化一些值。
private static Integer age;
static{
age = 18;
}
初始化时机
对于被static修饰的变量、方法块和方法的时机。
/*子类*/
@Slf4j
public class ChildStaticClass extends ParentStaticClass{
public ChildStaticClass() {
log.info("子类构造方法初始化...");
}
public static List<String> list = new ArrayList(){{log.info("子类静态变量初始化...");}};
static{
log.info("子类静态代码块初始化。。。");
}
public static void testStatic(){
log.info("子类静态方法执行。。。");
}
public static void main(String[] args) {
log.info("main 方法执行");
new ChildStaticClass();
}
}
/*父类代码*/
@Slf4j
public class ParentStaticClass {
public static List<String> list = new ArrayList() {
{
log.info("父类静态变量初始化...");
}
};
static {
log.info("父类静态代码块初始化...");
}
public ParrentStaticClass() {
log.info("父类构造方法初始化...");
}
public static void testStatic(){
log.info("父类静态方法执行...");
}
}
执行子类的main方法,打印结果是:
这里需要注意的是,如果将静态代码块放在静态变量前面,那么先加载静态代码块。这与静态顺序有关。
从结果上看,父类的静态代码块和静态变量比子类优先初始化;
静态变量和静态代码块比类构造器先初始化。
如果父类和子类都还有非静态代码块的话,执行顺序是:
父类静态代码块->子类静态代码块->父类非静态代码块->父类构造方法->子类非静态代码块->子类构造方法
static 总结
静态代码块内容先执行,接着执行父类非静态代码块和构造方法,然后在执行子类非静态代码块和构造方法。
注意
如果父类没有不带参数的构造方法,那么子类必须用super关键字调用父类带参数的构造方法,否则编译不通过。
final
final一般用于以下三种场景
- 被final修饰的类,表名该类是无法继承的。
- 被final修饰的方法,表名该方法是无法复写的;
- 被final修饰的变量是,说明该变量在声明的时候就必须初始化,而且以后不能修改其内存地址。
需要注意的是,无法更改的是内存地址,被final修饰的集合,例如Map,List等,可以修改里面元素的值,但是无法修改初始化时的内存地址。
volatile
Java中的volatile用于将变量标记为“存储在主内存中”,对于volatile变量的每次读操作都会直接从计算机的主内存中读取,同样对于volatile变量的写操作只写入主存,而不是仅仅写入CPU缓存。
可见性的保证
volatile是轻量级的synchronized,他在多处理器中保证共享变量的可见性。可见性的意思是:当一个线程修改一个共享变量时,另一个线程能读到这个修改的值。volatile之所以比synchronized执行成本更低是因为他不需要切换上下文和调度。
当写一个volatile变量的时候,Java内存模型(JMM)会把线程对应的本地内存中的共享变量刷新到主内存中;
当读一个volatile变量时,JMM会把线程对应的本地内存无效化,接下来线程会从主存中读取这个volatile变量。
实现原理
Java代码如下
private volatile Object instance = new Singleton();
通过工具转变成汇编代码(window下下载这个压缩包,解压到你jdk/jre/bin/server下)
https://sourceforge.net/projects/fcml/files/fcml-1.1.1/hsdis-1.1.1-win32-amd64.zip/download
Linux下https://sourceforge.net/projects/fcml/files/fcml-1.1.3/fcml-1.1.3.tar.gz/download
解压,切换目录,
- ./configure && make && sudo make install
- cd example/hsdis && make && sudo make install
- sudo ln -s /usr/local/lib/libhsdis.so /lib/amd64/hsdis-amd64.so
- sudo ln -s /usr/local/lib/libhsdis.so /jre/lib/amd64/hsdis-amd64.so
接下来便可以使用
执行main函数之前需要加上虚拟机参数
-server -Xcomp -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly XX:CompileCommand=compileonly,*类名.方法名
之后有一行指令会有 lock前缀,Lock前缀的指令在多喝处理器下会引发两件事。
当前处理器缓存行的数据写回到系统内存
写回内存的操作会使其他CPU里面缓存了该内存地址的变量无效。
Lock前缀指令导致在执行指令期间,声言处理器的Lock#信号。他在声言信号期间会独占共享内存(直接锁总线,导致其他CPU不能访问总线,不能访问总线就意味着不能访问内存)。但是在最新的处理器,一般不锁总线,锁总线开销会很大,直接锁的是缓存。这个操作被称为“缓存锁定”,缓存一致性机制会阻止同时修改由两个以上的处理器缓存的内存区域数据。
为什么写回内存的数据就会使其他CPU里面的相同内存地址数据的缓存失效呢?
IA-32和Intel64位的处理器会使用MESI(修改,独占,共享,无效)控制协议去维护内部缓存和其他处理器缓存的一致性。这两种处理器会嗅探其他处理器访问系统内存和他们的内部缓存。处理器使用嗅探技术保证内部缓存,其它处理器缓存,系统缓存在总线上是一致的。如果嗅探到一个处理器在写内存地址,而这个地址当前处于共享状态(也就是被volatile修饰),那么正在嗅探的处理器将使他自身的缓存失效,在下次访问相同的内存地址时,强制执行缓存填充。
什么时候使用volatile
如果两个线程都对共享变量进行读写,那么只是用关键字volatile就不能满足要求了。这种情况你需要使用synchronized保证读写变量的原子性。对volatile变量的读写不会阻塞其他线程,如果需要阻塞可以换成synchronized。
如果只有一个线程读写volatile变量,其他线程只读取,那么只读线程一定能看到最新写入到volatile变量的值。
transient
transient关键字是我们常用来修饰类变量的,意思是当前变量是无需进行序列化的。在序列化时,就忽略该变量,这些序列化工具在底层对transient进行了支持。
default
以前的接口中是不能有方法实现的,但是从java8引入default开始,这件事就改变了。default一般用于接口里的方法上,意思是对于该接口,子类无需强制实现该方法,因为有默认的实现。SpringBoot2.x中的一些接口采用了这种实现方式。子类无需实现也可正常使用。
default
public interface DefaultDemo {
default void test(){
//todo something
System.out.println("Hello");
}
}
public class DefautDemoImpl implements DefaultDemo {
}
参考:
Java并发编程的艺术
强烈推荐:https://www.cnblogs.com/xrq730/p/7048693.html
深入理解static、volatile关键字的更多相关文章
- 多线程与高并发(四)volatile关键字
上一篇学习了synchronized的关键字,synchronized是阻塞式同步,在线程竞争激烈的情况下会升级为重量级锁,而volatile是一个轻量级的同步机制. 前面学习了Java的内存模型,知 ...
- Java并发编程学习笔记 深入理解volatile关键字的作用
引言:以前只是看过介绍volatile的文章,对其的理解也只是停留在理论的层面上,由于最近在项目当中用到了关于并发方面的技术,所以下定决心深入研究一下java并发方面的知识.网上关于volatile的 ...
- Java并发专题(三)深入理解volatile关键字
前言 上一章节简单介绍了线程安全以及最基础的保证线程安全的方法,建议大家手敲代码去体会.这一章会提到volatile关键字,虽然看起来很简单,但是想彻底搞清楚需要具备JMM.CPU缓存模型的知识.不要 ...
- 全面理解Java内存模型(JMM)及volatile关键字(转载)
关联文章: 深入理解Java类型信息(Class对象)与反射机制 深入理解Java枚举类型(enum) 深入理解Java注解类型(@Annotation) 深入理解Java类加载器(ClassLoad ...
- 全面理解Java内存模型(JMM)及volatile关键字
[版权申明]未经博主同意,谢绝转载!(请尊重原创,博主保留追究权) http://blog.csdn.net/javazejian/article/details/72772461 出自[zejian ...
- 全面理解Java内存模型(JMM)及volatile关键字(转)
原文地址:全面理解Java内存模型(JMM)及volatile关键字 关联文章: 深入理解Java类型信息(Class对象)与反射机制 深入理解Java枚举类型(enum) 深入理解Java注解类型( ...
- 【java并发】(1)深入理解volatile关键字
volatile这个关键字可能很多朋友都听说过,或许也都用过.在Java 5之前,它是一个备受争议的关键字,因为在程序中使用它往往会导致出人意料的结果.在Java 5之后,volatile关键字才得以 ...
- volatile关键字深入理解
前言: 这个关键字的重点就三个字,就是可见性.但是面试的时候,你说出可见性三个字,基本上满分100的话,最多只能得到20分.剩下的那80分,就要靠你用硬功夫去获得了. 所谓的硬功夫,其实就是要整明白, ...
- 深入理解Java内存模型JMM与volatile关键字
深入理解Java内存模型JMM与volatile关键字 多核并发缓存架构 Java内存模型 Java线程内存模型跟CPU缓存模型类似,是基于CPU缓存模型来建立的,Java线程内存模型是标准化的,屏蔽 ...
- 对Java单例模式 volatile关键字作用的理解
单例模式是程序设计中经常用到的,简单便捷的设计模式,也是很多程序猿对设计模式入门的第一节课.其中最经典的一种写法是: class Singleton { private volatile static ...
随机推荐
- nginx转发上传图片接口图片的时候,报错413
我这边有一个接口是上传图片,使用nginx进行代理,上传大一点的图片,直接调用我的接口不会报错,但是调用nginx上传图片就会报错"413 Request Entity Too Large& ...
- sql server添加单独新用户
- .net5+nacos+ocelot 配置中心和服务发现实现
最近一段时间 因公司业务需要,需要使用.net5做一套微服务的接口,使用nacos 做注册中心和配置中心,ocelot做网关. 因为ocelot 支持的是consol和eureka,如果使用nacos ...
- 高速缓冲存储器Cache
目录 概述 问题的提出 局部性原理 命中与未命中 Cache的命中率 Cache-主存系统的效率 例题 工作原理 地址映射方式(本节最重要) 直接映射 全相联映射 组相联映射 例子 替换策略 例题 写 ...
- 【jenkins】构建工作集
构建工作集,参数化工作任务 1.New Item 2.配置新的工作任务 3.关联测试用例的远程仓库 4.添加任务构建后,触发发送报告信息 5.新建单个测试套件 6.添加触发轮询任务 7.关联测试集 8 ...
- Shell-匹配行及date日期转换
#将指定字符串转化为从1970年1月1日到现在的秒数. date -d '20170506' "+%s" #将1970年1月1日到现在累计的秒数转化为日期 date -d @149 ...
- 多任务-python实现-协程(2.1.11)
多任务-python实现-协程(2.1.11) 23/100 发布文章 qq_26624329 @ 目录 1.概念 2.迭代器 1.概念 协程与子例程一样,协程(coroutine)也是一种程序组件. ...
- Dotnet Core多版本API共存的优雅实现
API升级,新旧版本的API共存,怎么管理呢? 一.前言 最近,单位APP做了升级,同步的,API也做了升级. 升级过程中,出现了一点问题:API升级后,旧API也需要保留,因为有旧的APP还在使 ...
- 神经网络高维互信息计算Python实现(MINE)
论文 Belghazi, Mohamed Ishmael, et al. " Mutual information neural estimation ." Internatio ...
- 【python爬虫】一个简单的爬取百家号文章的小爬虫
需求 用"老龄智能"在百度百家号中搜索文章,爬取文章内容和相关信息. 观察网页 红色框框的地方可以选择资讯来源,我这里选择的是百家号,因为百家号聚合了来自多个平台的新闻报道.首先看 ...