为什么会有单列设计模式?

当应用程序中创建了多个实例时可能会造成资源的浪费。并且多次调用多个实例操作数据时容易造成结果错误。而单例模式能保证应用程序中有且只有一个实例。可以保证一个类在内存中的对象的唯一性,在一些常用的工具类、线程池、缓存、数据库等程序中可能只允许我们创建一个对象。

单例模式的设计思想

单例模式的关键在于保证应用程序中有且仅有一个对象,如何保证只有一个对象呢?其实只需要三步就可以保证对象的唯一性:

(1)不允许其他类new对象

(2)在本类中创建对象

(3)对外提供一个可以让其他类获取该对象方法

将上面步骤转化为代码描述为:

(1)私有化本类的构造方法

(2)通过new关键字在本类中创建一个本类对象

(3)定义一个公有方法,将在本类中创建的对象返回

单例模式的java代码实现:

单例模式可以分为两大类:饿汉式、懒汉式。

饿汉式和懒汉式的区别:

  • 饿汉式:指全局的单例实例在类装载时构建。

  • 懒汉式:指全局的单例实例在第一次被使用时构建。

单例模式的写法大致可以分为5类:懒汉式、饿汉式、双重校验锁、静态内部类、枚举。

单例模式的饿汉式(可用)

public class Singleton{
......
private static Singleton singleton = new Singleton();

private Singleton(){};

public static Singleton getSingleton(){
return singleton;
}
......
}

//访问方式
Singleton singleton = Singleton.getSingleton();

优点:

实现简单,在类加载时就完成了实例化,避免了线程同步问题。

缺点:

由于在类加载时就完成实例化,所以没有达到(Lazy Loading)懒加载的效果,也就是说可能我没有用到这个实例它也会创建,会造成内存浪费(但是这个浪费可以忽略,所以也是推荐使用的)。

单例模式的饿汉式变换写法(可用)

public class Singleton{
......
private static Singleton singleton = null;

static{
singleton = new Singleton();
}

private Singleton(){};

public static Singleton getSingleton(){
return singleton;
}
......
}

//访问方式
Singleton singleton = Singleton.getSingleton();

其实这种写法和上一种写法一样,都是在类初始化时创建对象的,它的优缺点和上面一样,只是写法有点不同,可以归为一种写法。

单例模式的懒汉式(线程不安全,不可用)

public class Singleton{
......
private static Singleton singleton = null;

private Singleton(){};

public static Singleton getSingleton(){

if(singleton == null){
singleton = new Singleton();
}
return singleton;
}
......
}

//访问方式
Singleton singleton = Singleton.getSingleton();

这种方式是在调用getSingleton()方法时才创建对象,它相对于饿汉式比较懒,因此被称为懒汉式。

上述这种写法其实是存在线程安全问题的,那为什么会存在线程安全问题呢?

是因为在运行过程中可能会存在这么一种情况:当有多个线程去调用getSingleton()方法来获取Singleton的实例时,第一个线程在执行if(singleton == null)这个语句时,此时singleton 是为null,进入语句。在还没有执行singleton = new Singleton()时(此时singleton还是为null的)第二个线程也进入if(singleton == null)这个语句,因为第一个线程还没有执行singleton = new Singleton(),所以它会继续执行singleton = new Singleton()语句来实例化Singleton对象,因为第二个线程也进入了if语句,所以它也会实例化一个Singleton对象。这样就导致实例化了两个Singleton对象。所以它是存在线程安全的。

线程安全的懒汉式(线程安全,效率低不推荐使用)

public class Singleton{
......
private static Singleton singleton = null;

private Singleton(){};

public static synchronized Singleton getSingleton(){

if(singleton == null){
singleton = new Singleton();
}
return singleton;
}
......
}

//访问方式
Singleton singleton = Singleton.getSingleton();

虽然通过加锁的方式解决了上面一种写法的线程安全问题,但是效率低。

缺点:

效率低,每个线程想要获得Singleton对象的时候,执行getSingleton()方法都要进行同步。而其实这个方法只需要执行一次实例化代码就够了,后面想要获得Singleton对象,直接return就行了。方法进行同步效率太低需要改进。

单例模式的懒汉式(线程不安全,不可用)

public class Singleton{
......
private static Singleton singleton = null;

private Singleton(){};

public static Singleton getSingleton(){

if(singleton == null){
synchronized (Singleton.class){
singleton = new Singleton();
}
}
return singleton;
}
......
}

//访问方式
Singleton singleton = Singleton.getSingleton();

由于上面一种写法效率太低,可能有人会想到这种写法。其实这种写法跟(3)的写法一样是线程不安全的。当一个线程还没有实例化Singleton时,另一个线程执行到if(singleton == null)语句时就会进入到if语句,虽然加了锁,但等到第一个线程执行完singleton = new Singleton()跳出这个锁时,另一个已经进入if语句的线程同样会实例化一个新的Singleton对象。线程不安全的原理跟(3)中的类似,因此这种方法并不可行。

单例模式懒汉式双重校验锁(推荐使用)

public class Singleton{
......
private static volatile Singleton singleton = null;

private Singleton(){};

public static Singleton getSingleton(){

if(singleton == null){
synchronized (Singleton.class){
if(singleton == null){
singleton = new Singleton();
}
}
}
return singleton;
}
......
}

//访问方式
Singleton singleton = Singleton.getSingleton();

这一版代码解决了(3)和(4)中存在的问题,其中有两次if(singleton == null)的判断,这个叫做【双重检查 Double-Check】。

  • 第一个if(singleton == null),其实是为了解决(4)中效率问题,只有singleton为null时,才进入synchronized的代码段。

  • 第二个if(singleton == null)则是为了解决(3)中的线程安全问题,防止多线程可能实例多个对象的情况。

volatile关键字是为了防止指令重排出现错误,就是说,由于有一个singleton不为null了,但是仍没有完成初始化的中间态,而这个时候,如果有其他线程刚好运行到第一层if语句,这里读取的singleton已经不为null了,所以直接把这个中间态的singleton拿去用,就会产生问题。(涉及原子操作、指令重排知识,volatile关键字的一个作用是禁止指令重排)

优点:

线程安全;延迟加载;效率较高

内部类(推荐使用)

public class Singleton{
......

private Singleton(){};

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

public static final Singleton getSingleton(){

return SingletonHolder.singleton;
}
......
}

//访问方式
Singleton singleton = Singleton.getSingleton();

它利用了ClassLoader来保证了同步,同时又能让开发者控制类加载的时机。从内部看是一个饿汉式的单例,但从外部看又的确是懒汉式实现

优点:

避免了线程不安全;延迟加载;效率高。

枚举(极推荐使用)

public enum SingletonEnum {

singleton;

private SingletonEnum(){}

public void method(){
//do something
}
}

//访问方式
SingletonEnum.singleton.method();

由于创建枚举实例的过程是线程安全的,所以这种写法也没有同步的问题。

优点:

这种写法在功能上与共有域方法相近,但它更简洁,无偿地提供了序列化机制,绝对防止对此实例化,即使是在面对复杂的序列化或者反射攻击的时候。


如有错误欢迎指出