public enum Singleton{
instance;
public void whateverMethod(){}
}
【JAVA技术应用】—— 单例模式
各位双体的小伙伴们,
今天同样为大家整理了
满满的干货小知识
跟随我来一起了解下吧!
饿汉式(线程安全)
懒汉式(线程安全)
静态内部类(线程安全)
双重检验锁
枚举单例
Java中单例(Singleton)模式是一种广泛使用的设计模式。单例模式的主要作用是保证在Java程序中,某个类只有一个实例存在。一些管理器和控制器常被设计成单例模式。
单例模式有很多好处,它能够避免实例对象的重复创建,不仅可以减少每次创建对象的时间开销,还可以节约内存空间;能够避免由于操作多个实例导致的逻辑错误。如果一个对象有可能贯穿整个应用程序,而且起到了全局统一管理控制的作用,那么单例模式也许是一个值得考虑的选择。
单例模式有很多种写法,大部分写法都或多或少有一些不足。下面将分别对这几种写法进行介绍。
public class Singleton{
private static final Singleton instance = new Singleton();
private Singleton(){}
public static Singleton newInstance(){
return instance;
}
}
从代码中我们看到,类的构造函数定义为private的,保证其他类不能实例化此类,然后提供了一个静态实例并返回给调用者。
饿汉模式是最简单的一种实现方式,饿汉模式在类加载的时候就对实例进行创建,实例在整个程序周期都存在。它的好处是只在类加载的时候创建一次实例,不会存在多个线程创建多个实例的情况,避免了多线程同步的问题。它的缺点也很明显,即使这个单例没有用到也会被创建,而且在类加载之后就被创建,内存就被浪费了。
这种实现方式适合单例占用内存比较小,在初始化时就会被用到的情况。但是,如果单例占用的内存比较大,或单例只是在某个特定场景下才会用到,使用饿汉模式就不合适了,这时候就需要用到懒汉模式进行延迟加载。
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
懒汉模式中单例是在需要的时候才去创建的,如果单例已经创建,再次调用获取接口将不会重新创建新的对象,而是直接返回之前创建的对象。如果某个单例使用的次数少,并且创建单例消耗的资源较多,那么就需要实现单例的按需创建,这个时候使用懒汉模式就是一个不错的选择。
这种写法能够在多线程中很好的工作,而且看起来它也具备很好的lazy loading,但是,遗憾的是,效率很低,99%情况下不需要同步。
public class Singleton {
private Singleton(){
}
public static Singleton getSingleton(){
return Inner.instance;
}
private static class Inner {
private static final Singleton instance = new Singleton();
}
}
这种方式同样利用了类加载机制来保证只创建一个instance实例。它与饿汉模式一样,也是利用了类加载机制,因此不存在多线程并发的问题。不一样的是,它是在内部类里面去创建对象实例。这样的话,只要应用中不使用内部类,JVM就不会去加载这个单例类,也就不会创建单例对象,从而实现懒汉式的延迟加载。也就是说这种方式可以同时保证延迟加载和线程安全。
实现代码简洁。和双重检查单例对比,静态内部类单例实现代码真的是太简洁,又清晰明了。
延迟初始化。调用getSingleton才初始化Singleton对象。
线程安全。JVM在执行类的初始化阶段,会获得一个可
以同步多个线程对同一个类的初始化的锁。
public class Singleton {
private volatile static Singleton singleton; //1:volatile修饰
private Singleton (){}
public static Singleton getSingleton() {
if (singleton == null) { //2:减少不要同步,优化性能
synchronized (Singleton.class) { // 3:同步,线程安全
if (singleton == null) {
singleton = new Singleton(); //4:创建singleton 对象
}
}
}
return singleton;
}
}
延迟初始化。和懒汉模式一致,只有在初次调用静态方法getSingleton,才会初始化signleton实例。
性能优化。同步会造成性能下降,在同步前通过判读singleton是否初始化,减少不必要的同步开销。
线程安全。同步创建Singleton对象,同时注意到静态变量singleton使用volatile修饰。
为什么要使用volatile修饰?
虽然已经使用synchronized进行同步,但在第4步创建对象时,会有下面的伪代码:
memory=allocate(); //1:分配内存空间
ctorInstance(); //2:初始化对象
singleton=memory; //3:设置singleton指向刚排序的内存空间
当线程A在执行上面伪代码时,2和3可能会发生重排序,因为重排序并不影响运行结果,还可以提升性能,所以JVM是允许的。如果此时伪代码发生重排序,步骤变为1->3->2,线程A执行到第3步时,线程B调用getsingleton方法,在判断singleton==null时不为null,则返回singleton。但此时singleton并还没初始化完毕,线程B访问的将是个还没初始化完毕的对象。当声明对象的引用为volatile后,伪代码的2、3的重排序在多线程中将被禁止!
volatile如何实现防止指令重排序的呢?volatile是否还有别的作用的,有兴趣的同学可以去了解一下
上面提到的四种实现单例的方式都有共同的缺点:
1.需要额外的工作来实现序列化,否则每次反序列化一个序列化的对象时都会创建一个新的实例。
2.可以使用反射强行调用私有构造器(如果要避免这种情况,可以修改构造器,让它在创建第二个实例的时候抛异常)。
而枚举类很好的解决了这两个问题,使用枚举除了线程安全和防止反射调用构造器之外,还提供了自动序列化机制,防止反序列化的时候创建新的对象。
因此《Effective Java》作者推荐使用的方法。不过,在实际工作中,很少看见有人这么写。
高手之间的过招,必选择枚举单例模式。
下面附上测试枚举类解决序列化和反射问题的代码块,有兴趣的同学可以跟源码看一下为什么枚举类能解决此问题
// Copyright 2016-2101 Pica.
package com.pica.cloud.base.api.workstation.server.service;
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
/**
* @ClassName SingleTon
* @Description TODO
* @Author Chongwen.jiang
* @Date 2020/5/10 10:46
* @ModifyDate 2020/5/10 10:46
* @Version 1.0
*/
public class SingleTon implements Serializable{
private SingleTon(){}
private volatile static SingleTon instance;
private String content;
public static SingleTon getInstance(){
if(instance == null) {
synchronized (SingleTon.class) {
if(instance == null) {
instance = new SingleTon();
}
}
}
return instance;
}
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, IOException, ClassNotFoundException {
SingleTon s1 = SingleTon.getInstance();
s1.setContent("内容答复");
/*SingleTon s2 = SingleTon.getInstance();
Constructor<SingleTon> constructor = SingleTon.class.getDeclaredConstructor();
constructor.setAccessible(true);
SingleTon s3 = constructor.newInstance();
System.out.println(s1 == s2);
System.out.println(s1 == s3);*/
ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream("SingleTon.obj"));
os.writeObject(s1);
os.flush();
os.close();
FileInputStream is = new FileInputStream("SingleTon.obj");
ObjectInputStream oss = new ObjectInputStream(is);
SingleTon s4 = (SingleTon)oss.readObject();
oss.close();
is.close();
System.out.println(s4.getContent());
System.out.println(s1 == s4);
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
}
// Copyright 2016-2101 Pica.
package com.pica.cloud.base.api.workstation.server.service;
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
/**
* @ClassName EnumSingleTon
* @Description TODO
* @Author Chongwen.jiang
* @Date 2020/5/10 11:17
* @ModifyDate 2020/5/10 11:17
* @Version 1.0
*/
public enum EnumSingleTon implements Serializable {
INSTANCE;
EnumSingleTon(){}
private String content;
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, IOException, ClassNotFoundException {
EnumSingleTon s1 = EnumSingleTon.INSTANCE;
s1.setContent("内容答复");
EnumSingleTon s2 = EnumSingleTon.INSTANCE;
Constructor<EnumSingleTon> constructor = EnumSingleTon.class.getDeclaredConstructor(String.class, int.class);
constructor.setAccessible(true);
EnumSingleTon s3 = constructor.newInstance();
System.out.println(s1 == s2);
System.out.println(s1 == s3);
/*ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream("EnumSingleTon.obj"));
os.writeObject(s1);
os.flush();
os.close();
FileInputStream is = new FileInputStream("EnumSingleTon.obj");
ObjectInputStream oss = new ObjectInputStream(is);
EnumSingleTon s4 = (EnumSingleTon)oss.readObject();
oss.close();
is.close();
System.out.println(s4.getContent());
System.out.println(s1 == s4);*/
}
}
编辑:郭宇璐
终审:任广一