设计模式-单例模式-懒汉&饿汉以及线程的安全问题

单例模式

单例模式是最简单的设计模式之一,属于创建型模式,它提供了一种创建对象的方式,确保只有单个对象被创建。这个设计模式主要目的是想在整个系统中只能出现类的一个实例,即一个类只有一个对象。
单例模式的解决的痛点就是节约资源,节省时间从两个方面看:
1.由于频繁使用的对象,可以省略创建对象所花费的时间,这对于那些重量级的对象而言,是很重要的.
2.因为不需要频繁创建对象,我们的GC压力也减轻了,而在GC中会有STW(stop the world),从这一方面也节约了GC的时间

单例模式的缺点:简单的单例模式设计开发都比较简单,但是复杂的单例模式需要考虑线程安全等并发问题,引入了部分复杂度。

常见的单例模式有懒汉模式 和饿汉模式
所谓的懒汉模式就是什么时候我们要用对象时再进行创建对象,属于一种延迟加载的模式。通俗来讲,懒汉一直会等到实在饿得不行的时候才会进行进食。
所谓的饿汉模式它会提前将我们需要的东西给加载出来,有什么东西就提前进行消耗的意思。
下面简单的代码介绍懒汉和饿汉是如何进行创建的。

class hungryBoy{
    //随着类的加载而创建对象  即提前创建对象
    private static hungryBoy lazyPerson = new hungryBoy();
    private static int bread=10;
    private hungryBoy() {
    }
    public static  hungryBoy getInstance(){
        return lazyPerson;
    }
}
//懒汉模式
class lazyBoy{
    //类加载的时候没有创建对象 直到被调用的时候再进行创建对象
    private static lazyBoy lazyBoy = null;
    public static lazyBoy getInstance(){
        if(lazyBoy==null){
            lazyBoy = new lazyBoy();
            return lazyBoy;
        }else{
            return lazyBoy;
        }
    }
    public lazyBoy() {
    }
}
线程安全问题
饿汉模式的线程安全分析

测试多线程情况下饿汉模式是否是线程安全的。

//创建多线程例子 调用单例模式的对象。查看其中的hashcode看是否相等
class demo implements Runnable{
    @Override
    public void run() {
        System.out.println(hungryBoy.getInstance().hashCode());
    }
}

main 方法运行多线程

 public static void main(String[] args) {
        //多线程测试单例模式线程是否安全
        demo[] d = new demo[10];//线程数组
        for(int i = 0;i<10;i++){
            d[i]= new demo();//创建线程
        }
        for(int i=0;i<10;i++){
            new Thread(d[i]).start();//线程启动
        }
    }

结果显示, 从下图可以看出懒汉模式所使用的对象的hashcode是一样的,简介的说明了所使用的的是同一个对象。原因:因为加载类的时候已经在堆里面创建好对象了。因而在程序运行的时候,对象肯定不为空。

设计模式-单例模式-懒汉&饿汉以及线程的安全问题_第1张图片

懒汉模式的线程安全分析
  1. 为了模拟饿汉模式在创建对象的延迟问题,我们在创建对象是添加一些延迟。
class lazyBoy{
    //类加载的时候没有创建对象 直到被调用的时候再进行创建对象
    private static lazyBoy lazyBoy = null;
    public static lazyBoy getInstance(){
        if(lazyBoy==null){
			 try {
                Thread.sleep(100);//设置延迟时间
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            lazyBoy = new lazyBoy();
        }
        return lazyBoy;
    }
    public lazyBoy() {
    }
}
  1. 测试多线程情况下饿汉模式的线程安全问题
class demo1 extends Thread{
	 @Override
    public void run() {
        System.out.println(lazyBoy.getInstance().hashCode());
    }
}
  1. main 方法使用多线程数组测试懒汉模式
public static void main(String[] args) {
        //多线程测试单例模式线程是否安全
        demo1[] d = new demo1[10];//线程数组
        for(int i = 0;i<10;i++){
            d[i]= new demo1();//创建线程
        }
        for(int i=0;i<10;i++){
            new Thread(d[i]).start();//线程启动
        }
    }

结果如下 可以看出在该多线程数组进行并发执行的时候 创建了很多对象,这样就无法实现单例模式了。
设计模式-单例模式-懒汉&饿汉以及线程的安全问题_第2张图片

懒汉模式安全性解决方案
  1. 使用synchronized关键字同步方法
    优点:加上synchroized之后会使线程在方法外进行排队执行,有效的阻止了线程不安全问题
    缺点:很 多线程一起执行的时候,阻塞情况非常严重。效率低下
class lazyBoy{
    private static int bread=100;
    private static lazyBoy lazyBoy = null;//类加载的时候没有创建对象 直到被调用的时候再进行创建对象
    public static synchronized  lazyBoy getInstance(){
        if(lazyBoy==null){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            lazyBoy = new lazyBoy();
        }
        return lazyBoy;
    }
    public lazyBoy() {
    }
}
  1. 使用synchroized 同步代码块 但是同样效率低下
class lazyBoy{
    private static int bread=100;
    private static lazyBoy lazyBoy = null;//类加载的时候没有创建对象 直到被调用的时候再进行创建对象
    public static  lazyBoy getInstance(){
        try {
            synchronized (lazyBoy.class) {//对代码块进行同步
                if (lazyBoy == null) {
                    Thread.sleep(100);
                        lazyBoy = new lazyBoy();
                    }
                }
        }catch(InterruptedException e){
            e.printStackTrace();
        }finally{
            return lazyBoy;
        }
    }
    public lazyBoy() {
    }
}

3.双检查锁机制 加上volatile 增加线程之间的可见性。在同步代码块中使用二次检查,以保证其不被重复实例化。集合其二者,这种实现方式既保证了其高效性,也保证了其线程安全性。

class lazyBoy{
    private static int bread=100;
    private volatile static lazyBoy lazyBoy = null;//类加载的时候没有创建对象 直到被调用的时候再进行创建对象
    public static  lazyBoy getInstance(){
        try {
            if(lazyBoy==null){
				synchronized(lazyBoy.class){
					  if (lazyBoy == null) {
                        Thread.sleep(100);
                        lazyBoy = new lazyBoy();
                      }
                   }
				}
			}
        }catch(InterruptedException e){
            e.printStackTrace();
        }finally{
            return lazyBoy;
        }
    }
    public lazyBoy() {
    }
}

你可能感兴趣的:(设计模式,单例模式,饿汉模式,懒汉模式,多线程安全)