单例模式
单例模式限制了一个类的实例化,并确保java虚拟机中只存在一个类的实例。
单例类必须提供一个全局访问点来获取类的实例。
单例模式用于日志记录,驱动程序对象,缓存和线程池。
Singleton设计模式也用于其他设计模式,如Abstract Factory,Builder,Prototype,Facade等。
要点
私有的构造函数
公共静态方法,返回类的实例,这是外部世界获取单例类的实例的全局访问点。
一般单例模式
package com.ice.singleton;
/**
* Created by yuanbing on 2018/3/20.
*/
public class EagerInitializedSingleton {
private EagerInitializedSingleton() {
}
private static final EagerInitializedSingleton instance = new EagerInitializedSingleton();
public static EagerInitializedSingleton getInstance(){
return instance;
}
}
这种在初始化时,Singleton类的实例是在类加载时创建的,这是创建单例类的最简单的方法,但它有一个缺点即使客户端应用程序可能没有使用它也会创建实例。
静态块初始化
public class StaticBlockSingleton {
private static StaticBlockSingleton instance;
private StaticBlockSingleton() {
}
static {
try {
instance = new StaticBlockSingleton();
} catch (Exception e){
throw new RuntimeException("there is an exception", e);
}
}
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;
}
}
上面的实现对单线程环境工作良好,但是当涉及到多线程系统时,如果多个线程同时处于if循环内部,则会导致问题。它会破坏单例模式,两个线程都会得到单例类的不同实例。在下一节中,我们将看到创建线程安全单例类的不同方法。
线程安全的单例模式
public class ThreadSafeSingleton {
private static ThreadSafeSingleton instance;
private ThreadSafeSingleton(){}
// public static synchronized ThreadSafeSingleton getInstance(){
// if(instance == null){
// instance = new ThreadSafeSingleton();
// }
// return instance;
// }
public static synchronized ThreadSafeSingleton getInstance(){
if(instance == null){
instance = new ThreadSafeSingleton();
}
return instance;
}
}
这种方式实现了线程安全的单例模式,但是在高并发场合使用时性能很糟糕。所以不推荐在高并发环境中使用。
Bill Pugh Singleton实现
-----
在Java 5之前,Java内存模型存在很多问题,以上方法在某些情况下失败,因为太多的线程试图同时获得Singleton类的实例。所以Bill Pugh提出了一种使用内部静态帮助类来创建Singleton类的另一种方法。Bill Pugh Singleton的实现是这样的;
public class BillPughSingleton {
private BillPughSingleton(){};
private static class SingletonHelper{
private static final BillPughSingleton INSTANCE = new BillPughSingleton();
}
public static BillPughSingleton getInstance(){
return SingletonHelper.INSTANCE;
}
}
注意包含单例类的实例的私有内部静态类。加载单例类时,SingletonHelper类不会加载到内存中,只有当有人调用getInstance方法时,才会加载此类,并创建Singleton类实例。这是Singleton类最广泛使用的方法,因为它不需要同步。
使用反射破坏单例模式
import java.lang.reflect.Constructor;
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());
}
}
Enum Singleton
public enum EnumSingleton {
INSTANCE;
public static void doSomething(){
//do something
}
}
为了克服Reflection的这种情况,Joshua Bloch建议使用Enum来实现Singleton设计模式,因为Java确保任何枚举值在Java程序中仅实例化一次。由于Java Enum值是全局访问的,单例也是全局可访问的。缺点是枚举类型有点不灵活; 例如,它不允许延迟初始化
序列化和单例
有时在分布式系统中,我们需要在Singleton类中实现Serializable接口,以便可以将它的状态存储在文件系统中,并在稍后的时间点检索它。
public class SerializedSingleton implements Serializable {
private static final long serialVersionUID = -7604766932017737115L;
private SerializedSingleton(){}
private static class SingletonHelper{
private static final SerializedSingleton instance = new SerializedSingleton();
}
public static SerializedSingleton getInstance(){
return SingletonHelper.instance;
}
}
public class SingletonSerializedTest {
@Test
public void test() throws IOException, ClassNotFoundException {
SerializedSingleton instanceOne = SerializedSingleton.getInstance();
ObjectOutput out = new ObjectOutputStream(
new FileOutputStream("filename.ser"));
out.writeObject(instanceOne);
out.close();
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());
}
}
instanceOne hashCode = 895328852
instanceTwo hashCode = 1922154895
运行上述测试代码会发现,犯序列化的两个对象不是同一个对象,为了解决这个问题我们需要做的就是提供readResolve()方法的实现。
protected Object readResolve() {
return getInstance();
}
参考资料
https://www.journaldev.com/1377/java-singleton-design-pattern-best-practices-examples
《java高并发程序设计》