文章目录
单例模式:
单个实例,保证一个实例,就要保证外界不能随便new这个对象,所以要私有化构造方法
*私有化构造方法后就是把new这个对象控制权收回了,
只能在类内部去实例化这个对象,
让类自身负责保存他的唯一实例。*
1、饿汉式
从时间空间上看:以空间换时间
从线程安全上看:安全的
public class Singleton1 {
//3、定义一个变量来存储创建好的类实例
// 不会有线程安全的问题,静态变量的赋值最终放到静态代码块中执行,可以认为静态代码块中的线程安全由java虚拟机保重
private static final Singleton1 INSTANCE = new Singleton1();
//1.私有化构造方法,好在内部控制创建实例的数目
private Singleton1() {
System.out.println("private Singleton1()");
}
//2、定义一个方法来为客户端提供类实例
//这个方法需要定义成类方法,也就是要加static
public static Singleton1 getInstance() {
return INSTANCE;
}
}
Singleton1 instance1= Singleton1.getInstance();
Singleton1 instance2= Singleton1.getInstance();
System.out.println(instance1==instance2);//true 是同一个对象
2、懒汉式
从时间空间上看:以时间换空间
从线程安全上看:不安全的(所以要加锁synchronized)
所以又叫“懒汉式”(也就是在用到的时候才去实例化)
// 懒汉式单例
public class Singleton2 {
//3、定义一个变量来存储创建好的类实例
private static Singleton2 INSTANCE = null;
//1.私有化构造方法,好在内部控制创建实例的数目
private Singleton2() {
System.out.println("private Singleton2()");
}
//2、定义一个方法来为客户端提供类实例
//这个方法需要定义成类方法,也就是要加static
// 多线程下要考虑线程安全问题 synchronized在Singleton2.class上加锁
public static synchronized Singleton2 getInstance() {
// 判断这个实例是不是有值
if (INSTANCE == null) {
// 如果没有,就创建一个类实例,并把值赋给存储类实例的变量
INSTANCE = new Singleton2();
}
return INSTANCE;
}
}
3、懒汉式优化:双重检查机制如果实例存在就没有必要走同步的必要,如果实例不存在才会进入同步块。
这样只会在第一次创建的时候同步一次,其余的时候不需要同步。
第二重检查是因为:当A线程执行 new Singleton()时候,B线程正在执行第一重检查if(instance == null)此时B线程会进入,
所以在synchronized中要再做一次检查。因为instance是volatile修饰的所以A线程new Singleton修改了instance的时候,
在B线程中能读取到instance不是null。
public class Singleton3 {
private Singleton3() {
System.out.println("private Singleton4()");
}
// volatile解决共享变量可见性,有序性(禁止指令重排),但是解决不了原子性
private static volatile Singleton3 INSTANCE = null;
public static Singleton3 getInstance() {
if (INSTANCE == null) {
synchronized (Singleton3.class) {
if (INSTANCE == null) {
INSTANCE = new Singleton3();
}
}
}
return INSTANCE;
}
}
为什么要加volatile?
原子操作:
简单来说,原子操作(atomic)就是不可分割的操作,在计算机中,就是指不会因为线程调度被打断的操作。
比如,简单的赋值是一个原子操作:m = 6; // 这是个原子操作
假如m原先的值为0,那么对于这个操作,要么执行成功m变成了6,要么是没执行 m还是0,而不会出现诸如m=3这种中间态,即使是在并发的线程中。
但是,声明并赋值就不是一个原子操作:int n=6;//这不是一个原子操作,对于这个语句,至少有两个操作:①声明一个变量n ②给n赋值为6。
这样就会有一个中间状态:变量n已经被声明了但是还没有被赋值的状态。在多线程中,由于线程执行顺序的不确定性,如果两个线程都使用m,就可能会导致不稳定的结果出现。
指令重排:
CPU乱序执行。乱序执行是相对于顺序执行来说的。计算机刚被发明的时候都是顺序执行,后来为了提升CPU运行效率,升级成了乱序执行。乱序执行就提高了运行效率,指令流水线。
指令重排:为了使处理器内部的运算单元能尽量被充分利用,处理器可能会对输入的代码进行乱序执行优化,处理器会在计算之后将乱序执行的结果重组,并确保这一结果和顺序执行结果是一致的,
但是这个过程并不保证各个语句计算的先后顺序和输入代码中的顺序一致,这就是指令重排序。简单来说,就是指你在程序中写的代码,在执行时并不一定按照写的顺序。
Java提供的反编译工具javap
\springboot\target\testclasses\com\gao\springboot\pattern>javap -c -v -p Singleton4.class
INSTANCE = new Singleton4();
CPU可能会对指令的执行次序做出优化,如果指令之间没有因果关系,CPU可能调换他们的执行次序,第17和21条指令是有因果关系的,对象内存没有分配就没法初始化成员变量,所以他们两个顺序是确定的。
而构造方法是做赋值操作的putstatic也是做赋值操作的,他们两个谁先执行谁后执行没有因果关系,CPU就可能会对代码执行顺序做出调整,可能先执行putstatic再执行构造,在单线程下面没有影响,但是在多线程下面就可以有问题。(多线程进入,因为必须先new一个对象,才能初始化成员变量,同时给地址赋值,但是给地址赋值和初始化没有顺序,正常来说都能执行,但是因为是多线程,当地址已经创建并赋值(指令重排会出现这种情况),在线程2中,地址已经不为空,直接return,但是没有初始化,所以不合理)
4、枚举饿汉式
枚举默认变量定义为 private static final
方法自定义private,同时构造方法可省。
// 枚举饿汉式
public enum Singleton4 {
// Singleton4 INSTANCE;
INSTANCE;
// 枚举构造方法默认就是私有的,去掉也可以
private Singleton4() {
System.out.println("private Singleton4()");
}
public static Singleton4 getInstance() {
return INSTANCE;
}
}
属于饿汉式单例,下面例子演示枚举原理,证明属于饿汉式,会放到静态代码块中执行,静态代码块里面的线程安全由虚拟机来保证。
这个是最离谱的。。饿汉式
// 枚举类可以非常方便的控制对象的个数
enum Sex {
MALE, FEMALE;
}
/*final class Sex extends Enum<Sex> {
public static final Sex MALE;
public static final Sex FEMALE;
private Sex(String name, int ordinal) {
super(name, ordinal);
}
// 饿汉式单例,类加载时候就构造好
static {
// 会放到静态代码块中执行,静态代码块里面的线程安全由虚拟机来保证
MALE = new Sex("MALE", 0);
FEMALE = new Sex("FEMALE", 1);
}
}*/
5、内部类懒汉式
反射、序列化/反序列化、Spring工具类
避免了双检锁的缺点
// 懒汉式单例 - 内部类
public class Singleton5 {
private Singleton5() {
System.out.println("private Singleton5()");
}
//没有用到这个静态内部类不会触发他的加载、连接、初始化,也就不会初始化对象
private static class Holder {
// 会放到静态代码块中执行,静态代码块里面的线程安全由虚拟机来保证
static Singleton5 INSTANCE = new Singleton5();
}
// 懒汉式,不调用getInstance时候不会触发Holder的加载和初始化
// 既保证了线程安全static静态保证,避免了双检锁的缺点,又保证懒汉特性,第一次访问才用到
public static Singleton5 getInstance() {
return Holder.INSTANCE;
}
}
JDK 中单例的体现
1、Runtime 体现了饿汉式单例
2、System类中Console 体现了双检锁懒汉式单例