Java 设计模式之单例(Singleton)

单例:保证一个类仅有一个实例,并提供一个访问它的全局访问点。

对于代码中一些不需要区分的对象,如果每次使用都 new 出一个,会太占用内存,所以我们有必要将该对象设计成单例模式。

单例模式中有“饿汉式”和“懒汉式”两种写法。下面进行举例说明。

举例说明

编写一个 Singleton

public class SingleTon {
    public SingleTon() {
        System.out.println("---new---");
    }
}

饿汉模式

  • 定义一个静态的变量 Singleton,直接初始化
  • 定义一个静态方法返回单例对象
public class TestHungarySingleton {
    /**
     *
     * 饿汉模式:初始化时直接创建对象,不会有线程安全问题
     * */
    private static SingleTon s1 = new SingleTon();

    public static SingleTon getS1() {
        return s1;
    }


    public static void main(String[] args) {
        test();
    }

    public static void test(){
        for (int i = 0; i < 500; i++) {
            new Thread(){
                @Override
                public void run() {
                    getS1();
                }
            }.start();
        }
    }
}
// log 日志
---new---

我们发现即使在多线程的情况下,也能保证内存中只有一个 Singleton 实例存在。

懒汉模式

  • 声明一个静态的变量 Singleton,但不进行初始化
  • 定义一个静态方法返回单例对象,在方法中进行初始化
public class TestLazySingleton {
    /**
     * 懒汉模式:初始化不创建对象,在第一次调用时创建对象
     *
     * */
    private static SingleTon s2;
    // 线程不安全写法,有可能创建多个s2实例
    public static SingleTon getS2() {
        if (s2 == null) {
            s2 = new SingleTon();
        }
        return s2;
    }

    public static void main(String[] args) {
        test();
    }

    static void test() {
        for (int i = 0; i < 500; i++) {
            new Thread(){
                @Override
                public void run() {
                    getS2();
                }
            }.start();
        }
    }
}
// log 日志
---new---
---new---
---new---
---new---

通过日志,我们看到 Singleton 对象被多次创建。在高并发 的多线程情况下,这种方式显然不能满足单例的要求,于是就有了“双重锁”写法。

双重锁的具体写法是,在获取对象的时候,先判断该对象是否为空,不为空直接返回,否则就对该对象进行加锁,然后再次需要判断是否为空。具体代码如下:

public class TestLazySingleton {
    /**
     * 懒汉模式:初始化不创建对象,在第一次调用时创建对象
     *
     * */
    private static SingleTon s2;
    // 线程不安全写法,有可能创建多个s2实例
    public static SingleTon getS2() {
        if (s2 == null) {
            s2 = new SingleTon();
        }
        return s2;
    }

    // 线程安全写法:双重锁
    public static SingleTon getS20() {
        if (s2 == null) {
            synchronized (SingleTon.class) {
                if (s2 == null) {
                    s2 = new SingleTon();
                }
            }
        }
        return s2;
    }



    public static void main(String[] args) {
        test();
    }

    static void test() {
        for (int i = 0; i < 500; i++) {
            new Thread(){
                @Override
                public void run() {
//                    getS2();
                    getS20();
                }
            }.start();
        }
    }
}
// log 日志
---new---

如果吧上述代码中第二个判空条件去掉,测试后会发现结果不是单例模式。

为什么会出现这样的情况?

假设现场A、B同时请求加锁,此时两个线程都已经通过了第一个为空的判断。假设A线程先加锁,B线程等待,然后A线程去创建对象,A释放锁后,B线程加锁,又创建了一个对象。

文章只是作为个人记录学习使用,如有不妥之处请指正,谢谢。

扫描二维码关注公众号,回复: 1715631 查看本文章

参考文章

Java设计模式透析之 —— 单例(Singleton)

猜你喜欢

转载自blog.csdn.net/modurookie/article/details/80314183