一、单例的含义
单例模式是为确保一个类只有一个实例,并为整个系统提供一个全局访问点的一种模式方法。
从概念中体现出了单例的一些特点:
(1)、在任何情况下,单例类永远只有一个实例存在
(2)、单例需要有能力为整个系统提供这一唯一实例
在计算机系统中,**线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象**常被设计成单例。这些应用都或多或少具有资源管理器的功能。每台计算机可以有若干个打印机,但只能有一个Printer Spooler,以避免两个打印作业同时输出到打印机中。每台计算机可以有若干通信端口,系统应当集中管理这些通信端口,以避免一个通信端口同时被两个请求同时调用。总之,选择单例模式就是为了避免不一致状态,避免政出多头。
正是由于这个特点,单例对象通常作为程序中的存放配置信息的载体,因为它能保证其他对象读到一致的信息。例如在某个服务器程序中,该服务器的配置信息可能存放在数据库或文件中,这些配置数据由某个单例对象统一读取,服务进程中的其他对象如果要获取这些配置信息,只需访问该单例对象即可。这种方式极大地简化了在复杂环境下,尤其是多线程环境下的配置管理,但是随着应用场景的不同,也可能带来一些同步问题。
二、单例实现
1、饿汉式单例
饿汉式单例(G:简单有效安全)是指在方法调用前,实例就已经创建好了。
简单安全,私有化构造方法,私有化一个实例。
public class MySingleton {
private static MySingleton instance = new MySingleton();
private MySingleton(){
}
public static MySingleton getInstance() {
return instance;
}
}
2、懒汉式单例
懒汉式单例是指在方法调用获取实例时才创建实例,因为相对饿汉式显得“不急迫”,所以被叫做“懒汉模式”。不是线程安全的。
public class MySingletonLazy {
private static MySingletonLazy instance = null;
private MySingletonLazy(){}
public static MySingletonLazy getInstance(){
if (instance == null){
instance = new MySingletonLazy();
}
return instance;
}
}
这里实现了懒汉式的单例,但是熟悉多线程并发编程的朋友应该可以看出,在多线程并发下这样的实现是无法保证实例实例唯一的,甚至可以说这样的实现是完全错误的,在实例化需要耗时的情况下,并不能保证创建的是一个实例。
3、线程安全的懒汉式单例
要保证线程安全,我们就得需要使用同步锁机制,下面就来看看我们如何一步步的解决存在线程安全问题的懒汉式单例(错误的单例)。
(1)方法中声明synchronized关键字
出现线程安全问题,是由于多个线程可以同时进入getInstance()方法,那么只需要对该方法进行synchronized的锁同步即可:
public class MySingletonLazySafe {
private static MySingletonLazySafe instance = null;
private MySingletonLazySafe(){}
public synchronized static MySingletonLazySafe getInstance(){
if (instance == null){
instance = new MySingletonLazySafe();
}
return instance;
}
}
问题已经解决了,但是这种实现方式的运行效率会很低。同步方法效率低,那我们考虑使用同步代码块来实现。
(2)同步代码块实现
public class MySingletonLazySafe {
private static MySingletonLazySafe instance = null;
private MySingletonLazySafe(){}
public static MySingletonLazySafe getInstance(){
synchronized (MySingletonLazySafe.class){
if (instance == null){
instance = new MySingletonLazySafe();
}
}
return instance;
}
}
这里的实现能够保证多线程并发下的线程安全性,但是这样的实现将全部的代码都被锁上了,同样的效率很低下。
(3)使用静态内部类实现单例模式
public class MySingletonLazySafeByInnerClass {
private static class MySingletonHandler{
private static MySingletonLazySafeByInnerClass instance = new MySingletonLazySafeByInnerClass();
}
private MySingletonLazySafeByInnerClass(){}
public static MySingletonLazySafeByInnerClass getInstance(){
return MySingletonHandler.instance;
}
}
静态内部类实现的单例在多线程并发下单个实例得到了保证。
(4)使用static代码块实现单例
public class MySingletonLazy {
private static MySingletonLazy instance = null;
private MySingletonLazy(){}
static {
instance = new MySingletonLazy();
}
public static MySingletonLazy getInstance(){
return instance;
}
}
静态代码块中的代码在使用类的时候就已经执行了,所以可以应用静态代码块的这个特性的实现单例设计模式。
(5)使用枚举数据类型实现单例模式
public enum EnumFactory {
singletonFactory;
private MySingletonLazy instance;
private EnumFactory(){
instance = new MySingletonLazy();
}
public MySingletonLazy getInstance(){
return instance;
}
public class MySingletonLazy{
public MySingletonLazy(){}
}
}
引用:
EnumFactory.MySingletonLazy singletonLazy = EnumFactory.singletonFactory.getInstance();
枚举enum和静态代码块的特性相似,在使用枚举时,构造方法会被自动调用,利用这一特性也可以实现单例。但是这样写枚举类被完全暴露了,据说违反了“职责单一原则”,那我们来看看怎么进行改造呢。
(6)完善使用枚举实现单例模式
public class ClassFactory {
private enum MyEnumSingleton{
singletonFactory;
private MySingleton1 instance;
private MyEnumSingleton(){ //枚举类的构造方法在类加载时被实例化
instance = new MySingleton1();
}
public MySingleton1 getInstance(){
return instance;
}
}
public static MySingleton1 getInstance(){
return MyEnumSingleton.singletonFactory.getInstance();
}
}
public class MySingleton1 {
//需要获实现单例的类,比如数据库连接Connection
public MySingleton1(){
}
}
(7)序列化与反序列化的单例模式实现
静态内部类虽然保证了单例在多线程并发下的线程安全性,但是在遇到序列化对象时,默认的方式运行得到的结果就是多例的。
代码实现如下:
public class MySingletonLazySafeByInnerClass implements Serializable {
private static final long serialVersionUID = 1L;
//静态内部类
private static class MySingletonHandler{
private static MySingletonLazySafeByInnerClass instance = new MySingletonLazySafeByInnerClass();
}
private MySingletonLazySafeByInnerClass(){}
public static MySingletonLazySafeByInnerClass getInstance(){
return MySingletonHandler.instance;
}
}
经验证序列号对象的hashCode和反序列化后得到的对象的hashCode值不一样,说明反序列化后返回的对象是重新实例化的,单例被破坏了。
解决办法就是在反序列化的过程中使用readResolve()方法,单例实现的代码如下:
public class MySingletonLazySafeByInnerClass implements Serializable {
private static final long serialVersionUID = 1L;
//静态内部类
private static class MySingletonHandler{
private static MySingletonLazySafeByInnerClass instance = new MySingletonLazySafeByInnerClass();
}
private MySingletonLazySafeByInnerClass(){}
public static MySingletonLazySafeByInnerClass getInstance(){
return MySingletonHandler.instance;
}
protected Object readResolve() throws ObjectStreamException {
System.out.println("调用了readResolve方法! ");
return MySingletonHandler.instance;
}
public static void main(String[] args) {
MySingletonLazySafeByInnerClass safeByInnerClass = MySingletonLazySafeByInnerClass.getInstance();
File file = new File("MySingleton.txt");
try {
FileOutputStream fos = new FileOutputStream(file);
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(safeByInnerClass);
oos.close();
fos.close();
System.out.println(safeByInnerClass.hashCode());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
try {
FileInputStream fis = new FileInputStream(file);
ObjectInputStream ois = new ObjectInputStream(fis);
MySingletonLazySafeByInnerClass rsafeByInnerClass = (MySingletonLazySafeByInnerClass) ois.readObject();
ois.close();;
fis.close();
System.out.println(rsafeByInnerClass.hashCode());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}