lotus

贵有恒何必三更眠五更起 最无益只怕一日曝十日寒

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

 

 

目录

一、什么是单例模式?

二、单例模式的应用场景

三、两种典型的方式实现单例模式

  • 1.饿汉模式
  • 2.懒汉模式
  • 3.理解懒汉模式和饿汉模式

四、单例模式和线程的关系

  • 1.饿汉模式是否线程安全?
  • 2.懒汉模式线程安全吗?为什么?
  • 2.1 如何改进懒汉模式?让代码变得线程安全呢?

————————————————

 

一、什么是单例模式?
单例模式是一种常见的“设计模式”

二、单例模式的应用场景
某个类,不应该有多个实例,此时就可以使用单例模式(DataSource就是一个典型的案例,一一个程序中只有一个实例,不应该实例化多个DataSource对象)。如果尝试创建多个实例,编译期就会报错。

三、两种典型的方式实现单例模式
1.饿汉模式

public class singlePattern {
    //先创建一个表示单例的类
    //我们就要求Singleton这个类只能有一个实例
    //饿汉模式的单例实现
    //饿汉模式的单例实现,“饿”指得是,只要类被加载,实例就会立刻创建(实例创建时机比较早)
    static class Singleton{
        //把 构造方法  变为私有,此时在该类外部,就无法 new 这个类的实例了
        private Singleton(){
 
        }
//再来创建一个 static 的成员,表示Singleton 类唯一的实例
 //static 和 类相关,和实例无关,类在内存中只有一份,static 成员也就只有一份
        static Singleton instance = new Singleton();
  //new没报错是因为Singleton类是singlePattern的内部类,singlePattern是可以访问内部类的private成员的
        public static Singleton getInstance(){
            return instance;
        }
  public static void main(String[] args) {
//此处得 getInstance 就是获取实例得唯一方式,不应该使用其他方式创建实例了
    Singleton s = Singleton.getInstance();
    Singleton s2 = Singleton.getInstance();
     System.out.println(s == s2);
    }
}
 
    }

只要类被加载,就会立刻实例化Singleton实例,后续无论怎么操作,只要严格使用getInstance,就不会出现其他实例。

2.懒汉模式

public class lazyPattern {
    //使用懒汉模式来实现,Singleton类被加载的时候,不会立刻实例化
    //等到第一次使用这个实例的时候,再实例化
    static class Singleton{
        private static Singleton instance = null;
        
        public static Singleton getInstance(){
            if(instance == null){
                instance = new Singleton();
            }
            return instance;
        }
    }
    public static void main(String[] args) {
 
    }
}

 

类被加载的时候,没有立刻被实例化,第一次调用getInstance的时候,才真正的实例化。

如果要是代码一整场都没有调用getInstance,此时实例化的过程也就被省略掉了,又称“延时加载”

一般认为“懒汉模式” 比 “饿汉模式”效率更高。

懒汉模式有很大的可能是“实例用不到”,此时就节省了实例化的开销。

3.理解懒汉模式和饿汉模式
各种编辑器,有两种主要的打开方式:

  • 记事本,会尝试把整个文件的内容都读取到内存中,然后再展示给用户(饿汉模式)
  • vscode/sublime,只是会把当前这个屏幕内的内容(以及周围的一小点内容)加载到内存中,随着翻页,会继续加载

四、单例模式和线程的关系
1.饿汉模式是否线程安全?
安全。

类加载只有一次机会,不可能并发执行,对于饿汉模式来说,多线程同时调用getInstance,由于getInstance里只做了一件事:读取instance实例的地址=》多个线程在同时读取同一个变量,不会产生线程不安全。

2.懒汉模式线程安全吗?为什么?
懒汉模式是线程不安全的,只有在实例化之前调用,存在线程不安全问题

如果要是已经把实例创建好了~后面再去并发调用getInstance 就是线程安全的了

以上面的懒汉模式代码为例,多线程并发执行的时间线如下。

 

 

 

2.1 如何改进懒汉模式?让代码变得线程安全呢?
1.加锁

public class lazyPatternToSafe {
    static class Singleton{
        private static Singleton instance = null;
        //方法一:
        public static Singleton getInstance1(){
            synchronized (lazyPattern.Singleton.class){
                if(instance == null){
                    instance = new Singleton();
                }
            }
            return instance;
        }
 
 
 
        //方法二:
       synchronized public static Singleton getInstance2(){
                if(instance == null){
                    instance = new Singleton();
                }
            return instance;
        }
 
    }
}

 

上面的“粒度”较小,下面的“粒度”较大。

加锁之后的时间线:

 

 

 

但是加锁之后,仍然存在效率问题。上面的改进代码,哪怕实例已经创建好了,但是每次调用getInstance还是涉及加锁解锁,而这里的加锁解锁已经不必要了。只要代码中涉及到锁,基本上就和高性能无缘了(因为涉及到锁之间的等待)

2.解决方案:只有在实例化之前调用的时候加锁,后面不加锁~

static class Singleton {
    private static Singleton instance = null;
 
    public static Singleton getInstance1() {
        if(instance == null){
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }

 


上述双线程执行的时间线如下:

 

 

 

 

 

 

此时即能保证线程安全,也能保证程序运行的效率。

3.但是还是存在问题,此处的多个操作,可能会被编译器优化,只有第一次读才从内存中读取,后续的读就直接从CPU中读取寄存器。就可能导致线程1修改之后,线程2没有读到最新的值,内存可见性导致的线程不安全问题(先加锁的线程在修改,后加锁的线程在读取)。

最终版本:加volatile关键字

static class Singleton {
    //为了解决内存不可见问题,需要加上关键字volatile
    private volatile static Singleton instance = null;
 
    public static Singleton getInstance1() {
        if(instance == null){
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

 

 

上面的代码:为了保证线程安全,涉及到三个要点:

1.加锁 【保证线程安全】

2.双重if 【保证效率】

3.volatile【避免内存可见性引来的问题】

posted on 2022-12-12 21:38  白露~  阅读(93)  评论(0编辑  收藏  举报