设计模式对于编写程序来说十分重要,它是一种编写技巧,也是一种艺术理念。
单例模式
指一个类只有一个实例,且该类能自行创建这个实例的一种模式。
单例模式的 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,"老谷")
}
}
}