vlambda博客
学习文章列表

单例模式的9中实现方式


什么是单例模式


内存中只允许一个对象实例,此时 我们选择使用单例设计模式

单例模式的特点


1、单例模式只能有一个实例。

2、单例类必须创建自己的唯一实例。

3、单例类必须向其他对象提供这一实例。


B站视频 同步

喜欢看视频的朋友,可以点击小程序卡片,查看!


交易担保 哔哩哔哩 没有人比我更懂单例

单例模式的实现

1. 懒汉模式(线程不安全)

public class SingletonDemo {
    private static SingletonDemo instance;
    private SingletonDemo(){

    }
    public static SingletonDemo getInstance(){
        if(instance==null){
            instance=new SingletonDemo();
        }
        return instance;
    }
}


如上,通过提供一个静态的对象instance,利用private权限的构造方法和getInstance()方法来给予访问者一个单例。

缺点是,没有考虑到线程安全。


2. 线程安全的懒汉模式(线程安全)

public class SingletonDemo {
    private static SingletonDemo instance;
    private SingletonDemo(){

    }
    public static synchronized SingletonDemo getInstance(){
        if(instance==null){
            instance=new SingletonDemo();
        }
        return instance;
    }
}



3. 饿汉模式(线程安全)

public class SingletonDemo {
    private static SingletonDemo instance=new SingletonDemo();
    private SingletonDemo(){

    }
    public static SingletonDemo getInstance(){
        return instance;
    }
}


直接在运行这个类的时候进行一次loading,之后直接访问。


4. 静态类内部加载(线程安全)

public class SingletonDemo {
    private static class SingletonHolder{
        private static SingletonDemo instance=new SingletonDemo();
    }
    private SingletonDemo(){
        System.out.println("Singleton has loaded");
    }
    public static SingletonDemo getInstance(){
        return SingletonHolder.instance;
    }
}


使用内部类的好处是,静态内部类不会在单例加载时就加载,而是在调用getInstance()方法时才进行加载,达到了类似懒汉模式的效果,而这种方法又是线程安全的。


5. 枚举方法(线程安全)

enum SingletonDemo{
    INSTANCE;
    public void otherMethods(){
        System.out.println("Something");
    }
}


Effective Java作者Josh Bloch 提倡的方式,在我看来简直是来自神的写法。解决了以下三个问题:


(1)自由串行化。


(2)保证只有一个实例。


(3)线程安全。


如果我们想调用它的方法时,仅需要以下操作:

public class Hello {
    public static void main(String[] args){
        SingletonDemo.INSTANCE.otherMethods();
    }
}


这种充满美感的代码真的已经终结了其他一切实现方法了。


6. 双重校验锁法(通常线程安全,低概率不安全)

public class SingletonDemo {
    private static SingletonDemo instance;
    private SingletonDemo(){
        System.out.println("Singleton has loaded");
    }
    public static SingletonDemo getInstance(){
        if(instance==null){
            synchronized (SingletonDemo.class){
                if(instance==null){
                    instance=new SingletonDemo();
                }
            }
        }
        return instance;
    }
}


接下来我解释一下在并发时,双重校验锁法会有怎样的情景:


STEP 1. 线程A访问getInstance()方法,因为单例还没有实例化,所以进入了锁定块。


STEP 2. 线程B访问getInstance()方法,因为单例还没有实例化,得以访问接下来代码块,而接下来代码块已经被线程1锁定。


STEP 3. 线程A进入下一判断,因为单例还没有实例化,所以进行单例实例化,成功实例化后退出代码块,解除锁定。


STEP 4. 线程B进入接下来代码块,锁定线程,进入下一判断,因为已经实例化,退出代码块,解除锁定。


STEP 5. 线程A获取到了单例实例并返回,线程B没有获取到单例并返回Null。


理论上双重校验锁法是线程安全的,并且,这种方法实现了lazyloading。


7. 第七种终极版 (volatile)

对于6中Double-Check这种可能出现的问题(当然这种概率已经非常小了,但毕竟还是有的嘛~),解决方案是:只需要给instance的声明加上volatile关键字即可,volatile版本如下:

public class Singleton{
    private volatile static Singleton singleton = null;
    private Singleton()  {    }
    public static Singleton getInstance()   {
        if (singleton== null)  {
            synchronized (Singleton.class) {
                if (singleton== null)  {
                    singleton= new Singleton();
                }
            }
        }
        return singleton;
    }
}


volatile关键字的一个作用是禁止指令重排,把instance声明为volatile之后,对它的写操作就会有一个内存屏障(什么是内存屏障?),这样,在它的赋值完成之前,就不用会调用读操作。


8. 使用ThreadLocal实现单例模式(线程安全)

public class Singleton {
    private static final ThreadLocal  tlSingleton =
             new ThreadLocal () {
                @ Override
                protected Singleton initialValue(
{
                     return  new Singleton();
                }
            };
     /**
     * Get the focus finder for this thread.
     */

     public static Singleton getInstance({
         return tlSingleton. get();
    }
     // enforce thread local access
     private Singleton({}
}


ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。


9. 使用CAS锁实现(线程安全)

/**
 * 更加优美的Singleton, 线程安全的
 */

public class Singleton {
 /** 利用AtomicReference */
 private static final AtomicReference  INSTANCE =  new AtomicReference ();
  /**
  * 私有化
  */

  private Singleton(){
 }
  /**
  * 用CAS确保线程安全
  */

  public static final Singleton getInstance(){
   for (;;) {
   Singleton current = INSTANCE. get();
             if (current !=  null) {
                 return current;
            }
            current =  new Singleton();
             if (INSTANCE.compareAndSet( null, current)) {
                 return current;
            }
        }
 }

  public static void main(String[] args{
    Singleton singleton1 = Singleton.getInstance();
    Singleton singleton2 = Singleton.getInstance();
    System. out.println(singleton1 == singleton2);
 }
}



hello,我是御风,很高兴在这里与你相遇。我是一名程序员,也是一名B站UP主。我乐于分享,勤于学习。希望我们共同成长。永远相信梦想的力量




单例模式的9中实现方式

       
         
         
       
给个[在看],是对御风最大的支持