首页 > 其他 > 详细

01 设计模式-单例模式

时间:2020-06-18 19:58:57      阅读:40      评论:0      收藏:0      [点我收藏+]

单例模式

饿汉式

package com.hxh;

public class Hungry {
    private static final Hungry HUNGRY = new Hungry();

    private Hungry() {
        System.out.println("创建");
    }

    public static Hungry getInstance() {
        return HUNGRY;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                System.out.println(Hungry.getInstance());
            }).start();
        }
    }
}

注意事项:

  1. 变量HUNGRY必须是:私有,静态,不可变;
  2. 构造器私有;
  3. 公开的getInstance方法,使外界可以获取变量。

懒汉式

package com.hxh;

public class LazyMan {
    private static LazyMan lazyMan;

    private LazyMan() {
        System.out.println("LazyMan被创建");
    }

    public static LazyMan getInstance() {
        if (lazyMan == null) {
            lazyMan = new LazyMan();
        }
        return lazyMan;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            System.out.println(LazyMan.getInstance());
        }
    }
    
}

输出如下,单线程下看起来毫无问题:

LazyMan被创建
com.hxh.LazyMan@39a054a5
com.hxh.LazyMan@39a054a5
com.hxh.LazyMan@39a054a5
com.hxh.LazyMan@39a054a5
com.hxh.LazyMan@39a054a5
com.hxh.LazyMan@39a054a5
com.hxh.LazyMan@39a054a5
com.hxh.LazyMan@39a054a5
com.hxh.LazyMan@39a054a5
com.hxh.LazyMan@39a054a5

修改main函数,改为多线程

public static void main(String[] args) {
    for (int i = 0; i < 10; i++) {
        new Thread(() -> {
            System.out.println(LazyMan.getInstance());
        }).start();
    }
}

技术分享图片

从上图可以看出,一共创建了4个对象,并不是单例的。

通过修改获取实例方法getInstance,给对象加锁解决上面的问题:

public static LazyMan getInstance() {
    if (lazyMan == null) {
        synchronized (LazyMan.class) {
            if (lazyMan == null) {
                lazyMan = new LazyMan();
            }
        }
    }
    return lazyMan;
}

输出如下:

LazyMan被创建
com.hxh.LazyMan@33ca79ec
com.hxh.LazyMan@33ca79ec
com.hxh.LazyMan@33ca79ec
com.hxh.LazyMan@33ca79ec
com.hxh.LazyMan@33ca79ec
com.hxh.LazyMan@33ca79ec
com.hxh.LazyMan@33ca79ec
com.hxh.LazyMan@33ca79ec
com.hxh.LazyMan@33ca79ec
com.hxh.LazyMan@33ca79ec

现在看起来好像并没有什么问题。但是lazyMan = new LazyMan();这并不是一个原子性操作,极端情况下可能会发生指令重排,那么什么是指令重排呢?

指令重排:

  1. JVM创建对象分为三步:
    1. 分配内存空间
    2. 执行构造方法、创建实例
    3. 将内存与实例关联
  2. 问题就出现在上面的步骤中,我们所预期的步骤是123,而编译器有时为了优化性能,会选择其他的顺序执行上面的步骤。在单线程下没有影响,而多线程时就会出现问题,如:
    1. 线程a执行步骤为132;
    2. 线程b执行步骤为123;
    3. 在线程a执行到3时,b线程开始执行,发现对象不为空之后就开始执行下面的语句,可能就会出现异常,因为a线程还未将对象初始化成功。

解决办法也很简单,为lazyMan对象添加volatitle关键字,保证不会出现指令重排

完整代码如下:

package com.hxh;

public class LazyMan {
    private volatile static LazyMan lazyMan;

    private LazyMan() {
        System.out.println("LazyMan被创建");
    }

    public  static LazyMan getInstance() {
        if (lazyMan == null) {
            synchronized (LazyMan.class) {
                if (lazyMan == null) {
                    lazyMan = new LazyMan();
                }
            }
        }
        return lazyMan;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                System.out.println(LazyMan.getInstance());
            }).start();
        }
    }

}

01 设计模式-单例模式

原文:https://www.cnblogs.com/shimeath/p/13159328.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!