博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
单例模式
阅读量:4539 次
发布时间:2019-06-08

本文共 4273 字,大约阅读时间需要 14 分钟。

为什么需要单例模式

  1. 如果整个程序运行时有一个实例就够用了,何必创建出来多个实例浪费内存呢;
  2. 有些保存数据的实例在程序中只有一份即可,有多份的话可能导致数据不一致问题;

实现

1. 非线程安全的懒汉模式

/** * Created by clearbug on 2018/3/10. * * singleton, [n] 独生子, 独身, (扑克牌)单张。在这里即表示“单例”的意思 */public class Singleton {    private static Singleton instance;        // 构造器私有化    private Singleton() {}    public static Singleton getInstance() {        if (instance == null) {            instance = new Singleton();        }        return instance;    }}

优点:

  • 简单易懂;

缺点:

  • 获取实例的方法 getInstance 线程不安全,多线程环境下可能导致创建了多个实例;

2. 线程安全的懒汉模式(单锁)

/** * Created by clearbug on 2018/3/10. * * singleton, [n] 独生子, 独身, (扑克牌)单张。在这里即表示“单例”的意思 */public class Singleton {    private static Singleton instance;    // 构造器私有化    private Singleton() {}    public static synchronized Singleton getInstance() {        if (instance == null) {            instance = new Singleton();        }        return instance;    }}

这里保证线程安全的方法很简单:就是在获取实例的方法 getInstance 上使用 synchronized 加锁!这样虽然是保证了线程安全,但是每个线程来获取实例是都要经过“获取锁”、“释放锁”两个步骤,无疑效率大打折扣!那么,怎么做到既能保证线程安全又可以尽可能的提高效率呢?这就要说到 DCL(double-checked locking),双重校验锁机制了。

3. 线程安全的懒汉模式(单锁,双重校验)

/** * Created by clearbug on 2018/3/10. * * singleton, [n] 独生子, 独身, (扑克牌)单张。在这里即表示“单例”的意思 */public class Singleton {    private static volatile Singleton instance;    // 构造器私有化    private Singleton() {}    public static Singleton getInstance() {        if (instance == null) {            synchronized (Singleton.class) {                if (instance == null) {                    instance = new Singleton();                }            }        }        return instance;    }}

这种写法相比上两种稍微有点复杂:

  1. 首先,instance 字段要用 volatile 关键字修饰,volatile 关键字的作用就是当某个线程修改了 instance 字段的内容后,其他线程能立即看到 instance 字段的最新值;
  2. 单锁锁定的还是类 Singleton;
  3. 双重校验则是在锁外和锁内均对 instance 是否为 null 做判断,以防止刚开始有多个线程跳过了第一层校验然后又都去初始化 instance 字段的问题;
  4. 在 1.4 及更早版本的 Java 中,JVM 对于 volatile 关键字的实现会导致双重检查加锁的失败;

4. 线程安全的懒汉模式(静态类内部加载)

/** * Created by clearbug on 2018/3/10. * * singleton, [n] 独生子, 独身, (扑克牌)单张。在这里即表示“单例”的意思 */public class Singleton {    private static class SingletonHolder {        public SingletonHolder() {            System.out.println("SingletonHolder constructor method.");        }        private static Singleton instance = new Singleton();    }    private Singleton() {        System.out.println("2. Singleton constructor method.");    }    public static Singleton getInstance() {        System.out.println("1. Singleton getInstance method.");        return SingletonHolder.instance;    }    public static void main(String[] args) throws InterruptedException {        System.out.println(Singleton.getInstance());    }}

运行结果:

1. Singleton getInstance method.2. Singleton constructor method.Singleton@60e53b93

instance 字段被 SingletonHolder 类包装了,并且只是在 Singleton 调用 getInstance 方法时才进行了初始化!并且这种方式也是线程安全的,为啥说它线程安全呢?我的理解是类中的静态字段、静态代码区的代码都是由 JVM 自身调度执行的,所以不存在用户多线程竞争问题!

以上就是懒汉模式的四种写法了,下面接着说饿汉模式。

5. 线程安全的饿汉模式(静态字段初始化)

/** * Created by clearbug on 2018/3/10. * * singleton, [n] 独生子, 独身, (扑克牌)单张。在这里即表示“单例”的意思 */public class Singleton {    private static Singleton instance = new Singleton();    private Singleton() {        System.out.println("2. Singleton constructor method.");    }    public static Singleton getInstance() {        System.out.println("1. Singleton getInstance method.");        return instance;    }    public static void main(String[] args) {        System.out.println(Singleton.getInstance());    }}

运行结果:

2. Singleton constructor method.1. Singleton getInstance method.Singleton@60e53b93

instance 字段在 JVM 加载类 Singleton 时就已经执行了初始化过程,所以是线程安全的!只是如果程序运行期根本没有用到这个实例的话,会造成内存浪费!

6. 线程安全的饿汉模式(枚举类)

/** * Created by clearbug on 2018/3/10. * * singleton, [n] 独生子, 独身, (扑克牌)单张。在这里即表示“单例”的意思 */public enum  Singleton {    INSTANCE;    public void otherMethod() {        System.out.println("otherMethod");    }    public static void main(String[] args) {        Singleton.INSTANCE.otherMethod();    }}

这种方法我之前没咋听说过,看下面参考博客里面 cielosun 这位老铁说:“Effective Java作者Josh Bloch 提倡的方式,在我看来简直是来自神的写法”,听起来很牛逼的样子!

这种方法也是线程安全的,我的理解是枚举类的字段也是 JVM 在加载该类时初始化的,所以也不存在用户多线程竞争问题!而且,由于枚举类的特性,就算是反序列化之后运行的 JVM 中也只有一个实例。
以上就是饿汉模式的两种写法,下面说说单例模式的一些注意点吧:

  1. 序列化和反序列化问题对单例的影响,可以通过修改 readResolve 方法来解决;
  2. 多个类加载器加载对单例的影响?尴尬,忽然想起来我对类加载器的知识也不大熟练就先不说了吧!
  3. 通过反射调用单例类的私有构造方法对单例的影响,可以通过修改构造方法,以便第二次创建实例时抛出异常!

参考

转载于:https://www.cnblogs.com/optor/p/8542359.html

你可能感兴趣的文章
c - 给分数分级别
查看>>
chrome 调试
查看>>
luoguP2774 方格取数问题
查看>>
tcp/ip协议各层的理解与
查看>>
python中的setdefault()方法
查看>>
转 VSFTP用户权限管控
查看>>
poj2420 A Star not a Tree? 模拟退火
查看>>
微信小程序--登录授权,一键获取用户微信手机号并登录
查看>>
[转载] C#面向对象设计模式纵横谈——13. Proxy代理模式
查看>>
JqueryEasyUI浅谈---视频教程公布
查看>>
ASP.NET导出Excel,打开时提示“您尝试打开文件'XXX.xls'的格式与文件扩展名指定文件不一致”...
查看>>
Javaweb之 servlet 开发详解1
查看>>
Restore IP Addresses
查看>>
DWR框架简单应用
查看>>
KMP 学习心得-----转
查看>>
time.strftime:格式化字符串中含中文报错处理
查看>>
模态窗口缓存无法清除怎么办? 在地址上加个随机数吧"&rd=" + new Date().getTime()
查看>>
阿里的weex框架到底是什么
查看>>
Tesis enDYNA
查看>>
FxZ,C#开发职位面试测试题(30分钟内必须完成)
查看>>