单例模式核心在于为整个系统只提供一个的实例,并提供一个全局访问点。 分为饿汉式和懒汉式,前者天生就是线程安全的,后者则需要考虑线程安全性,常见的线程安全的懒汉式单例的实现有内部类式和双重检查式两种。
1、饿汉式:
在类加载初始化时,就已经创建好了对象,没有延迟加载,线程安全,如果程序不访问就造成了资源浪费
思路:
1.构造器私有化
2.私有静态变量创建该对象
3.向外部提供一个获得该对象的方法
public class Singleton{
//私有静态变量创建该对象
private static Demo01 demo01=new Demo01();
//构造器私有化
private Demo01(){
}
//向外部提供一个获得该对象的方法
public static Demo01 getDemo01(){
return demo01;
}
}
2、懒汉式
实现了延迟加载,真正用的时候才加载,避免了浪费资源,但是因为有synchronized关键词修饰,所以会变得很慢,而且这个创建对象只会在第一次才会使用到锁,下面就是直接使用这个对象了,所以这个方式存在性能问题。
public class Demo02 {
//私有静态变量创建该对象
private static Demo02 demo02=null;
//构造器私有化
private Demo02(){
}
//加一个锁,保证每次只有一个进入这个方法
public synchronized static Demo02 getDemo02(){
if(demo02==null){
demo02=new Demo02();
}
return demo02;
}
}
3.双重检测锁:
可以实现延迟加载而且是线程安全的,双重检测锁,顾名思义就是需要进行两次判断,而两次判断的目的是不同的,第一次判断是检查这个单例对象是不是已经创建好,第二次是在保证只有一个线程的情况下进行二次判断这个单例对象是不是已经创建好,他们的目的是不同的。
public class Singleton {
private static volatile Singleton test=null;
private Singleton(){
}
//双重检查判定
public Demo05Test1 getDemo05Test11(){
if(test==null){ //第一次判断
synchronized (Demo05Test1.class){ //锁定这个对象
if(test==null){//第二次判断
test=new Demo05Test1();
}
}
}
return test;
}
}
对于双重检测锁,我要说一句, 双重检测锁中test的成员变量需要用volatile修饰,这一点牵涉到指令的重排序问题,因为在test=new Demo05Test1()这段代码分为三部分,分配内存,初始化类,让堆中对象指向test引用,这时会发生指令的重排序问题,所以需要用volatile关键字修饰,如果能把这一点说出来,估计能镇住面试官?,有关volatile可以看:
https://blog.youkuaiyun.com/javazejian/article/details/72772461,这篇文章写的很好。
volatile有两层语义:可见性和禁止指令的重排序。
4.私有静态内部类
静态内部类单例模式也称单例持有者模式,实例由内部类创建,由于 JVM 在加载外部类的过程中, 是不会加载静态内部类的, 只有内部类的属性/方法被调用时才会被加载, 并初始化其静态属性。静态属性由static
修饰,保证只被实例化一次,并且严格保证实例化顺序。
线程安全的懒汉式单例 —— 私有静态内部类方式,安全的原因在同一个类加载器下,一个类型只会被初始化一次(关于这点不同可以去百度)。这个也是最好的单例模式(以前这样认为)。
public class Singleton {
//静态私有内部类
private static class InnerClass {
private final static Singleton instance = new Singleton();
}
private Singleton(){ //构造器私有化
}
public static Singleton getInstance(){ //向外提供一个访问接口
return InnerClass.instance;
}
}
5.枚举类使用单例模式
以上四种单例的实现存在两个缺点:
一是:序列化可能会破坏单例模式,比较每次反序列化一个序列化的对象实例时都会创建一个新的实例
二是:使用反射强行调用私有构造器,解决方式可以修改构造器,让它在创建第二个实例的时候抛异常
所以使用枚举类是最好的方法。
public enum SingletonEnum {
INSTANCE;
private String name;
public String getName(){
return name;
}
public void setName(String name){
this.name = name;
}
}
详细请看:深入理解Java枚举类型(enum)