多线程中的单例模式

单例模式大家一定不陌生

  1. 饿汉式
public class SingletonTest {
    private static SingletonTest instanse = new SingletonTest();

    private SingletonTest() {

    }

    public static SingletonTest getInstanse() {
        return instanse;
    }
}

在类加载的时候就初始化一个对象,这样没有线程安全问题。但是没办法做到懒加载
2. 懒汉式

public class SingletonTest2 {
    private static SingletonTest2 instance;

    private SingletonTest2() {

    }

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

只有在getInstance时才去初始化这个对象,实现了懒加载
但是在线程情况下会出现线程安全问题,如果线程1到了if(null==instance)这一步停止了,线程2这时也会走到这一步,这样就会创建两个实例。最简单的解决办法是在方法上加synchronized。但是这样又会影响性能,因为只有第一次会出现线程安全问题,以后只要判断返回即可。
因此可以采用双重检查的方法

public class SingletonTest3 {
    private static SingletonTest3 instance;

    private SingletonTest3() {

    }

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

不幸的是,这样依然没有解决问题,会遇到空指针问题。
可能会非常奇怪,这样怎么会有空指针问题呢?这和java的内存模型有关系。在上面的类中,除了一个instance变量,就没有其他的成员变量了。如果这个类中有许多其他的属性和变量,或者在构造方法中做了一些耗时的初始化工作,那么在instance变量构造完成后,其他属性并没有来得及加载完毕,直接调用就会引发空指针问题。
在并发中的三个重要的概念
1. 原子性
2. 可见性
3. 有序性
在jvm的优化下,有序性并不是完全严格的。例如在一段代码块中

{
    int a;       (1)
    boolean b;   (2)
    a = 1;       (3)
    b = false;   (4)
}

执行顺序并不一定是 1,2,3,4.可能是1,3,2,4.jvm会保证最终一致性,但是会做一些优化顺序,所以就会出现这样的问题

那么怎么解决这个问题呢?

public class SingletonTest4 {
    private static volatile SingletonTest4 instance;

    private SingletonTest4() {

    }

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

这个例子中,instance被volatile修饰,便可以解决这个问题。
原因是volatile关键字保证了两个性质,可见性和有序性,但是不保证原子性。可见性是指不同线程读取到同一个变量,必须是一致的,不会因为各自线程的缓存而有区别。有序性是指在读取这个变量是,必须先执行写操作,也就是保证最终拿到的instance,一定是在初始化工作全部完成后的,也就是happens-before原则。

还有几个方法也可以保证线程安全
1. 使用内部类

public class SingletonTest5 {
    private static volatile SingletonTest5 instance;

    private SingletonTest5() {

    }


    private static class InstanceHolder {
        private static final SingletonTest5 instance = new SingletonTest5();
    }

    public static SingletonTest5 getInstance() {
        return InstanceHolder.instance;
    }
}

分析一下,在类的加载中有几个阶段,1,装载 2,链接 3,初始化。static在加载的时候只会被运行一次且能严格保证运行顺序。而且是主动加载,也就是懒加载。在调用的时候会被初始化一次。这样便可以解决问题

  1. 使用枚举
public class SingletonTest6 {
    private static volatile SingletonTest6 instance;

    private SingletonTest6() {

    }

    private enum Instance {
        INSTANCE;

        private final SingletonTest6 instance;

        private Instance() {
            instance = new SingletonTest6();
        }

        private SingletonTest6 getInstance() {
            return instance;
        }

    }

    public static SingletonTest6 getInstance() {
        return Instance.INSTANCE.getInstance();
    }


}

枚举定义的时候构造函数中就创建了instance,且只会被创建一次,因此也是线程安全的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值