实例变量的懒初始化
时间:2022-05-14
本文章向大家介绍实例变量的懒初始化,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。
今天遇到一个很有趣的问题,由于业务要求,需要懒初始化一个实例变量。
简单方法
很顺手就写出下面的代码。
public class LazyFieldInitializer {
private Object obj = null;
public LazyFieldInitializer(){
}
public void someOp(){
if(obj == null){
obj = new Object();
}
}
public void otherOp(){
}
public static void main(String[] args) {
LazyFieldInitializer instance = new LazyFieldInitializer();
instance.someOp();
}
}
但这种方法存在问题,线程不安全,当两个线程同时调用someOp方法,obj变量被初始化了两次。
加个锁吧
public class LazyFieldInitializer {
private Object obj = null;
public LazyFieldInitializer(){
}
public void someOp(){
synchronized (this) {
if (obj == null) {
obj = new Object();
}
}
}
public void otherOp(){
}
public static void main(String[] args) {
LazyFieldInitializer instance = new LazyFieldInitializer();
instance.someOp();
}
}
这种方法虽说没问题,就是效率不高,每次执行someOp方法都会锁this。
双重校验
好了,来个双重校验吧
public class LazyFieldInitializer {
private Object obj = null;
public LazyFieldInitializer(){
}
public void someOp(){
if (obj == null) {
synchronized (this) {
if(obj == null) {
obj = new Object();
}
}
}
}
public void otherOp(){
}
public static void main(String[] args) {
LazyFieldInitializer instance = new LazyFieldInitializer();
instance.someOp();
}
}
这次没问题了吧。很可惜还是有问题,关键在obj = new Object();
这句,这并非是一个原子操作,事实上在 JVM 中这句话大概做了下面 3 件事情。
- 给obj分配内存
- 调用Object的构造函数来初始化成员变量
- 将obj对象指向分配的内存空间(执行完这步obj就为非null了)
这个就是JVM很有特色的指令重排序优化。也就是说上面的第二步和第三步的顺序是不能保证的,最终的执行顺序可能是 1-2-3 也可能是 1-3-2。如果是后者,则在3执行完毕、2 未执行之前,被另一个线程二抢占了,这时 instance 已经是非 null 了(但却没有初始化),这个线程拿着这个obj引用去干活,自然就会出问题。
规避指令重排序优化的办法
- 使用volatile关键字禁止指令重排序优化
public class LazyFieldInitializer {
private volatile Object obj = null;
public LazyFieldInitializer(){
}
public void someOp(){
if (obj == null) {
synchronized (this) {
if (obj == null) {
obj = new Object();
}
}
}
}
public void otherOp(){
}
public static void main(String[] args) {
LazyFieldInitializer instance = new LazyFieldInitializer();
instance.someOp();
}
}
volatile
关键字在这里有两层含义,一个是禁止JVM对该变量的指令重排序优化,另一个是使这个变量的修改对其它线程可见。
Java单例
查阅JVM的指令重排序优化相关文章,还看到Java单例写法的文章,这里小小总结一下。
/**
* Created by jeremy on 16/6/11.
* 懒汉模式, 线程不安全
*/
public class Singleton1 {
private static Singleton1 instance;
private Singleton1(){
}
public void sayHello(){
System.out.println("hello");
}
public static Singleton1 getInstance(){
if(instance == null){
instance = new Singleton1();
}
return instance;
}
public static void main(String[] args) {
Singleton1.getInstance().sayHello();
}
}
/**
* Created by jeremy on 16/6/11.
* 懒汉模式, 线程安全
*/
public class Singleton2 {
private static Singleton2 instance;
private Singleton2(){
}
public void sayHello(){
System.out.println("hello");
}
public static synchronized Singleton2 getInstance(){
if(instance == null){
instance = new Singleton2();
}
return instance;
}
public static void main(String[] args) {
Singleton2.getInstance().sayHello();
}
}
/**
* Created by jeremy on 16/6/11.
* 饿汉模式, 类变量类加载时初始化, 线程安全
*/
public class Singleton3 {
private static Singleton3 instance = new Singleton3();
private Singleton3(){
}
public void sayHello(){
System.out.println("hello");
}
public static Singleton3 getInstance(){
return instance;
}
public static void main(String[] args) {
Singleton3.getInstance().sayHello();
}
}
/**
* Created by jeremy on 16/6/11.
* 饿汉模式, 类变量类加载时在类的静态初始化块里初始化, 线程安全
*/
public class Singleton4 {
private static Singleton4 instance = null;
static {
instance = new Singleton4();
}
private Singleton4(){
}
public void sayHello(){
System.out.println("hello");
}
public static Singleton4 getInstance(){
return instance;
}
public static void main(String[] args) {
Singleton4.getInstance().sayHello();
}
}
/**
* Created by jeremy on 16/6/11.
* 懒汉模式, 采用内部静态类,线程安全
*/
public class Singleton5 {
private static class Singleton5Holder {
private static final Singleton5 instance = new Singleton5();
}
private Singleton5(){
}
public void sayHello(){
System.out.println("hello");
}
public static Singleton5 getInstance(){
return Singleton5Holder.instance;
}
public static void main(String[] args) {
Singleton5.getInstance().sayHello();
}
}
/**
* Created by jeremy on 16/6/11.
* 懒汉模式, 枚举实现, 线程安全
*/
public enum Singleton6 {
INSTANCE;
Singleton6(){
}
public void sayHello(){
System.out.println("hello");
}
public static void main(String[] args) {
Singleton6.INSTANCE.sayHello();
}
}
/**
* Created by jeremy on 16/6/11.
* 懒汉模式, 双重校验, 采用volatile规避指令重排序优化, 线程安全
*/
public class Singleton7 {
private static volatile Singleton7 instance;
private Singleton7(){
}
public void sayHello(){
System.out.println("hello");
}
public static Singleton7 getInstance(){
if(instance == null){
synchronized (Singleton7.class){
if(instance == null){
instance = new Singleton7();
}
}
}
return instance;
}
public static void main(String[] args) {
Singleton7.getInstance().sayHello();
}
}
总结
Java还是有不少暗坑的,写代码时得小心了。记得大学时看过Java Puzzlers,那上面列举过不少这样的Java陷阱,抽时间要重读一读这本书了。
- idea中使用scala运行spark出现Exception in thread "main" java.lang.NoClassDefFoundError: scala/collection/Gen
- 使用JPA中@Query 注解实现update 操作
- 微信小程序开发教程!博卡君第二弹【微信小程序项目结构以及配置】
- WCF浅尝
- 如何将页面设置为微信端才能打开
- 你不可错过的二维码生成与解析-java后台与前端js都有
- Windows服务创建及安装
- dede自定义表单增加添加时间怎么弄
- nodejs配置简单HTTP服务器
- marquee一行代码实现滚动跑马灯效果无需js
- 如何用SQL命令行工具删除dedecms指定id文章
- 怎么采集dedecms自定义内容模型
- dedecms怎样调用指定id文章?
- c++ list, vector, map, set 区别与用法比较
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- Linux下设置Vim编辑器里Tab的长度行号
- Centos定制rpm包、搭建yum仓库的教程
- linux手动、自动更改网卡MAC地址的方法
- Centos7的Firewalld防火墙基础命令详解
- Linux下安装或升级Python 2.7的操作方法
- Linux中git用https连接时不用每次输入密码的方法
- Centos7.3 RabbitMQ分布式集群搭建示例
- Ubuntu16.04环境下搭建FTP服务器的教程
- Linux 查看空间使用情况的实例详解
- CentOS 6.8 安装vsftpd的方法步骤
- centos7下搭建ZooKeeper3.4中间件常用命令小结
- Linux性能测试 pmap命令详解
- Linux7.7设置交换分区SWAP的方法
- 在Linux里安装和启动nginx的方法
- Linux下如何对ISO文件编辑的方法示例