单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
在单例类首次加载时就创建单例
public class HungerSingleton{
private static final HungerSingleton instance =new HungerSingleton();
private HungerSingleton(){}
public static HungerSingleton getInstance(){
return instance;
}
}
为了解决饿汉式单例可能带来的内存浪费的问题,提出了懒汉式单例。懒汉式单例的特点是:单例对象要在被使用时才会初始化。
public class LazySingleton{
private static LazySingleton instance =null;
private LazySingleton(){}
public static LazySingleton getInstance(){
if(instance==null){
return new LazySingleton();
}
return instance;
}
}
public class LazySingleton{
private static LazySingleton instance =null;
private LazySingleton(){}
public static synchronized LazySingleton getInstance(){
if(instance==null){
return new LazySingleton();
}
return instance;
}
}
缺点
既能解决线程安全又能提升程序性能,提出了双重检查锁
class SynchronizedSingleton{
private static volatile SynchronizedSingleton instance =null;
private SynchronizedSingleton(){}
private static SynchronizedSingleton getInstance(){
if (instance==null){
synchronized (SynchronizedSingleton.class){
if (instance==null){
return new SynchronizedSingleton();
}
}
}
return instance;
}
}
利用了内部类的加载方式:外部类初始化的时候内部类不会加载,只要在调用内部类的方法时才会初始化内部类。
是性能最优的且线程安全的一种懒汉式单例写法
class LazyInnerClassSingleton{
private LazyInnerClassSingleton(){}
public static final LazyInnerClassSingleton getInstance() {
return LazyHoder.instance;
}
//延迟加载
private static class LazyHoder{
private static final LazyInnerClassSingleton instance=new LazyInnerClassSingleton();
}
}
但是,上面这种方法就是完美的么?我们先来看一个测试例子
class LazyInnerClassSingletonTest{
public static void main(String[] args) {
try {
Class<?>clazz=LazyInnerClassSingleton.class;
Constructor c=clazz.getDeclaredConstructor(null);
c.setAccessible(true);//强吻
Object o1=c.newInstance();
Object o2=LazyInnerClassSingleton.getInstance();
System.out.println(o1==o2);
} catch (Exception e) {
e.printStackTrace();
}
}
}
运行结果:
可以看到,我们通过反射来调用其构造方法,和调用getInstance方法,出现了两个不同的实例,这种情况怎么解决呢?
class LazyInnerClassSingleton{
private LazyInnerClassSingleton(){
if(LazyHoder.instance!=null){
throw new RuntimeException("不允许创建多个实例");
}
}
public static final LazyInnerClassSingleton getInstance() {
return LazyHoder.instance;
}
//延迟加载
private static class LazyHoder{
private static final LazyInnerClassSingleton instance=new LazyInnerClassSingleton();
}
}
我们通过构造方法的判断和异常,让调用者只能通过getInstance去拿实例
一个对象创建完成,有时需要将对象序列化后写入磁盘,下次使用再从磁盘读取对象并进行反序列化,将其转成内存对象。反序列化后的对象会重新分配内存,即重新创建。如果序列化的对象是单例对象,就会破坏单例
public class HungerSingleton implements Serializable{
private static final HungerSingleton instance =new HungerSingleton();
private HungerSingleton(){}
public static HungerSingleton getInstance(){
return instance;
}
public static HungerSingleton deepClone(HungerSingleton hs){
HungerSingleton i =null;
try {
//序列化对象
FileOutputStream fo=new FileOutputStream("HungerSingleton.obj");
ObjectOutputStream os=new ObjectOutputStream(fo);
os.writeObject(hs);
os.flush();
os.close();
//反序列化对象
FileInputStream fi=new FileInputStream("HungerSingleton.obj");
ObjectInputStream ois=new ObjectInputStream(fi);
i=(HungerSingleton)ois.readObject();
ois.close();
} catch (Exception e) {
e.printStackTrace();
}
return i;
}
public static void main(String[] args) {
HungerSingleton i=HungerSingleton.getInstance();
HungerSingleton ii=deepClone(i);
System.out.println(i==ii);
}
}
运行结果:
很明显,序列化后的对象和手动创建的对象是不一致的,实例化了两次。那么,怎么去解决这个问题呢?很简单,只需要增加readResolve方法就可以了
public class HungerSingleton implements Serializable{
private static final HungerSingleton instance =new HungerSingleton();
private HungerSingleton(){}
public static HungerSingleton getInstance(){
return instance;
}
//序列化破坏单例的解决方案:
//重写readResolve方法
private Object readResolve(){
return instance;
}
}
注册式单例又被称为登记式单例,就是讲每一个实例都登记到某一个地方,使用唯一标识获取单例
enum EnumSingleton{
INSTANCE;
private Object obj;
private Object getObj(){
return obj;
}
public static EnumSingleton getInstance(){
return INSTANCE;
}
public static void main(String[] args) {
EnumSingleton e1=EnumSingleton.getInstance();
EnumSingleton e2=EnumSingleton.getInstance();
System.out.println(e1==e2);
}
}
枚举法单例模式是《Effective Java》书中推荐的一种单例模式实现写法
前面我们也讲过ThreadLocal,它是单个线程唯一的,天生就是线程安全的
class ThreadLocalSingleton{
private ThreadLocalSingleton(){}
private static final ThreadLocal<ThreadLocalSingleton> instance=new ThreadLocal<ThreadLocalSingleton>(){
@Override
protected ThreadLocalSingleton initialValue() {
return new ThreadLocalSingleton();
}
};
private static ThreadLocalSingleton getInstance(){
return instance.get();
}
}
ThreadLocal单例也算是一种特殊的单例模式吧,对ThreadLocal原理感兴趣的可以去看之前的文章深入理解ThreadLocal
一般建议使用饿汉式单例。只有在要明确实现 lazy loading 效果时,才会使用内部类实现的单例模式。如果涉及到反序列化创建对象时,可以尝试使用第枚举方式。如果有其他特殊的需求,可以考虑使用双重检锁方式。