设计模式(一)单例模式

  • 一、什么是单例模式
  • 二、单例模式的8种写法
  • (1)饿汉式
  • (2)静态语句块实现
  • (3)懒汉式(lazy loading)
  • (4)懒汉式升级版
  • (5)在方法4的基础上在升级
  • (6)懒汉式完美级写法(双重检查)
  • (7)单例最完美的写法
  • (8)完美中的完美写法


一、什么是单例模式

单例模式是java设计模式中比较常见的一种设计模式,本文介绍8中单例模式的创建以及发展流程。
1.特点
(1)单例类只能有一个实例。
(2)单例类必须自己创建自己的唯一实例。
(3)单例类必须给所有其他对象提供这一实例。

二、单例模式的8种写法

(1)饿汉式

类加载到内存后,就会实例一个单例,JVM保证线程安全
特点:
简单实用,推荐使用,但是不管是否用到,类加载时完成实例化

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

    private Mgr01(){}

    public static Mgr01 getINSTANCE() {
        return INSTANCE;
    }
    public void m(){
        System.out.println("m");
    }

    public static void main(String[] args) {
        Mgr01 m1=Mgr01.getINSTANCE();
        Mgr01 m2=Mgr01.getINSTANCE();
        System.out.println(m1==m2);
    }
}

注意
如果在其他类中加载不能直接new,需要直接调用getINSTANCE() 方法

public class SingletonMain {
    public static void main(String[] args) {
        Mgr01 mgr01=Mgr01.getINSTANCE();
        mgr01.m();;
    }
}
(2)静态语句块实现

和饿汉式差不多的实现,主要是通过静态语句块来进行初始化

public class Mgr02 {

    private static final Mgr02 INSTANCE;

    static {
        INSTANCE = new Mgr02();
    }

    public static Mgr02 getINSTANCE() {
        return INSTANCE;
    }

    public void m() {
        System.out.println("m");
    }
}
(3)懒汉式(lazy loading)

特点:什么时候需要,什么时候加载,但是线程不安全

public class Mgr03 {
    private static Mgr03 INSTANCE;

    private Mgr03() {
    }
    //多线程访问的时候,会影响
    public static Mgr03 getINSTANCE() {
        if (INSTANCE == null) {
            try {
                Thread.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            INSTANCE = new Mgr03();
        }
        return INSTANCE;
    }

    public void m() {
        System.out.println("m");
    }

    public static void main(String[] args) {
        //测试,hashcode会出现不同,相同对象的hashcode相同
        for (int i = 0; i < 1000; i++) {
            new Thread(() -> {
                System.out.println(Mgr03.getINSTANCE().hashCode());
            }).start();
        }
    }
}
(4)懒汉式升级版

为了解决懒汉式的线程安全问题,给getINSTANCE方法加上了锁,虽然解决了线程安全问题,但是降低了效率

public class Mgr04 {
    //优化版,加锁,效率低了
    private static Mgr04 INSTANCE;

    private Mgr04() {
    }

    public static synchronized Mgr04 getINSTANCE() {
        if (INSTANCE == null) {
            try {
                Thread.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            INSTANCE = new Mgr04();
        }
        return INSTANCE;
    }

    public void m() {
        System.out.println("m");
    }

    public static void main(String[] args) {
        //测试,hashcode会出现不同,相同对象的hashcode相同
        for (int i = 0; i < 1000; i++) {
            new Thread(() -> {
                System.out.println(Mgr04.getINSTANCE().hashCode());
            }).start();
        }
    }
}
(5)在方法4的基础上在升级

经过(4)的优化后,带来了效率低的问题。就有人问了,能不能有既保证线程安全的情况下,又提高了效率呢?所有又有了一个升级版,但是却不行

public class Mgr05 {
    private static Mgr05 INSTANCE;

    private Mgr05() {
    }
    //多线程访问的时候,会影响
    public static  Mgr05 getINSTANCE() {
        if (INSTANCE == null) {
            //妄图通过减小同步代码块的方式提高效率,然后不可行
        synchronized (Mgr05.class){
                try {
                    Thread.sleep(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                INSTANCE = new Mgr05();
            }
        }
        return INSTANCE;
    }
    public static void main(String[] args) {
        //测试,hashcode会出现不同,相同对象的hashcode相同
        for (int i = 0; i < 1000; i++) {
            new Thread(()->{
                System.out.println(Mgr05.getINSTANCE().hashCode());
            }).start();
        }
    }
}

不行的原因:当线程1还未结束的时候,线程2进入了方法,这时候,线程1未结束,INSTANCE 还是为空的,这时候线程2,就会跳过这个判断,等待线程1结束后,再创建实例

(6)懒汉式完美级写法(双重检查)

这时候就有人想吹毛求疵了,能不能解决上面的问题,实现单例呢?有

public class Mgr06 {
    private static volatile Mgr06 INSTANCE;

    private Mgr06() {
    }
    //完美写法
    public static Mgr06 getINSTANCE() {
        if (INSTANCE == null) {
         //双重检查
            synchronized (Mgr06.class) {
                if (INSTANCE == null) {
                    try {
                        Thread.sleep(2);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    INSTANCE = new Mgr06();
                }
            }
        }
        return INSTANCE;
    }

    public static void main(String[] args) {
        //测试,hashcode会出现不同,相同对象的hashcode相同
        for (int i = 0; i < 1000; i++) {
            new Thread(() -> {
                System.out.println(Mgr06.getINSTANCE().hashCode());
            }).start();
        }
    }
}

补充:通过双重检查,就能完美解决线程安全问题。细心的小伙伴就发现了,能不能直接写一个检查,写在方法内部?答案肯定是不行的。为啥呢?当然为了效率…

(7)单例最完美的写法

使用静态内部类的方法,JVM保证了线程安全,加载外部类的时候不会加载内部类,这样可以实现懒加载。最完美的写法,比饿汉式完美。只有去调用getInstance时才去加载实例

public class Mgr07 {

    private Mgr07() {
    }

    private static class Mgr07Holder {
        private final static Mgr07 INSTANCE = new Mgr07();
    }

    //调用getInstance时才去加载实例
    public static Mgr07 getInstance() {
        return Mgr07Holder.INSTANCE;
    }

    public static void main(String[] args) {
        //测试,hashcode会出现不同,相同对象的hashcode相同
        for (int i = 0; i < 1000; i++) {
            new Thread(() -> {
                System.out.println(Mgr07.getInstance().hashCode());
            }).start();
        }
    }
}
(8)完美中的完美写法

这种写法很少用到,但确实理想中的完美级。不仅可以解决线程安全问题,还可以防止反序列化。主要原因:枚举类没有构造方法

public enum  Mgr08 {
    INSTANCE;

    public static void main(String[] args) {
        //测试,hashcode会出现不同,相同对象的hashcode相同
        for (int i = 0; i < 1000; i++) {
            new Thread(() -> {
                System.out.println(Mgr08.INSTANCE.hashCode());
            }).start();
        }
    }
}