单例模式详解

单例模式详解

定义: 一个类最多只有一个实例,并提供一个全局访问点。就好像我们的 Windows 系统的任务管理器,你只能开一个,不能开多个。
单例模式的种类: 懒汉式饿汉式
实现的必要条件: 私有化构造、静态全局访问
暴力破解: 反射
使用场景:

  • 要求生成唯一序列号的环境
  • 在整个项目中需要一个共享访问点或共享数据,例如一个 Web 页面上的计数器,可以不用把每次刷新都记录到数据库中,使用单例模式保持计数器的值,并确保是线程安全的;
  • 创建一个对象需要消耗的资源过多,如要访问 IO 和数据库等资源;
  • 需要定义大量的静态常量和静态方法(如工具类)的环境,可以采用单例模式(当然,也可以直接声明为 static 的方式)。

1.饿汉式单例

public class SingletonHungry {
    private SingletonHungry(){};
    private static final SingletonHungry INSTANCE = new SingletonHungry();

    public SingletonHungry getSingLeHungry() {
        return INSTANCE;
    }
}

特点:
       (1) 类一创建就生成了对象并加载到内存,会造成内存的浪费。
       (2) 线程安全

2.懒汉式单例

2.1.普通方式实现

public class SingletonLazy {
    private SingletonLazy() {}
    private static SingletonLazy instance = null;

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

特点:
       (1) 实现简单
       (2) 线程不安全,适合单线程下使用

2.2.实例化方法加锁的实现

public class SingletonLazy_Synchronized {
    private SingletonLazy_Synchronized() {}
    private static SingletonLazy_Synchronized instance = null;

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

       (1) 线程安全
       (2) 若频繁调用 getInstance() 方法,不管是否已实例化对象都会唤醒阻塞线程,频繁线程上下文切换导致性能极低。

2.3.双检锁实现

public class SingletonLazy_DoubleCheck {
    private SingletonLazy_DoubleCheck() {}
    private static volatile SingletonLazy_DoubleCheck INSTANCE= null;

    public static SingletonLazy_DoubleCheck getInstance() {
        // 实例未被创建,开启同步代码块准备创建
        if (INSTANCE == null) {
            synchronized (SingletonLazy_DoubleCheck.class) {
                // 也许其他线程在判断完后已经创建,再次判断
                if (INSTANCE == null) {
                    INSTANCE = new SingletonLazy_DoubleCheck();
                }
            }
        }
        return INSTANCE;
    }
}

注意事项: 单例对象必须使用 volatile 修饰保证可见性。否则可能出现指令重排引起的线程安全问题。

new 对象分三步,第二三步可能发生指令重排:
memory = allocate(); // 1.分配对象内存空间
instance(memory); // 2.初始化对象
instance = memory; // 3.设置instance指向刚分配的内存地址,此时 instance != null

特点:
       (1) 线程安全
       (2) 只有第一次创建对象时才会加锁同步,使之线程安全的同时还能避免每次获取单例对象时的性能消耗

2.4.静态内部类实现

public class SingletonLazy_StaticInnerClass {
    private SingletonLazy_StaticInnerClass() {}
    // 静态内部类,实例化时自动生成对象
    private static class LazyHolder {
        private static final SingletonLazy_StaticInnerClass INSTANCE = new SingletonLazy_StaticInnerClass();
    }

    public static SingletonLazy_StaticInnerClass getInstance() {
        return LazyHolder.INSTANCE;
    }
}

特点:
       (1) 线程安全
       (2) 无需加锁,静态内部类第一次被加载时创建对象。保证线程安全的同时还没有加锁时带来的性能消耗,同时也是对象被使用时才创建对象。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值