Java/Kotlin 单例模式

设计模式对于编写程序来说十分重要,它是一种编写技巧,也是一种艺术理念。

单例模式

指一个类只有一个实例,且该类能自行创建这个实例的一种模式。
单例模式的 3 个特点:
1.单例类只有一个实例对象;
2.该单例对象必须由单例类自行创建;
3.单例类对外提供一个访问该单例的全局访问点

单例模式的结构与实现

单例模式分为两个部分:
1、单例类:包含一个实例且能自行创建这个实例的类
2、访问类:使用单例的类

单例模式的两种实现形式:
1、懒汉式单例

class LazySingleton {
    private static LazySingleton instance = null;
    private LazySingleton() {}
    public static LazySingleton getInstance() {
        if (null==instance) {
            instance = new LazySingleton();
        }
        return instance;
    }
}

从代码中可以看到,在类加载时,并没有生成单例,静态成员变量赋值为null,在第一次调用getInstance时创建实例。
2、饿汉式单例

class HungrySingleton {
    private static HungrySingleton instance = new HungrySingleton();
    private HungrySingleton() {}
    public static HungrySingleton getInstance() {
        return instance;
    }
}

从代码中可以看到,类创建的同时创建里一个静态对象,这种用法的好处是静态对象创建之后不会再改变,所以它是线程安全的,可以直接用于多线程。
那么懒汉式有没有办法做到线程安全呢?当然可以:

class LazySingleton {
    private static volatile LazySingleton instance = null;
    private LazySingleton() {}
    public static LazySingleton getInstance() {
        if (null==instance) {
            synchronized (LazySingleton.class) {
                if (null==instance) {
                    instance = new LazySingleton();
                }
            }
        }
        return instance;
    }
}

上面的代码使用了双重检查加锁来确保线程安全,首先为什么不在getInstance直接加上synchronized关键字呢?
首先确保线程安全是确保instance的初始化线程安全,如果instance已经完成初始化了,之后的getInstance将会直接返回,但是由于synchronized此方法被上锁了,所以对性能产生影响。
接下来分析双重检查加锁,线程1进入getInstance,由于instance为null,线程1进入synchronized块,线程1让出cpu给到线程2,线程2进入getInstance方法,instance为null,尝试获取锁,获取失败进入阻塞,线程2让出cpu,线程1执行,初始化instance,退出synchronized并返回,线程2获取锁之后检查instance是否为null,由于此时instance已经初始化结束,线程2返回。
以为这样就结束了?并没有,由于java内存模型运行无序写入,双重检查加锁并不能保证一定顺利进行。
原因:在初始化阶段,线程1在构造函数执行之前,使得实例成为非null,并让出cpu,线程2检查实例非null直接返回构造结束但只有部分初始化instance引用对象。
所以,相对于通常懒汉式单例,双重检查加锁模式在instance声明上加上了volatile关键字,volatile关键字的一个功能就是禁止指令重排。

案例:

    enum Subject {
        // 班主任,语文老师,数学老师,英语老师
        HEADMASTER,CHINESE,MATH,ENGLISH
    }

    static class Student {
        public Teacher headMaster;
        public Teacher english;
        public Teacher chinese;
        public Teacher math;
        public Student() {}
    }

    static class Teacher {
        private static Teacher instance = null;
        public Subject subject;
        public String name;
        private Teacher() {
        }
        private Teacher(Subject subject, String name) {
            this.subject = subject;
            this.name = name;
        }
        public static Teacher getInstance() {
            if (null==instance) {
                instance = new Teacher(Subject.HEADMASTER,"老谷");
            }
            return instance;
        }
    }

上面新建了两个类和一个枚举,教师类是单例类,而学生类是访问类,接下来看测试代码:

    @Test
    public void main() {
        final int studentNum = 20;

        List students = new ArrayList<>(studentNum);

        for (int i = 0; i < studentNum; i++) {
            students.add(new Student());
        }

        for (int i = 0; i < studentNum; i++) {
            students.get(i).headMaster = Teacher.getInstance();
        }

        for (int i = 0; i < studentNum; i+=4) {
            System.out.println(String.format("学生编号:%d 的班主任是:%s 存储地址是:%s"
                    ,i
                    ,students.get(i).headMaster.name
                    ,students.get(i).headMaster));
        }
    }

----------------------------------------------------------------------
输出结果:
学生编号:0 的班主任是:老谷 存储地址是:$Teacher@1b9e1916
学生编号:4 的班主任是:老谷 存储地址是:$Teacher@1b9e1916
学生编号:8 的班主任是:老谷 存储地址是:$Teacher@1b9e1916
学生编号:12 的班主任是:老谷 存储地址是:$Teacher@1b9e1916
学生编号:16 的班主任是:老谷 存储地址是:$Teacher@1b9e1916
----------------------------------------------------------------------

从上面的例子可以看到,所有的学生都拥有同一个班主任,老师类不是唯一的,但是对于学生来说,班主任是唯一的,语文老师,数学老师,英语老师也是唯一的,扩展:

    static class Teacher {
        private static Map instance = new HashMap<>(8);
        public Subject subject;
        public String name;
        private Teacher(Subject subject, String name) {
            this.subject = subject;
            this.name = name;
        }
        public static Teacher getInstance(Subject subject) {
            Teacher teacher = instance.get(subject);
            if (null==teacher) {
                switch (subject) {
                    case HEADMASTER:
                        teacher = new Teacher(subject,"老谷");
                        break;
                    case CHINESE:
                        teacher = new Teacher(subject,"老刘");
                        break;
                    case MATH:
                        teacher = new Teacher(subject,"老李");
                        break;
                    case ENGLISH:
                        teacher = new Teacher(subject,"老张");
                        break;
                    default:
                        throw new IllegalArgumentException("Error subject!");
                }
                instance.put(subject, teacher);
            }
            return teacher;
        }
    }

    @Test
    public void main() {
        final int studentNum = 20;

        List students = new ArrayList<>(studentNum);

        for (int i = 0; i < studentNum; i++) {
            students.add(new Student());
        }

        for (int i = 0; i < studentNum; i++) {
            students.get(i).headMaster = Teacher.getInstance(Subject.HEADMASTER);
            students.get(i).chinese = Teacher.getInstance(Subject.CHINESE);
            students.get(i).math = Teacher.getInstance(Subject.MATH);
            students.get(i).english = Teacher.getInstance(Subject.ENGLISH);
        }

        for (int i = 0; i < studentNum; i+=10) {
            Student student = students.get(i);
            System.out.println(String.format("学生编号:%d 的班主任是:%s 存储地址是:%s , " +
                            "语文老师是:%s 存储地址是:%s ," +
                            "数学老师是:%s 存储地址是:%s ," +
                            "英语老师是:%s 存储地址是:%s ."
                    ,i
                    ,student.headMaster.name
                    ,student.headMaster
                    ,student.chinese.name
                    ,student.chinese
                    ,student.math.name
                    ,student.math
                    ,student.english.name
                    ,student.english));
        }
    }

----------------------------------------------------------------------
学生编号:0 的班主任是:老谷 存储地址是:$Teacher@3b764bce , 语文老师是:老刘 存储地址是:$Teacher@759ebb3d ,数学老师是:老李 存储地址是:$Teacher@484b61fc ,英语老师是:老张 存储地址是:$Teacher@45fe3ee3 .
学生编号:10 的班主任是:老谷 存储地址是:$Teacher@3b764bce , 语文老师是:老刘 存储地址是:$Teacher@759ebb3d ,数学老师是:老李 存储地址是:$Teacher@484b61fc ,英语老师是:老张 存储地址是:$Teacher@45fe3ee3 .
----------------------------------------------------------------------

为将单例类改为多例类,以静态hashmap保存teacher实例,以subject作为key值,从代码的输出可以看到,每个学生都拥有4个单例对象,分别对应班主任、语文、数学、英语,四位老师。

kotlin实现

饿汉式:

    object Teacher {
        val subject = Subject.HEADMASTER
        val name = "老谷"
    }

懒汉式:

    class Teacher private constructor(val subject: Subject,val name:String) {
        companion object {
            private var instance:Teacher?=null
                get() {
                    if (field==null) {
                        field = Teacher(Subject.HEADMASTER,"老谷")
                    }
                    return field
                }
            fun getSingleton():Teacher {
                return instance!!
            }
        }
    }

线程安全式:

    class Teacher private constructor(val subject: Subject,val name:String) {
        companion object {
            val instance:Teacher by lazy(mode=LazyThreadSafetyMode.SYNCHRONIZED) {
                Teacher(Subject.HEADMASTER,"老谷")
            }
        }
    }

你可能感兴趣的:(Java/Kotlin 单例模式)