java设计模式-单例模式

原文链接:https://liushiming.cn/2020/03/01/java-design-pattern-singleton/

概述

单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

特点:

  1. 单例类只能有一个实例。
  2. 单例类必须自己创建自己的唯一实例。
  3. 单例类必须给所有其他对象提供这一实例。

线程不安全单例

饿汉模式

优点:

  • 实现简单
  • 线程安全

缺点:

  • 可能造成资源浪费,即使不使用也会占用内存,特别是实例比较大的时候

适用场景:
只有在初始化类的成本较低或程序总是需要类的实例时才使用

    public final class EagerSingleton {
        
        private static EagerSingleton singObj = new EagerSingleton();
 
        private EagerSingleton() {
        }
 
        public static EagerSingleton getSingleInstance() {
            return singObj;
        }
    }

懒汉模式

优点:

  • 对象仅在需要时被创建,无内存和cpu的浪费

缺点:

  • 非线程安全,多个线程同时走到if (null == singObj )就会创建多个实例

适用场景:
非多线程环境

    public final class LazySingleton {
 
        private static LazySingleton singObj = null;
 
        private LazySingleton() {
        }
 
        public static LazySingleton getSingleInstance() {
            if (null == singObj ) {
                singObj = new LazySingleton();
            }
            return singObj;
        }
    }

线程安全单例

线程安全单例测试方法,新建多个线程同时实例化单例类,看hashCode是否一致:

class MyThread extends Thread {

    @Override
    public void run() {
        System.out.println(Singleton.getInstance().hashCode());
    }

    public static void main(String[] args) {

        MyThread[] mts = new MyThread[10];
        for (int i = 0; i < mts.length; i++) {
            mts[i] = new MyThread();
        }

        for (int j = 0; j < mts.length; j++) {
            mts[j].start();
        }
    }
}

同步方法

优点:

  • 对象仅在需要时被创建,无内存和cpu的浪费
  • 线程安全,因为给方法加了Synchronized同步锁

缺点:

  • 方法上加锁大幅限制了多线程的效率

适用场景:
不建议使用

public final class ThreadSafeSingleton  {
    private static ThreadSafeSingleton singObj = null;

    private ThreadSafeSingleton() {
    }

    public static synchronized ThreadSafeSingleton getInstance() {
        if (null == singObj) {
            singObj = new ThreadSafeSingleton();
        }
        return singObj;
    }
}

同步代码块、双检查

优点:

  • 对象仅在需要时被创建,无内存和cpu的浪费
  • 线程安全。因为给需要加锁的代码块加了Synchronized同步锁,且代码块中有双检查

缺点:

  • 代码略繁琐

适用场景:
基本上都适用,是一种较优的单例模式实现

class DoubleCheckedSingleton {
    private static DoubleCheckedSingleton singObj = null;

    private DoubleCheckedSingleton() {
    }

    public static DoubleCheckedSingleton getInstance() {
        if (null == singObj) {
            synchronized (DoubleCheckedSingleton.class) {
                if (null == singObj) {
                    singObj = new DoubleCheckedSingleton();
                }
            }
        }
        return singObj;
    }

}

内部类

优点:

  • 对象仅在需要时被创建,无内存和cpu的浪费
  • 线程安全
  • 代码简洁

缺点:

  • 静态内部类虽然保证了单例在多线程并发下的线程安全性,但是在遇到序列化对象时,默认的方式运行得到的结果是多例的。

适用场景:
不涉及序列化与反序列化的场景

    public class Singleton {
        private static class SingletonHolder {
            public final static Singleton instance = new Singleton();
        }
 
        public static Singleton getInstance() {
            return SingletonHolder.instance;
        }
    }

静态代码块

优点:

  • 静态代码块在使用类的时候被执行一次 ,且仅执行一次,实现了延迟实例化
  • 线程安全

缺点:

  • 静态内部类虽然保证了单例在多线程并发下的线程安全性,但是在遇到序列化对象时,默认的方式运行得到的结果是多例的。

适用场景:
推荐

public class MySingleton{
	 
	private static MySingleton instance = null;
	 
	private MySingleton(){}
 
	static{
		instance = new MySingleton();
	}
	
	public static MySingleton getInstance() { 
		return instance;
	} 
}

序列化与反序列化单例问题

在不涉及序列化与反序列化的场景中,以上线程安全的单例类实现都没有问题,但是在反序列化时,默认得到的结果是多例的

单例实现:

    public class Singleton {
        private static class SingletonHolder {
            public final static Singleton instance = new Singleton();
        }
 
        public static Singleton getInstance() {
            return SingletonHolder.instance;
        }
    }

测试代码:

public class SingletonTest {

    public static void main(String[] args) {
        Singleton singleton = Singleton.getInstance();

        File file = new File("MySingleton.txt");

        try {
            FileOutputStream fos = new FileOutputStream(file);
            ObjectOutputStream oos = new ObjectOutputStream(fos);
            oos.writeObject(singleton);
            fos.close();
            oos.close();
            System.out.println(singleton.hashCode());
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

        try {
            FileInputStream fis = new FileInputStream(file);
            ObjectInputStream ois = new ObjectInputStream(fis);
            Singleton rSingleton = (Singleton) ois.readObject();
            fis.close();
            ois.close();
            System.out.println(rSingleton.hashCode());
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

    }
}

结果:

1023892928
193064360

从结果中我们发现,序列号对象的hashCode和反序列化后得到的对象的hashCode值不一样,说明反序列化后返回的对象是重新实例化的,单例被破坏了。那怎么来解决这一问题呢?

解决办法就是在反序列化的过程中使用readResolve()方法,单例实现的代码如下:

class Singleton implements Serializable {
    private static final long serialVersionUID = 1L;

    private static class SingletonHolder {
        public final static Singleton instance = new Singleton();
    }

    public static Singleton getInstance() {
        return SingletonHolder.instance;
    }

    protected Object readResolve() throws ObjectStreamException {
        System.out.println("调用了readResolve方法!");
        return getInstance();
    }
}

结果:

1023892928
调用了readResolve方法!
1023892928

对于Serializable和Externalizable类,readResolve方法允许类在将从流中读取的对象返回给调用者之前替换/解析它。通过实现readResolve方法,一个类可以直接控制被反序列化的自身实例的类型和实例。

参考资料

高并发下线程安全的单例模式
jdk文档-The readResolve Method
菜鸟教程-单例模式

posted @ 2020-03-01 16:21  huahuayu  阅读(299)  评论(0编辑  收藏  举报