什么是单例模式?
1、一个类仅生成一个实例,作为大家共有的资源。
<span style="font-size: 18px; font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);">2、单例模式限制类的实例化和保证类只有一个实例存在于Java虚拟机。单例类必须提供获得该类的实例的全局访问点。单例模式用于测井,驱动对象,缓存和线程池。</span>
3、Singleton设计模式也被用在其他的设计模式,抽象工厂,生成器,原型,立面等单件设计模式是用于核心Java类也,例如java.lang.runtime,java.awt.desktop。
单例模式实现主要的几种方式?
单例模式的实现方式有很多,主要的实现如下:
1、饿汉式
2、懒汉式
3、静态初始化方式(饿汉加强)
4、枚举方式
5、双重锁定(懒汉加强)
6、静态内部类的方式
饿汉式实现
public class EagerInitializedSingleton {
private static final EagerInitializedSingleton instance = new EagerInitializedSingleton();
//private constructor to avoid client applications to use constructor
private EagerInitializedSingleton(){}
public static EagerInitializedSingleton getInstance(){
return instance;
}
}
饿汉式单例模式,在系统初始化的时候就加载单例对象
缺点:1、如果单例为资源或者文件系统,我们应该避免在客户端调用getInstance方法前就实例化。(若实例话)
2、没有提供任何的异常处理的选项。
静态初始化单例(饿汉式加强)
public class StaticBlockSingleton {
private static StaticBlockSingleton instance;
private StaticBlockSingleton(){}
//static block initialization for exception handling
static{
try{
instance = new StaticBlockSingleton();
}catch(Exception e){
throw new RuntimeException("Exception occured in creating singleton instance");
}
}
public static StaticBlockSingleton getInstance(){
return instance;
}
}
说明:静态初始化单例,是饿汉式单例模式的加强。通过在静态初始化块中进行,拥有了异常捕获的选择。
缺点:如饿汉式一样,系统一初始化就加载单例。
懒汉式实现
public class LazyInitializedSingleton {
private static LazyInitializedSingleton instance;
private LazyInitializedSingleton() {
}
public static LazyInitializedSingleton getInstance() {
if (instance == null) {
instance = new LazyInitializedSingleton();
}
return instance;
}
}
说明:懒汉式单例模式,单例在初始化时不进行实例,等有客户端调用getInstance方法时,判断是否有实例,没有创建实例,若存在实例则返回该实例对象。
缺点:1、适合在单线程中使用该方式,若存在多线程环境的话,会造成多单例的情况。
双重校验锁(懒汉式加强)
public class ThreadSafeSingleton {
private static ThreadSafeSingleton instance;
private ThreadSafeSingleton(){}
public static ThreadSafeSingleton getInstanceUsingDoubleLocking(){
if(instance == null){
synchronized (ThreadSafeSingleton.class) {
if(instance == null){
instance = new ThreadSafeSingleton();
}
}
}
return instance;
}
}
说明:该方式是在懒汉式的基础上进行多线程优化的实现。
缺点:由于是在懒汉式的基础上做的,加强了多线程的操作。将多线程同步操作的同时。每次都要避免这种额外的开销,双重检查锁定原理应用。
在这种方法中,同步块内用如果确保创建只有一个单例类实例的一个额外的检查条件。
枚举实现
public enum EnumSingleton {
INSTANCE;
public static void doSomething(){
//do something
}
}
<span style="font-family: Arial, Helvetica, sans-serif; font-size: 14px; background-color: rgb(255, 255, 255);">分析:这种方式是Effective Java作者Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象,</span>
可谓是很坚强的壁垒啊,不过,个人认为由于1.5中才加入enum特性。
静态内部类实现
public class BillPughSingleton {
private BillPughSingleton(){}
private static class SingletonHelper{
private static final BillPughSingleton INSTANCE = new BillPughSingleton();
}
public static BillPughSingleton getInstance(){
return SingletonHelper.INSTANCE;
}
}
说明:该方式通过内部类的方式实现单例的。
优势:1、由于试用了内部类的方式,不试用的时候不进行加载。
2、jvm在初始化静态变量的时候仅初始化一次,因此线程安全。
通过反射的方式破坏单例
public class ReflectionSingletonTest {
public static void main(String[] args) {
EagerInitializedSingleton instanceOne = EagerInitializedSingleton.getInstance();
EagerInitializedSingleton instanceTwo = null;
try {
Constructor[] constructors = EagerInitializedSingleton.class.getDeclaredConstructors();
for (Constructor constructor : constructors) {
//Below code will destroy the singleton pattern
constructor.setAccessible(true);
instanceTwo = (EagerInitializedSingleton) constructor.newInstance();
break;
}
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(instanceOne.hashCode());
System.out.println(instanceTwo.hashCode());
}
}
方式:
1、通过反射的方式获得类类型
Constructor[] constructors = EagerInitializedSingleton.class.getDeclaredConstructors();
2、 将private类型构造方法设置为可操作的。
constructor.setAccessible(true);
3、创造单例的实例对象
面对反射的破坏,如何抵御?
instanceTwo = (EagerInitializedSingleton) constructor.newInstance();
如果借助AccessibleObject.setAccessible方法,通过反射机制调用私有构造器,抵御这种方法可以在创建第二次构造器时抛出异常来解决。
//自定义异常
public class InitializationException extends RuntimeException {
private static final long serialVersionUID = 1L;
public InitializationException(String msg) {
super(msg);
}
}
public class Singleton {
private static int count = 0;
private static final Singleton INSTANCE = new Singleton();
private Singleton() {
if(count == 1)
throw new InitializationException("只能初始化一次!");
count++;
}
public static Singleton getInstance() {
return INSTANCE;
}
}
测试类如下:
public class Test {
public static void main(String[] args) throws Exception{
Singleton s1 = Singleton.getInstance();
Singleton s2 = Singleton.getInstance();
System.out.println(s1 == s2); // 返回 true
// 试图通过反射机制创建实例
for(Constructor<?> c : s1.getClass().getDeclaredConstructors()) {
c.setAccessible(true); // AccessibleObject
Singleton s3 = (Singleton)c.newInstance();
}
}
}
测试结果:
Exception in thread "main" java.lang.reflect.InvocationTargetException
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(Unknown Source)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(Unknown Source)
at java.lang.reflect.Constructor.newInstance(Unknown Source)
at org.reflect.Test.main(Test.java:17)
Caused by: org.reflect.InitializationException: 只能初始化一次!
at org.reflect.Singleton.<init>(Singleton.java:10)
... 5 more
当尝试反射的方式再一次初始化时,由于计数器在判断第二次进入构造方法是进行判断,计数器数量大于1则抛出自定义异常。
public class SingletonSerializedTest {
public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
SerializedSingleton instanceOne = SerializedSingleton.getInstance();
ObjectOutput out = new ObjectOutputStream(new FileOutputStream(
"filename.ser"));
out.writeObject(instanceOne);
out.close();
//deserailize from file to object
ObjectInput in = new ObjectInputStream(new FileInputStream(
"filename.ser"));
SerializedSingleton instanceTwo = (SerializedSingleton) in.readObject();
in.close();
System.out.println("instanceOne hashCode="+instanceOne.hashCode());
System.out.println("instanceTwo hashCode="+instanceTwo.hashCode());
}
}
方式说明
由于实现了Serializable接口,通过将数据流的方式重新将生成一个新的单例对象。
解决方法方式:
在单例类中实现readResolve方法。
protected Object readResolve() {
return getInstance();
}
原因:在实现反序列化的时候,ObjectInputStream 会检查对象的class是否定义了readResolve方法。如果定义了,将由readResolve方法指定返回的对象。