单例模式介绍
单例模式,是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例的特殊类。通过单例模式可以保证系统中,使用单例模式的类只有一个对象实例。
单例模式的使用场景
- 我们常用的windows的任务管理器和回收站都是单例的
- Spring中创建的Bean实例默认都是单例。
- Java-Web中,一个Servlet类只有一个实例。
单例模式的四种创建方式
1.饿汉式
public class Singleton {
private int id;
private static Singleton instance = new Singleton()
private Singleton(){};
private int getId(){
return id;
}
public static Singleton getInstance(){
return instance;
}
public static void main(String[] args) {
Singleton singleton1 = Singleton.getInstance();
Singleton singleton2 = Singleton.getInstance();
System.out.println(singleton1);
System.out.println(singleton2);
}
}
饿汉模式,比较常见的一种写法。在类加载的时候就对实例进行初始化,没有线程安全问题;获取实例的静态方法没有使用同步,调用效率高;但是没有使用懒加载,如果该实例从始至终都没被使用过,则会造成内存浪费。
2.懒汉式
public class Singleton {
private int id;
private static Singleton instance;
private Singleton(){}
private int getId(){
return id;
}
public static synchronized Singleton getInstance(){
if(instance == null){
instance = new Singleton();
}
return instance;
}
public static void main(String[] args) {
Singleton singleton1 = Singleton.getInstance();
Singleton singleton2 = Singleton.getInstance();
System.out.println(singleton1);
System.out.println(singleton2);
}
}
线程安全的懒汉模式,比较常见的一种写法。在第一次使用的时候才进行初始化,达到了懒加载的效果;由于获取实例的静态方法用synchronized修饰,所以也没有线程安全的问题;但是,这种写法每次获取实例都要进行同步(加锁),因此效率较低,并且可能很多同步都是没必要的。
3.双重检测机制(DCL)
public class Singleton {
private int id;
private static volatile Singleton instance;
private Singleton(){}
private int getId(){
return id;
}
public static Singleton getInstance(){
if(instance == null){
synchronized(Singleton.class){
if(instance == null){
instance = new Singleton();
}
}
}
return instance;
}
public static void main(String[] args) {
Singleton singleton1 = Singleton.getInstance();
Singleton singleton2 = Singleton.getInstance();
System.out.println(singleton1);
System.out.println(singleton2);
}
}
双重检测机制(双重检查加锁),比较常见的一种写法。在第一次使用的时候才进行初始化,达到了懒加载的效果;在进行初始化的时候会进行同步(加锁),因此没有线程安全问题;并且只有第一次进行初始化才进行同步,因此不会有效率方面的问题。
通过在synchronized外面和里面加上锁,可以防止初始化后,多次获取锁的不必要;如果没有外面的if,初始化后,后续代用方法时依然会占用锁;如果没有里面的if,在并发情况会创建多个Singleton对象
注意,属性必须加上volatile关键字,防止出现有序性问题
具体可以看这篇博客:volatile关键字
4.静态内部类
public class Singleton {
private int id;
private static volatile Singleton instance;
private Singleton(){}
private int getId(){
return id;
}
private static class SingletonHolder{
private static final Singleton instance = new Singleton();
}
public static Singleton getInstance(){
return SingletonHolder.instance;
}
public static void main(String[] args) {
Singleton singleton1 = Singleton.getInstance();
Singleton singleton2 = Singleton.getInstance();
System.out.println(singleton1);
System.out.println(singleton2);
}
}
JVM将推迟SingletonHolder的初始化操作,直到开始使用这个类时才初始化,并且由于通过一个静态初始化来初始化Singleton,因此不需要额外的同步。当任何一个线程第一次调用getInstance时,都会使SingletonHolder被加载和被初始化,此时静态初始化器将执行Singleton的初始化操作。
静态初始化时,jvm会保证线程安全,所以这种方式也不会产生线程安全问题