设计模式专题(二)单例模式

目录

单例模式

饿汉式单例

懒汉式单例

内部类单例

注册登记式单例

枚举式单例

单例模式

单例模式(一个类模板,在整个系统执行过程中,只允许产生一个实例)应用广泛,主要应用在:

  • 配置文件
  • Ioc容器
  • 日历
  • 工厂本身

单例模式:解决一个并发访问的时候线程安全问题,保证单例的技术方案有很多种

饿汉式单例

在实例使用之前,不管你用不用,我都先new出来再说,避免了线程安全问题

饿汉式单例:

public class Hungry {

   //私有构造方法,防止外部new
   private Hungry(){}

   private static final Hungry hungry = new Hungry();

   public static Hungry getInstance(){
       return  hungry;
   }

}

我们来测试: 

public static void main(String[] args) {
       int count = 200;

       //发令枪,我就能想到运动员
       final CountDownLatch latch = new CountDownLatch(count);

       long start = System.currentTimeMillis();
       for (int i = 0; i < count;i ++) {
           new Thread(){
               @Override
               public void run() {
                   try{
                       try {
                           // 阻塞
                           // count = 0 就会释放所有的共享锁
                           // 万箭齐发
                           latch.await();
                       }catch(Exception e){
                           e.printStackTrace();
                       }
                       //必然会调用,可能会有很多线程同时去访问getInstance()
                       Object obj = Hungry.getInstance();
                       System.out.println(System.currentTimeMillis() + ":" + obj);
                   }catch (Exception e){
                       e.printStackTrace();
                   }
               }
           }.start(); //每循环一次,就启动一个线程,具有一定的随机性
           //每次启动一个线程,count --
           latch.countDown();
       }
       long end = System.currentTimeMillis();
       System.out.println("总耗时:" + (end - start));

   }

打印结果发现,无论怎么运行,程序始终拿到的是一个实例

懒汉式单例

默认加载的时候不实例化,在需要用到这个实例的时候进行实例化(延时加载)
懒汉式单例:

public class LazyOne {
    private LazyOne(){}
    //静态块,公共内存区域
    private static LazyOne lazy = null;
    
    public static LazyOne getInstance(){
        //调用方法之前,先判断
        //如果没有初始化,将其进行初始化,并且赋值
        //将该实例缓存好
        if(lazy == null){
            //两个线程都会进入这个if里面
            lazy = new LazyOne();
        }
        //如果已经初始化,直接返回之前已经保存好的结果
        return lazy;
    }
}

打印结果发现存在不同实例,说明饿汉式单例是线程不安全
怎么变为安全的呢!我们通常可以这样设计饿汉式单例(在获取实例方法上加上synchronized 锁)

public class LazyTwo {

    private LazyTwo(){}

    private static LazyTwo lazy = null;

    public static synchronized LazyTwo getInstance(){

        if(lazy == null){
            lazy = new LazyTwo();
        }
        return lazy;
    }
}

然后我们将测试工具类实例改为:
运行发现,无论怎么运行,程序始终拿到的是一个实例,说明synchronized 锁是可以保证线程安全的!
但是synchronized 性能并不是最好的锁!

我们看这样的测试:

public static void main(String[] args) {
        long start = System.currentTimeMillis();
        for (int i = 0; i < 200000000;i ++) {
            Object obj = LazyTwo.getInstance();
        }
        long end = System.currentTimeMillis();
        System.out.println("总耗时:" + (end - start));
    }

运行发现每次获取的时间性能较低,由此产生另一种单例,内部类单例模式

内部类单例

特点:

  1. 在外部类被调用的时候内部类才会被加载
  2. 内部类一定是要在方法调用之前初始化
  3. 巧妙地避免了线程安全问题
  4. 这种形式兼顾饿汉式的内存浪费,也兼顾synchronized性能问题,完美地屏蔽了这两个缺点
// 史上最牛B的单例模式的实现方式
public class LazyThree {

    private boolean initialized = false;

    //每一个关键字都不是多余的
    //static 是为了使单例的空间共享
    //保证这个方法不会被重写,重载
    public static final LazyThree getInstance(){
        //在返回结果以前,一定会先加载内部类
        return LazyHolder.LAZY;
    }

    //默认不加载
    private static class LazyHolder{
        private static final LazyThree LAZY = new LazyThree();
    }

}

注册登记式单例

  • 每使用一次,都往一个固定的容器中去注册并且将使用过的对象进行缓存,下次取对象就直接从缓存中取值,以保证每次获取的都是同一个对象
  • IOC中的单例模式,就是典型的注册登记式单例

基于map形式的注册单例:

public class RegisterMap {

   private RegisterMap(){}

   private static Map<String,Object> register = new ConcurrentHashMap<String,Object>();

   public static RegisterMap getInstance(String name){
       if(name == null){
           name = RegisterMap.class.getName();
       }

       if(register.get(name) == null){
           try {
               register.put(name, new RegisterMap());
           }catch(Exception e){
               e.printStackTrace();
           }
       }
       return (RegisterMap)register.get(name);
   }

}

枚举式单例

使用常量值来保证对象的唯一,实际上就是注册登记式的一种


public enum DataSourceEnum {
    DATASOURCE;
    private DBConnection connection = null;
    private DataSourceEnum() {
        connection = new DBConnection();
    }
    public DBConnection getConnection() {
        return connection;
    }
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章