定义:保证一个类只有一个实例,并且提供一个访问它的全局访问点。构造器私有化,提供静态方法getInstance()得到类的示例。
七种写法:
1.饿汉式
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton (){
}
public static Singleton getInstance() {
return instance;
}
}
类加载时就实例化对象,典型的以空间换时间,需要调用对象时不需要判断是否实例化过,节省运行时间,但是若没用到该对象仍要实例化对象浪费内存。
2.懒汉式
public class Singleton {
private static Singleton instance;
private Singleton (){
}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
第一次调用对象时才实例化对象,节约内存但是第一次调用对象需要实例化,反映稍慢。并且线程不安全,并发场景下会多次实例化。
3.懒汉式
public class Singleton {
private static Singleton instance;
private Singleton (){
}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
在getInstance()方法上加synchronized锁,第一次调用对象时才实例化对象,节约内存,但是多线程情况下会造成线程阻塞,只有一个线程执行完同步方法,下一个线程才会获得对象锁执行同步方法,性能较差。
4.双重校验锁(DCL)
public class Singleton {
/**
* 注意此处使用的关键字 volatile,
* 被volatile修饰的变量的值,将不会被本地线程缓存,
* 所有对该变量的读写都是直接操作共享内存,从而确保多个线程能正确的处理该变量。
*/
private volatile static Singleton singleton;
private Singleton() {
}
public static Singleton getInstance() {
if (instance == null) {
synchronized(Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return singleton;
}
}
这种写法在getInstance()中进行了两次判空,第一次是为了不必要的同步,第二次是在instance等于null的情况下才创建实例。
为什么要加volatile呢?instance = new Singleton()时,新建一个对象可以分为三个部分:位对象分配内存空间,初始化对象和将对象内存地址赋值给instance实例对象。JVM为了提高性能会进行代码重排序,将初始化对象排在将内存地址赋值给instance对象后。这样导致多个线程并发调用对象时,一些线程会调用到未初始化的对象。如果对象用volatile修饰,在多线程环境下会禁止重排序。
5.静态内部类
public class Singleton {
private Singleton(){
}
public static Singleton getInstance(){
return SingletonHolder.sInstance;
}
private static class SingletonHolder {
private static final Singleton sInstance = new Singleton();
}
}
采用内部类,在内部类里创建对象实例。这样不使用内部类的话,JVM就不会实例化单例对象,实现懒汉式的延迟加载。当调用getInstance方法时会调用内部类进行加载。JVM进行类加载初始化时会保证同步性(所以线程安全!)。每个类都有一个唯一的初始化锁LC,线程需要获取类的初始化锁进行类的加载初始化。类初始化时static修饰的单例变量就会实例化(且只实例化一次!),这样保证了只实例化一次。
6.枚举
《Java与模式》中,作者这样写道,使用枚举来实现单实例控制会更加简洁,而且无偿地提供了序列化机制,并由JVM从根本上提供保障,绝对防止多次实例化,是更简洁、高效、安全的实现单例的方式。
public enum Singleton {
//定义一个枚举的元素,它就是 Singleton 的一个实例
INSTANCE;
public void doSomeThing() {
// do something...
}
}
使用方法如下:
public static void main(String args[]) {
Singleton singleton = Singleton.instance;
singleton.doSomeThing();
}
枚举单例的优点就是简单,但是大部分应用开发很少用枚举,可读性并不是很高,不建议用。
7.使用容器
public class SingletonManager {
private static Map<String, Object> objMap = new HashMap<String,Object>();
private Singleton() {
}
public static void registerService(String key, Objectinstance) {
if (!objMap.containsKey(key) ) {
objMap.put(key, instance) ;//添加
}
}
public static ObjectgetService(String key) {
return objMap.get(key) ;//获取
}
}
这种是用SingletonManager 将多种单例类统一管理,在使用时根据key获取对象对应类型的对象。这种方式使得我们可以管理多种类型的单例,并且在使用时可以通过统一的接口进行获取操作,降低了用户的使用成本,也对用户隐藏了具体实现,降低了耦合度。