Java单例模式的5种实现方法
大家好,又见面了,我是你们的朋友全栈君。
单例模式有5种实现方式:饿汉、懒汉、双重校验锁、静态内部类和枚举
饿汉
类加载的时候就创建了实例 优点:类加载的时候创建一次实例,避免了多线程同步问题
缺点:即使单例没被用到也会创建,浪费内存
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton() { }
public static Singleton getInstance() {
return instance;
}
}
饿汉-变种
public class Singleton {
private static Singleton instance = null;
static {
instance = new Singleton();
}
private Singleton() { }
public static Singleton getInstance() {
return this.instance;
}
}
懒汉-(非线程安全)
优点:需要时才去创建 缺点:没有考虑线程安全问题,多个线程并发调用getInstance,可能会创建多个实例
public class Singleton {
private static Singleton instance = null;
private Singleton() { }
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
懒汉-(线程安全)
缺点:性能问题,添加了synchronized的函数比一般方法慢得多,若多次调用getInstance,则累积的性能损耗特别大。
public class Singleton {
private static Singleton instance = null;
private Singleton() { }
public static Synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
考虑到以上的性能问题,所以又有一种双重校验锁的实现方式:
双重校验锁
大部分情况下,同步代码块都不会执行到,提高了程序的性能。
有一种情况,两个线程ThreadA,ThreadB,如果threadA执行到了第一个if条件判断,instance = null;ThreadB也执行到了if条件判断instance = null,所以A和B会依次执行同步代码块里的代码。为了避免创建两个实例,因此又在同步代码块里添加了if条件进行二重检验。
public class Singleton {
private static Singleton instance = null;
private Singleton() { }
public static Singleton getInstance() {
if (instance == null) {
synchronized(Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
隐患:
1、此处涉及Java的指令重排优化。指令重排优化是指在不改变原语义的情况下,通过调整指令的执行顺序让程序运行地更快。
2、JVM中没有规定编译器优化的相关内容,也即JVM可以自由地进行指令重排序的优化。
3、此问题的关键在于由于指令重排序优化的存在,导致初始化Singleton和将对象地址赋给instance字段的顺序是不确定的。
4、在某个线程创建单例对象时,在构造函数被调用前,就为该对象分配了内存空间并将对象的字段设置为默认值。此时就可以将分配的内存地址赋值给instance字段了,然而该对象可能还没初始化。若紧接着另一个线程来调用getInstance,获取到的就是状态不正确的对象,程序出错。
JDK5的修正:以上是双重校验锁失效的原因,不过在JDK1.5之后的版本添加了volatile关键字。
1、volatile的一个语义是禁止指令重排序优化,也就保证了instance变量被赋值的时候对象已经是初始化过的,从而避免了上述问题。
2、Java中的volatile变量是什么?
(1)关键字的作用有两个:
①多线程主要围绕可见性和原子性两个特性展开,使用volatile关键字修饰的变量,保证了其在多线程之间的可见性,即每次读取到的volatile变量,一定是最新的数据。
②代码底层执行的顺序是Java代码–>字节码–>根据字节码执行对应的C/C++代码–>C/C++代码被编译成汇编语言–>和硬件电路交互。实际中,为了获取更好的性能,JVM可能会对指令进行重排序,多线程下可能会出现一些意想不到的问题。使用volatile则会禁止语义重排序,也一定程度上降低了代码执行效率。实践角度而言,volatile的一个重要作用就是和CAS结合,保证了原子性。
(2)volatile是一个特殊的修饰符,只有成员变量才能使用它。在Java并发程序缺少同步类的情况下,多线程对成员变量的操作对其他线程是透明的。volatile变量可以保证下一个读取操作会在前一个写操作之后发生。
代码如下:
public class Singleton {
private static volatile Singleton instance = null;
private Singleton() { }
public static Singleton getInstance() {
if (instance == null) {
synchronized(Singleton.class) {
if (instance == null){
instance = new Singleton();
}
}
}
return instance;
}
}
静态内部类
public class StaticSingleton {
private StaticSingleton() {}
private static class SingletonHolder {
private static StaticSingleton INSTANCE = new StaticSingleton();
}
public static StaticSingleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
枚举
public enum Singleton {
INSTANCE;
public void xx(){
}
}
既能避免多线程同步问题,又能防止反序列化重新创建新的对象。
参考链接:
https://blog.csdn.net/fly910905/article/details/79286680
http://www.blogjava.net/kenzhh/archive/2016/03/28/357824.html
发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/156818.html原文链接:https://javaforall.cn
相关文章
- java 四舍五入运算_JAVA正确的四舍五入方法「建议收藏」
- Java连接MySQL mysql-connector-java-bin.jar驱动包的下载与安装
- java判断一个对象是否为空_Java中判断对象是否为空的方法的详解
- protostuff java_Protostuff一键序列化工具、Protobuf JAVA实现
- Java实现单例模式的9种方法
- java单例模式——详解JAVA单例模式及8种实现方式
- java验证手机号正则表达式_Java使用正则表达式验证手机号和电话号码的方法「建议收藏」
- ringbuffer java例子_Java RingBuffer.publish方法代碼示例「建议收藏」
- java的栈内存和堆内存_Java本地方法栈
- Java方法重载_java入门方法的使用
- java prototype是什么,Java设计模式之原型模式(Prototype模式)介绍
- JavaEE13 - Java方法
- Java学习笔记之二java标识符命名规范详解编程语言
- Java之所有对象的公用方法>8.Obey the general contract when overriding equals详解编程语言
- 实现Java程序操作MySQL数据库(java调用mysql)
- 深入Java:利用API快速创建MySQL表(java创建mysql表)
- 利用Redis存储Java对象的方法(redis存储java对象)
- Java Set.add()方法:向Set集合添加对象
- Java DriverManager.getDriver()方法:选择一个适当的驱动程序
- 解决Linux下重启Java程序的方法(linux重启java)
- 玩转Linux:Java开发入门指南(linux上开发java)
- 时间解决Redis在Java中设置过期时间的方法(redisjava过期)
- key处理Java处理Redis过期Key的方法(redisjava过期)
- 时间处理Java对Redis设置过期时间的方法(redisjava过期)
- 深入浅出 使用 Java 连接 Neo4j(java连接neo4j)
- Java应用在Linux上乱码的原因及解决方法(java linux乱码)
- java读写文件[多种方法]
- java判断两个时间是不是同一天的方法