什么是单例模式
确保一个类只有一个实例,也就是类有且仅有一个对象,并且提供一个全局的访问点,外部通过这个访问点来访问该类的唯一实例
单例模式的特点
- 单例类只能有一个实例;
- 单例类必须自己创建自己的唯一实例;
- 单例类必须给所有其他对象提供这一实例。
几种常见的单例模式
单例模式的写法有好几种,这里主要介绍四种:懒汉式单例、饿汉式单例、登记式单例、枚举式单例
懒汉式单例
不着急实例化,需要用的时候才初始化
原始式
public class Singleton {
private Singleton() {}
private static Singleton singleton = null;
public static Singleton getInstance() {
if (singleton== null) {
singleton= new Singleton();
}
return singleton;
}
}
缺点:
- 在反射面前没什么用,因为java反射机制是能够实例化构造方法为private的类的;
- 线程不安全,因为线程访问具有随机性,并发环境可能出现多个singleton实例
线程同步式
public class Singleton {
private Singleton() {}
private static Singleton singleton = null;
//为了保证多线程环境下正确访问,给方法加上同步锁synchronized
public static synchronized Singleton getInstance() {
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
}
缺点:在方法调用上加了同步,虽然线程安全了,但是每次都有同步,会影响性能,毕竟99%是不需要同步的
双重锁检查式
public class Singleton {
private Singleton() {}
private static Singleton singleton = null;
//双重锁检查
public static Singleton getInstance() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton3();
}
}
}
return singleton;
}
}
优点:线程安全,且确保了只有第一次调用单例时才会做同步,避免了每次都同步的性能损耗;
缺点:双重锁降低了程序响应速度和性能
静态内部类式
public class Singleton {
private Singleton() {}
//内部类的初始化需要依赖主类,需要先等主类实例化之后,内部类才能开始实例化
private static class LazyHolder {
//这里加final是为了防止内部将这个属性覆盖掉
private static final Singleton INSTANCE = new Singleton();
}
//这里加final是为了防止子类重写父类
public static final Singleton getInstance() {
return LazyHolder.INSTANCE;
}
}
优点:利用了classloader机制来保证初始化instance时只有一个线程,线程安全且没有性能损耗
饿汉式
类初始化的时候就立刻实例化,天生线程安全
public class Singleton {
private Singleton() {}
private static final Singleton singleton = new Singleton();
public static Singleton getInstance() {
return singleton;
}
}
缺点:系统启动时,占用资源;如果后期这个单例没有被使用,会造成资源浪费
注册登记式
- 相当于有一个容器装载所有实例,在实例产生之前先检查下容器有没有,如果有就直接取出来,如果没有就先new一个放进去,然后给后面的人用,SpringIoc容器就是一种注册登记式单例
- 登记式单例实际上维护了一种单例类的实例,将这些实例存放在一个Map中,对于已经登记过的实例,则从Map直接返回,没有登记的,则先登记,然后返回;
- 登记式单例内部实现其实还是用的饿汉式,因为其中的static方法块,它的的单例在类被装载时就被实例化了
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* 注册登记式单例
* 类似Spring里面的方法,将类名注册,下次从里面直接获取
*/
public class Singleton {
//Spring最底层的这个容器就是一个map,说白了,IOC容器就是一个map
private static Map<String, Singleton> map = new ConcurrentHashMap<String, Singleton>();
//每个class对应一个map的key,也就是唯一的id
static {
Singleton singleton = new Singleton();
map.put(singleton.getClass().getName(), singleton);
}
//保护默认构造函数
protected Singleton() {}
//静态工厂方法,返回此类唯一的实例
public static Singleton getInstance(String name) {
if (name == null) {
name = Singleton.class.getName();
}
if (map.get(name) == null) {
try {
map.put(name, (Singleton) Class.forName(name).newInstance());
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
return map.get(name);
}
}
枚举式单例
- 这种方式不仅能解决多线程同步问题,而且能防止反序列化重新创建新的对象,不过由于jdk1.5中才加入enum特性,所以不常用
public enum Singleton {
INSTANCE;
public void getInstance() {
}
}