设计模式:单例模式及其线程安全

单例模式确实已经接触了好久了,现在就着线程安全对这个设计模式做一个总结(顺序:单例模式基础->单例模式线程安全->线程安全的基础上改进效率->防止Java反射机制再创建对象),有错误之处请指教。

单例模式的本质就是要控制类的实例的个数,就是要保证每个类只能有一个实例对象。
单例模式实现的方式就是:
   - 构造函数私有化;
   - 创建一个本类对象;
   - 提供方法返回这个对象;

单例模式主要有两种写法:饿汉式和懒汉式(延迟加载模式)
饿汉式
所谓饿汉式就是一来就new一个对象实例。

class MySingleton{
    private static MySingleton mySingleton=new MySingleton();
    private MySingleton(){}
    public static MySingleton getInstance(){
        return mySingleton;
    }
}

懒汉式(延迟加载)
懒汉式与饿汉式的不同之处在于,懒汉式是在第一次调用到getInstance方法时才会new一个实例。

class MySingleton{
    private static MySingleton mySingleton=null;
    private MySingleton(){}
    public static MySingleton getInstance(){
        if(mySingleton==null) mySingleton=new MySingleton();
        return mySingleton;
    }
}

有多线程基础的肯定都知道懒汉式是线程不安全的,因此我们就要用到Java的线程安全机制来解决它。


我就按我的另一篇文章中: Java多线程线程安全实现方式 来说一下。
Synchronized方法和同步代码块
很容易想到,我们只需要把Synchronized加载会产生线程安全问题的地方就OK了。

//方法同步
class MySingleton{
    private static MySingleton mySingleton=null;
    private MySingleton(){}
    public synchronized static MySingleton getInstance(){
        if(mySingleton==null) mySingleton=new MySingleton();
        return mySingleton;
    }
}
//同步代码块
class MySingleton{
    private static MySingleton mySingleton=null;
    private MySingleton(){}
    public static MySingleton getInstance(){
        synchronized(MySingleton.class){
            if (mySingleton == null) mySingleton = new MySingleton();
        }
        return mySingleton;
    }
}

到这里线程安全这个问题算是基本解决了。(为什么说是基本呢?请认真往下看,提醒一下:注意 Java的 反射机制可以动态获取到类的所有信息,包括私有的)。


但是!但是!效率问题还没解决,我们的对象只要new一次,也就是说同步只有在第一次new的时候才需要体现他的作用,结果每次调用他都在同步,大大降低了效率。
因此需要修改让它只在第一次同步,代码如下:

class MySingleton{
    private static MySingleton mySingleton=null;
    private MySingleton(){}
    public static MySingleton getInstance(){
        if (mySingleton == null) {
            synchronized (MySingleton.class) {
                if (mySingleton == null) mySingleton = new MySingleton();
            }
        }
        return mySingleton;
    }
}

这种写法被称为“双重检查锁”。进行两次null检查,极大提升了并发度,进而提升了性能。


当然,能用隐式锁机制synchronized实现的我们也可以用显示锁机制实现。那篇文章中说了,显示锁机制主要是:ReentrantLock 、ReentrantReadWriteLock、StampedLock ,还是再给一下文章链接吧:Java多线程线程安全实现方式
我们就选用简单点的ReentrantLock来示范一下:

class MySingleton{
    private static ReentrantLock reentrantLock=new ReentrantLock();
    private static MySingleton mySingleton=null;
    private MySingleton(){}
    public static MySingleton getInstance(){
        if (mySingleton == null) {
                reentrantLock.lock();
                if (mySingleton == null) mySingleton = new MySingleton();
                reentrantLock.unlock();
        }
        return mySingleton;
    }
}

可以看出,synchronized的实现和ReentrantLock的实现整个结构框架其实是差不多的。


Java反射机制对单例模式的影响

还是简单的回忆一下Java反射机制(自己的Java反射机制知识还没有写到blog里,如果之后增加了再回来添加个链接)。
JAVA反射机制是在运行状态中,对于任意一个类(class文件),都能够知道这个类的所有属性和方法;对于任意一个对象,都能调用他的任意一个方法和属性。
因此就可以通过反射机制得到私有的构造函数,自己再new一个对象,破坏我们对单例模式的需求。

public class Singleton {
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        //通过Java反射调用单例模式的私有构造函数
        Class<?> class1=MySingleton.class;
        Constructor<?> constructor=class1.getDeclaredConstructor(null);
        constructor.setAccessible(true);
        MySingleton mysingleton1=(MySingleton)constructor.newInstance();//通过反射机制创建的实例
        MySingleton mysingleton2=MySingleton.getInstance();//通过单例模式方法取得的实例

        System.out.println(mysingleton1.equals(mysingleton2));
        System.out.println(mysingleton1==mysingleton2);

    }
}
class MySingleton{
    private static ReentrantLock reentrantLock=new ReentrantLock();
    private static MySingleton mySingleton=null;
    private MySingleton(){}
    public static MySingleton getInstance(){
        if (mySingleton == null) {
                reentrantLock.lock();
                if (mySingleton == null) mySingleton = new MySingleton();
                reentrantLock.unlock();
        }
        return mySingleton;
    }
}

这里写图片描述
很清楚的看到,两个实例是不等的。


要解决这样的不安全隐患,可以用一个标志,使其在创建第一个对象是抛异常。
把上述MySingleton代码修改为:

class MySingleton{
    private static boolean isFirstCreat=true;
    private static ReentrantLock reentrantLock=new ReentrantLock();
    private static MySingleton mySingleton=null;
    private MySingleton(){
        if(isFirstCreat)  isFirstCreat=false;
        else throw new RuntimeException("不允许创建第二个对象");
    }
    public static MySingleton getInstance(){
        if (mySingleton == null) {
                reentrantLock.lock();
                if (mySingleton == null) mySingleton = new MySingleton();
                reentrantLock.unlock();
        }
        return mySingleton;
    }
}

这里写图片描述


现在就已经是一个非常安全的单例模式了。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值