单例模式:保证类在内存中只有一个对象。
分为:
饿汉式:类一加载就创建对象 经典案例:JDK中的Runtime类
懒汉式:用的时候,才去创建对象
如何保证类在内存中只有一个对象呢?
A:把构造方法私有
B:在成员位置自己创建一个对象
C:通过一个公共的方法提供访问单例模式:保证类在内存中只有一个对象。
A.饿汉式(是不会出问题的单例模式):
public class Student {
// 构造私有
private Student() {
}
// 自己造一个
// 静态方法只能访问静态成员变量,加静态
// 为了不让外界直接访问修改这个值,加private
private static Student s = new Student();
// 提供公共的访问方式
// 为了保证外界能够直接使用该方法,加静态
public static Student getStudent() {
return s;
}
}
测试类:
public class StudentDemo {
public static void main(String[] args) {
Student s1 = new Student();
Student s2 = new Student();
System.out.println(s1 == s2); // false 非单例模式
Student s1 = Student.getStudent();
Student s2 = Student.getStudent();
System.out.println(s1 == s2); // true 饿汉式单例模式
System.out.println(s1); // null //Student@175078b
System.out.println(s2); // null //Student@175078b
}
}
B.懒汉式(可能会出问题的单例模式):
A:懒加载(延迟加载)
B:线程安全问题
public class Teacher {
private Teacher() {
}
private static volatile Teacher t = null;
// volatile关键字在下方解释
public static Teacher getTeacher() {
if (t == null) { //第一次判断,提高效率
synchronized (Teacher.class) {
if(t == null) { //第二次判断,实现单例
t = new Teacher();
}
}
}
return t;
}
}
测试类:
public class TeacherDemo {
public static void main(String[] args) {
Teacher t1 = Teacher.getTeacher();
Teacher t2 = Teacher.getTeacher();
System.out.println(t1 == t2); // true 懒汉式单例模式
System.out.println(t1); //Teacher@175078b
System.out.println(t2); //Teacher@175078b
}
}
后记:关于volatile关键字
t = new Teacher();这行代码中,他的指令大致可分为三步:
1:给对象分配地址空间。
2:初始化对象。
3:将 t 引用指向刚刚分配的地址。
编译器会对指令进行重排序,从而导致部分JIT编译器会出现获得的对象是一个还未经过初始化的对象。重排序大概可以理解为:处理器不会按照你代码的顺序去执行,而是根据一定的优化规律,在几个指令的执行结果不互相 影响的情况下将其重新排序再执行。
t = new Teacher();这行代码中,他的指令大致可分为三步:
1:给对象分配地址空间。
2:初始化对象。
3:将 t 引用指向刚刚分配的地址。
编译器会对指令进行重排序,从而导致部分JIT编译器会出现获得的对象是一个还未经过初始化的对象。重排序大概可以理解为:处理器不会按照你代码的顺序去执行,而是根据一定的优化规律,在几个指令的执行结果不互相 影响的情况下将其重新排序再执行。
因为第一步的执行结果跟第二三步是紧密相连的,换句话说就是只有分配了空间,才能有空间进行初始化,引用才能指向分配的地址,也就是说第一步的执行结果影响到了第二三步,所以第一步肯定是先执行。
而第二三步之间没有必然的联系,就算是交换了顺序,最终的结果还是一样,因此处理器有可能先执行了第三步,执行第三步后也就是说 t 已经不为null了,此时一个新的线程进入第一次判断,结果为false,便return了该对象,可是此时该对象并没有进行初始化!
解决这个问题的方法便是用volatile关键字去修饰 t 变量
该关键字的一个重要作用就是禁止指令重排序优化,普通的变量仅仅会保证该方法的执行过程中所有依赖赋值结果的地方都能获取到正确的结果,而不能保证变量赋值操作顺序与程序代码中的执行顺序一致。