一、定义
确保一个类只有一个实例,并且提供一个全局变量来访问到这个唯一的实例
二、实现方式
1.饿汉式
public class EagerSingleton{
private static final EagerSingleton instance = new EagerSingleton();
//防止在其他类中new出一个对象,可以给构造器加上private访问权限
private EagerSingleton(){
}
//向外暴露一个方法,来得到这个类的唯一的对象
public static EagerSingleton getInstance(){
return instance;
}
}
当类被加载的时候,静态变量instance就会被初始化,这个类的唯一实例就会创建。
优点:写法简单,在类加载的时候就会创建出对象,类只会加载一次,那么也就只会创建一次对象,避免了线程安全问题
缺点:(1)不能把控对象创建的时机,因为只要类被加载,这个对象就会被创建,而类的加载不只是使用类的static方法
(2)可能会造成内存空间的浪费,也就是说,也跟第一条一样,对象可能会被无意识的创建出来
2懒汉式
public class LazySingleton{
private static volatile LazySingleton instance;
private LazySingleton(){
}
public static LazySingleton getInstance01(){
if(instance == null){
instance = new LazySingleton();
}
return instance;
}
public static synchronized LazySingleton getInstance02(){
if(instance == null){
instance = new LazySingleton();
}
return instance;
}
public static LazySingleton getInstance03(){
//第一重判断
if(instance == null){
synchronized(LazySingleton.class){
//第二重判断
if(instance == null){
instance = new LazySingleton();
}
}
}
return instance;
}
}
懒汉式和饿汉式一样,将构造器设置为私有的,只能在它类的内部创建对象。与饿汉式不同的是,懒汉式并没有在类被加载的时候去创建对象,而是将类对象的创建交给了一个暴露的方法。当第一次使用这个方法的时候,instance是空的,那么它就会先创建出一个对象,那么接下来的调用都只需要直接返回这个对象。
对于01方法来说,线程是不安全的,为什么这么说呢?在多线程的情况下,可能会出现多个线程同时进到if判断中,并且都是第一次进到这个判断,那么就会创建出多个对象,违背了单例模式的设计思想。
对于02方法来说,线程是安全,但是对一个方法上锁,在高并发的情况下,每个线程使用这个方法都需要获取锁,导致系统的性能大大降低。
03方法又叫双重检查,是推荐使用的。只有在对象未被创建的情况下,才会出现线程安全的问题。那么,对需要创建对象的线程,先让它进到第一重判断中,然后通过同步代码块,只让其中的一个线程获取到锁,并且new出对象,这个线程结束之后,其他线程获取到锁进入第二重判断,由于第一个线程已经创建好了对象,那么到它就不会再创建了。
3.静态内部类创建对象
public class Singleton{
private Singleton(){
}
private static class HolderClass{
private final static Singleton instance = new Singleton();
public static Singleton getInstance(){
return HolderClass.instance;
}
}
}
Singleton的对象是作为内部类的成员变量,当Singleton类加载时,不会发生实例化,只有当第一次调用内部类的getInstance方法的时候,内部类才会被加载,对象才会被一同创建。
静态内部类比上面两种方式好在哪些地方呢?
对象只有在第一次使用内部类的方法的时侯才会创建,且只会创建一次。那饿汉式也一样啊,有什么区别?饿汉式下,因为对象属性作为类的成员变量,当类加载的时候就会直接创建出来,可能会无意识的创建出来。而对于静态内部类来说,将想要创建的对象和它所属的类(外部类)分开了,放到一个内部类中,外部类的加载不会影响对象。
类的静态属性只会在类第一次加载的时候会被初始化,在类进行初始化的时候,别的线程是无法进入的,保障了线程安全问题。而得到对象的方法没有加锁的限制,不会影响性能。
综上:静态内部类的方法实现了延迟加载,保障线程安全的前提下,又不会影响性能。
三、优缺点
优点:提供了对唯一实例的受控访问,节约系统资源,允许可变数目的实例。
缺点:没有抽象层,扩展较困难;职责过重,一定程度上违背了单一职责原则,因为它既提供了创建对象的方法,又提供了业务方法;Java的垃圾回收技术,将会导致共享的单例对象状态丢失,需要重新创建对象。
四、适用场景
系统只需要一个实例对象;客户调用类的单个实例只允许使用一个公共访问点