一、单例模式的概念
单例模式,也叫单子模式,是一种常用的软件设计模式。在应用这个模式时,单例对象的类必须保证只有一个实例存在。许多时候整个系统只需要拥有一个的全局对象,这样有利于我们协调系统整体的行为。比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息。这种方式简化了在复杂环境下的配置管理。(维基百科)
二、单例模式的实现
1、懒汉式——需要时再点外卖
1.1 朴素版
public class Singleton{
private static Singleton instance;
//构造方法私有——禁止外部实例化
private Singleton(){}
//静态方法——外部通过类名访问
public static Singleton getInstance(){
if(instance==null){
instance=new Singleton();
}
return instance;
}
}
说明:懒汉式在需要的时候再实例化对象,一定程度上节约内存
在多线程情况下不保证实例唯一(原因在于判断instance是否为null的时候有可能多个线程均进入了if条件块)
1.2 synchronized版
public class Singleton{
private static Singleton instance;
private Singleton(){}
//同步方法——保证多线程下实例唯一
public synchronized static Singleton getInstance(){
if(instance==null){
instance=new Singleton();
}
return instance;
}
}
说明:同步方法使得同一时刻只有一个线程进入方法块执行
但是实例化后每个想要进入该方法获取实例的线程都要阻塞排队(同步粒度粗——影响性能)
1.3 Double-Check版
public class Singleton{
private volatile static Singleton instance;
private Singleton(){}
public static Singleton getInstance(){
//保证实例化后线程不用进入同步块
if(instance==null){
//保证只有一个线程创建实例(若“多个线程”执行到此)
synchronized(Singleton.class){
//(使创建线程外的“多个线程”跳过)
if(instance==null){
instance = new Singleton();
}
}
}
return instance;
}
}
说明:(Java虚拟机初始化一个对象总的来说,做了3件事情:1、在堆空间分配内存 2、执行构造方法进行初始化 3、将对象指向内存中分配的内存空间,也就是地址) 由于JVM和CPU在执行指令时有可能进行重排序,则可能在instance指向内存空间时对象没有初始化,通过引入volatile关键字解决这一问题。
1.4 静态内部类
public class Singleton{
private Singleton(){}
public static Singleton getInstance(){
//访问时再加载
return Single.instance;
}
private static class Single{
private static Singleton instance = new Singleton();
}
}
说明:懒汉式,线程安全
2、饿汉式——提前点好外卖
public class Singleton{
//类加载时实例化
private static Singleton instance = new Singleton();
private Singleton(){}
public static Singleton getInstance(){
return instance;
}
}
说明:由于提前加载实例,在一定程度上会占用内存
饿汉式是在类加载时实例化(虚拟机保证每个类只加载一次),因此不会出现多线程问题
三、单例模式的应用
Spring中的Bean默认即为单例、Windows的Task Manager(任务管理器)
网站的计数器、数据库连接池、多线程的线程池
四、单例の其他问题
1、枚举实现单例
***枚举是特殊的常量类,且构造方法被默认强制私有***
public enum Singleton{
INSTANCE;
}
- 枚举类实现其实省略了
private
类型的构造函数 - 枚举类的域(field)其实是相应的enum类型的一个实例对象
public enum Singleton {
INSTANCE;
//隐藏的私有构造方法
private Singleton () {}
public static void main(String[] args) {
Singleton instance = Singleton.INSTANCE;
//输出Singleton,instance是该类的实例
System.out.println(instance.getClass().getName());
}
}
- 枚举单例的常见写法
// 定义单例模式中需要完成的代码逻辑
public interface MySingleton {
void do();
}
public enum Singleton implements MySingleton {
INSTANCE {
@Override
public void do() {
//TODO
}
};
public static MySingleton getInstance() {
return Singleton.INSTANCE;
}
}
2、单例的破坏
- 序列化
如果过该类实现Serializable,那么就会在反序列化的过程中再创建一个对象
这个问题的解决办法就是在反序列化时,指定反序化的对象实例
private static Singleton instance = new Singleton();
private Object readResolve() {
return instance;
}
- 反射
反射可以获取类的构造函数,通过setAccessible(true)方法可以调用私有的构造函数,创建对象
五、Reference
六、Resource