单例模式与双重锁

设计模式中,最为基础与常见的就是单例模式。这也是经常在面试过程中被要求手写的设计模式。

下面就先写一个简单的单例:

public class Singleton {

    private static Singleton singleton =new Singleton();

    private void Singleton(){}

    public static Singleton getSingleton(){

        return singleton;

    }

}

上面是饿汉式单例:jvm在启动的时候直接在内存中初始化一个单例对象,我们在调用getSingleton()时直接获取该对象。

public class Singleton {

private static Singleton singleton =null;

    private void Singleton(){}

    public static Singleton getSingleton(){

        if(singleton==null){

            singleton = new Singleton();

        }

        return singleton;

    }

}

上面的是懒汉机制:在jvm启动的时候不会初始化单例对象,只有调用getSingleton()时才去创建对象。多线程的情况下会出现不同步问题,因此需要加锁。

public class Singleton {

private static Singleton singleton =null;

    private void Singleton(){}

    public static synchronized Singleton getSingleton(){

    if(singleton==null){

        singleton  = new Singleton();

        }

    return singleton;

    }

}

懒汉加锁之后,我们调用getSingleton()方法时,会在该方法上面加锁,每次只允许一个线程进入,可以解决同步问题,但是性能会下降。

public class Singleton {

    private static Singleton singleton =null;

    private void Singleton(){}

    public static Singleton getSingleton(){

    if(singleton==null){                                   @1

        synchronized (Singleton.class){          @2

            if(singleton==null){                            @3

                singleton = new Singleton();          @4

                }

        }

    }

        return singleton;

    }

}

在懒汉加锁基础上编程双锁机制:解决了同步时的性能问题。但是在多线程的情况下,还是会出现问题,问题出现在哪里?

 singleton = new Singleton();          @4 

这一步实例化的过程有问题:

这一行代码可以分解为如下的三行伪代码:

  memory = allocate();   //1:分配对象的内存空间

  ctorInstance(memory);  //2:初始化对象

  instance = memory;     //3:设置instance指向刚分配的内存地址

在JIT编译器中,可能会发生重排序。在重排的情况下:如果a,b两个线程同时调用getSingleton()方法,例如a线程先获取到,在a线程 singleton = new Singleton(); 时发生重排,执行1,3,2,执行到3,b线程获取singleton的没有被初始化。

如何解决:

第一:加关键字volatile

public class Singleton {

    private static volatile Singleton singleton =null;

    private void Singleton(){}

    public static Singleton getSingleton(){

        if(singleton==null){

            synchronized (Singleton.class){

                if(singleton==null){

                    singleton =new Singleton();

                    }

            }

        }

    return singleton;

    }

}

Volatile关键字: 可以解决可见性问题,不能确保原子性问题(通过 synchronized 进行解决), 禁止指令的重排序(单例主要用到此JVM规范)。

第二:利用静态内部类

public class Singleton {

    private static Singleton singleton =null;

    private void Singleton(){}

    private static class StaticSingleton{

        private static final Singleton SINGLETON =new Singleton();

    }

    public static Singleton getSingleton(){

        if(singleton==null){

            synchronized (Singleton.class){

                if(singleton==null){

                    singleton = StaticSingleton.SINGLETON;

                }

        }

    }

    return singleton;

    }

}

静态内部类确保在第一次初始化的时候,不用担心并发问题。因为jvm会负责同步整个过程,在初始化进行一半的时候,别的线程无法使用。





个人公号:【排骨肉段】,可以关注一下。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,877评论 18 399
  • 前言 本文主要参考 那些年,我们一起写过的“单例模式”。 何为单例模式? 顾名思义,单例模式就是保证一个类仅有一个...
    tandeneck阅读 2,549评论 1 8
  • 一. Java基础部分.................................................
    wy_sure阅读 3,882评论 0 11
  • JAVA面试题 1、作用域public,private,protected,以及不写时的区别答:区别如下:作用域 ...
    JA尐白阅读 1,196评论 1 0
  • 五四广场。五月的风。海浪拍击岩石和堤岸的唰唰声让人感觉回到1919年5月4日这天,群情激愤的爱过学生走上街头,高呼...
    禅静一生阅读 373评论 7 13