深入理解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 ...
随机推荐
- spark中map和mapPartitions算子的区别
区别: 1.map是对rdd中每一个元素进行操作 2.mapPartitions是对rdd中每个partition的迭代器进行操作 mapPartitions优点: 1.若是普通map,比如一个par ...
- yum源 软件仓库 国外===国内
yum源 软件仓库 国外===国内 国内常见的软件仓库地址/yum源 1阿里云 mirrors.aliyun.com 2清华 如何修改mirrors.aliyun.com a 备份系统当前的yum [ ...
- mac系统下用ssh方式连接git仓库
1.应用程序-终端,键入命令 ssh-keygen -t rsa -C "xxxxx@xxxxx.com" ,后面是你的邮箱地址.一直回车,生成密钥. 2.键入 open ~ ...
- 数据结构与算法——图(游戏中的自动寻路-A*算法)
在复杂的 3D 游戏环境中如何能使非玩家控制角色准确实现自动寻路功能成为了 3D 游戏开 发技术中一大研究热点.其中 A*算法得到了大量的运用,A*算法较之传统的路径规划算法,实时性更高.灵活性更强, ...
- RMAN迁移数据库(不改变文件目录)
1.目标库创建相应目录mkdir -p /u01/app/oracle/oradata/orclmkdir -p /u01/app/oracle/fast_recovery_area/ORCLmkdi ...
- SpringBoot + Mybatis-Plus 实现多数据源简单示例
1. 简介 在单体项目中,经常出现想要访问多个数据源的情况,或者因为某些性能瓶颈,将大数据量的业务表分离到另一个库等情况. 实现多数据源的方案有很多,Mybatis-Plus提供了非常简单的实 ...
- ERROR 1071 (42000): Specified key was too long; max key length is 1000 bytes
这个错误是我在安装ambari平台时,准备为ambari指定mysql数据库时,执行建表语句时遇到的. ERROR 1071 (42000): Specified key was too long; ...
- web服务器专题:tomcat(三)tomcat-user.xml 配置文件
回顾:web服务器专题:tomcat(二)模块组件与server.xml 配置文件 Tomcat管理模块 安装Tomcat后,访问127.0.0.1/8080可以看到这个首页,上图中的三个按钮即为To ...
- [日常摸鱼]JSOI2008最大数
校运会的时候随手抽的题- 一句话题意 维护一个序列,初始为空,要求滋兹: 1.查询这个序列末尾$x$个数的最大值 2.设上一次查询的答案为$t$(如果还没查询$t=0$),在末尾插入一个数$(x+t) ...
- 浅析JavaWeb开发模式:Model1和Model2
一.前言 在学习JavaWeb的过程中,大家都会接触到Model1和Model2,历史的发展过程是Model1 → Model2.那么它们之间有何相同之处和不同之处呢? 二.Model1 Model1 ...