Java单例模式

单例模式:只产生一个实例对象,如何确保只有一个实例产生,私有化构造方法,提供一个public方法来获取实例对象

1 饿汉式

public class Singleton1 {

    //饿汉式:在类加载的时候初始化实例对象,不管你需不需要用
    //就像一个被饿怕了的人,不管饿没饿,都先把吃的准备好
    private static  Singleton1 instance = new Singleton1();

    private Singleton1() {
    }

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

在类加载的时候就进行了实例化,不存在多线程同步的问题,是线程安全的。缺点是在类加载的时候就初始化,如果这个实例很占用内存就形成了浪费。如果这个实例很大且只在特定场景下才使用,就用懒汉式。

2 懒汉式

public class Singleton2 {

    //就是一个懒惰的人,在饿了的时候才会去找吃的
    private static Singleton2 instance = null;

    private Singleton2() {
    }

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

懒汉式在需要实例时才进行创建,但是多个线程同时调用getInstance()方法时候可能会产生多个实例,就需要给这个获取实例的方法上锁,加上关键字synchronized后代码如下:

public class Singleton2 {

    private static Singleton2 instance = null;

    private Singleton2() {
    }

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

加锁后的懒汉式解决了线程并发的问题,但是依然不够完美,锁住的东西太多了,导致程序运行速度变慢,需要进行优化:

public class Singleton2 {

    private static Singleton2 instance = null;

    private Singleton2() {
    }

    public static Singleton2 getInstance() {
        if (instance == null) {  //标记步骤
            synchronized(Singleton2.class){
                instance = new Singleton2();
            }
        }
        return instance;
    }
}

此种方式依然不是线程安全的,如果线程1执行完if(instance == null)那一步然后中断了,此时是不存在实例的,线程2执行到这一步判断依然为空,向下执行创建实例,然后释放锁的钥匙,线程1获取线权后拿到钥匙继续执行并创建实例,程序就出错了,需要在同步代码块里面再判断一次,双重检查:

public class Singleton2 {

    private static Singleton2 instance = null;

    private Singleton2() {
    }

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

此时的代码没问题了吗?no no no!

因为处理器会进行指令重排序,比如说有如下代码:

int i = 1; //语句1
int n = 2; //语句2
i = i + 1; //语句3
n = i + n; //语句4

处理器为了提高程序运行效率,可能会对输入代码进行优化,它不保证程序中各个语句的执行先后顺序同代码中的顺序一致,但是它会保证程序最终执行结果和代码顺序执行的结果是一致的。也就说这一段代码的执行顺序可能是 1 -> 2 -> 3 -> 4 ,也有可能是 1 -> 3 -> 2 -> 4 , 还可能是 2 -> 1 ->3 -> 4 ,总之只要不影响最后结果的顺序都有可能,但是如果一条指令(指令2)用到了另一条(指令1)的结果,那么处理器会确保指令1先于指令2执行,也就说上面代码不可能出现语句4先于语句3的情况。在单线程中是不会有问题的,但是多线程中指令重排序就会让程序存在问题了。

那指令重排序与我们这个单例模式又有啥关系?问题就出在一面这一句:

instance = new Singleton2();

这一句在处理器中执行的时候是分为三个步骤去执行的:

① 给instance分配内存

② 执行new Singleton2()创建实例

③ 将instance对象指向分配的内存空间(执行了这一步后instance就不再是null了)

因为存在指令重排序,当jvm执行时候顺序就可能是1 -> 2 -> 3 ,也可能是 1 -> 3 -> 2,如果是后者,线程1执行玩第3步,还没执行第2步,突然被线程2抢占了,此时instance已经不是null了,线程2执行getInstance()就会出错。所以此处需要一个关键字:volatile,代码如下

public class Singleton2 {

    private static volatile Singleton2 instance = null;

    private Singleton2() {
    }

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

volatile作用禁止指令重排序,能够保证线程的有序性,就是volatile修饰的变量进行读写操作时,前面的语句已经执行完了,对后面的可见,并且后面的语句都还没有执行。这就保证了上述步骤执行顺序时1 -> 2 -> 3

3 静态内部类模式

public class Singleton3 {

    private static class Singleton3Holder{
        private static final Singleton3 instance = new Singleton3();
    }

    private Singleton3() {
    }

    public static Singleton3 getInstance() {
        return Singleton3Holder.instance;
    }
}

这种模式和饿汉式一样使用了类加载机制去创建实例,不同的是这种可以再需要的时候进行创建,也不会产生线程并发的问题。


主要常用的还是静态内部类和懒汉式的双重检查锁模式

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值