Java 单例模式(入门)

1.什么是单例模式?

Java中单例模式定义:“一个类有且仅有一个实例,并且自行实例化向整个系统提供。”
单例模式就是一个类只有一个对象的实例

2.为什么要用单例模式?

某些应用场景中,有些对象有且只能有一个,如果创建多个就会出现很多问题。例如:一个操作系统中只能打开一个任务管理器,如果可以同时打开多个任务管理器就会出现问题。

3.怎么创建单例模式?

#####方法一:饿汉模式

public class Singleton{
   
  // 1.将构造方法私有化,不允许外部直接创建对象,只能通过类名调用静态方法来获取对象
    private Singleton(){
        
    }
    //2.创建类的唯一实例,使用private static修饰
     private static final Singleton instance = new Singleton();
    //3.提供一个用于获取实例的静态方法,使用public static修饰
    public static Singleton getInstance(){
        return instance;
    }
}

创建思路:
(1)如何保证只有一个实例?
方法:将构造方法设置为私有的,这样外部类就无法直接创建类的对象
那么问题来了……
(2)外部类是需要一个唯一实例的,无法创建怎么办?
方法:在类的内部创建类的唯一实例,并声明为static(声明为static的目的是将实例声明为类的成员,这样在其他类中就可以通过类名直接访问这个实例),同时声明为private防止外部直接访问这个实例,但是这样就会导致使用类名调用这个实例不能实现,此时需要创建一个方法来调用这个实例
(3)提供一个用于获取实例的方法,要用static修饰,类方法

#####方法二:懒汉模式

public class Singleton{
   
  // 1.将构造方法私有化,不允许外部直接创建对象
    private Singleton(){
        
    }
    //2.**声明**类的唯一实例,使用private static修饰(只是声明类的实例并没有实例化)
     private static final Singleton instance;
    //3.提供一个用于获取实例的静态方法,使用public static修饰
    public static Singleton getInstance(){
        if(instance==null){
        instance=new Singleton();
    }
    return instance;
}

创建思路:
(1)同懒汉模式
(2)声明类的唯一实例(只是声明,创建工作留在方法中)
(3)提供一个获取实例的方法(方法中创建实例)

##4.饿汉模式和懒汉模式的区别 ##
饿汉模式的特点是加载类比较慢(因为加载类的时候就已经创建了对象),但是运行时获取对象的速度较快 线程安全
懒汉模式的特点是加载类比较快(加载类的时候没有创建对象),但是运行时获取对象的速度较慢(运行时创建对象) 非线程安全(两个线程同时校验instance非空们就会同时创建instance对象)

##5.改进版:双重锁 ##

public class Singleton{
    private static volatile Singleton instance=null;
    //volatile关键字:防止jvm优化代码
    private Singleton(){
    }
    public static  Singleton getInstance(){
        if(instance==null){
            synchronized(Singleton.class){
                if(instance==null){
                    instance=new Singleton();
                }
            }
        }
        return instance;
     }
}

接着上一篇的单例模式说起,为什么要在多线程中创建单例模式的时候要进行双重锁定?先回顾一下双重锁定的代码块。

public class SingleTon {

private static SingleTon singleTon = null;

   
public SingleTon() {
    // TODO Auto-generated constructor stub
}


public static SingleTon getInstance(){
    if (singleTon == null) {
        synchronized (SingleTon.class) {
            if (singleTon == null) {
                singleTon = new SingleTon();
            }
        }
    }
    return singleTon;
}

}
为何要使用双重检查锁定呢?
考虑这样一种情况,就是有两个线程同时到达,即同时调用 getInstance() 方法,
此时由于 singleTon == null ,所以很明显,两个线程都可以通过第一重的 singleTon == null ,
进入第一重 if 语句后,由于存在锁机制,所以会有一个线程进入 lock 语句并进入第二重 singleTon == null ,
而另外的一个线程则会在 lock 语句的外面等待。
而当第一个线程执行完 new SingleTon()语句后,便会退出锁定区域,此时,第二个线程便可以进入 lock 语句块,
此时,如果没有第二重 singleTon == null 的话,那么第二个线程还是可以调用 new SingleTon ()语句,
这样第二个线程也会创建一个 SingleTon实例,这样也还是违背了单例模式的初衷的,
所以这里必须要使用双重检查锁定。
细心的朋友一定会发现,如果我去掉第一重 singleton == null ,程序还是可以在多线程下完好的运行的,
考虑在没有第一重 singleton == null 的情况下,
当有两个线程同时到达,此时,由于 lock 机制的存在,第一个线程会进入 lock 语句块,并且可以顺利执行 new SingleTon(),
当第一个线程退出 lock 语句块时, singleTon 这个静态变量已不为 null 了,所以当第二个线程进入 lock 时,
还是会被第二重 singleton == null 挡在外面,而无法执行 new Singleton(),
所以在没有第一重 singleton == null 的情况下,也是可以实现单例模式的?那么为什么需要第一重 singleton == null 呢?
这里就涉及一个性能问题了,因为对于单例模式的话,new SingleTon()只需要执行一次就 OK 了,
而如果没有第一重 singleTon == null 的话,每一次有线程进入 getInstance()时,均会执行锁定操作来实现线程同步,
这是非常耗费性能的,而如果我加上第一重 singleTon == null 的话,
那么就只有在第一次,也就是 singleTton ==null 成立时的情况下执行一次锁定以实现线程同步,
而以后的话,便只要直接返回 Singleton 实例就 OK 了而根本无需再进入 lock 语句块了,这样就可以解决由线程同步带来的性能问题了。
为什么要加volatile
首先要理解new Singleton()做了什么。
new一个对象有几个步骤。
1.看class对象是否加载,如果没有就先加载class对象
2.分配内存空间,初始化实例
3.调用构造函数
4.返回地址给引用。
而cpu为了优化程序,可能会进行指令重排序,打乱这3,4这几个步骤,导致实例内存还没分配,就被使用了,进而返回null。
双重锁模式的优点
//这个模式将同步内容放到if内部,提高了执行的效率,不必每次获取对象时都进行同步,只有第一次才同步,创建了以后就没必要了。
//这种模式中双重判断加同步的方式,比第一个例子中的效率大大提升,因为如果单层if判断,在服务器允许的情况下,
//假设有一百个线程,耗费的时间为100*(同步判断时间+if判断时间),而如果双重if判断,100的线程可以同时if判断,理论消耗的时间只有一个if判断的时间。
//所以如果面对高并发的情况,而且采用的是懒汉模式,最好的选择就是双重判断加同步的方式。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

-乾坤-

????????????????????????

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值