概念(单个实例)
单例模式保证只有一个实例,就要保证外界不能随便的new这个对象,所以要私有化结构方法。私有化结构方法后就是把new这个对象控制权收回了,只能在类内部去实例这个对象,让自身负责保存他的唯一实例。
饿汉式
从时间空间上看:以空间换时间
从线程安全上看:安全的
所以又叫“饿汉式”(还没有使用就实例化了)
//懒汉式
public class SingLetonDemo1 {
//在类加载的时候就初始化对象
private static SingLetonDemo1 instance = new SingLetonDemo1();
//私有化构造方法
private SingLetonDemo1(){
}
//静态方法获取该类实例
public static SingLetonDemo1 getInstance(){
return instance;
}
}
懒汉式
从时间空间上看:以时间换空间
从线程安全上看:不安全的(所以要加锁synchronized)
所以又叫“懒汉式”(也就是在用到的时候才去实例化)
//懒汉式
public class SingLetonDemo2 {
private static SingLetonDemo2 instance = null;
private SingLetonDemo2(){
}
//加锁()保证每个线程在某一个时刻只有一个线程进入这个方法,解决了线程安全的问题
//但是每一个线程都要判断是否有锁,影响了效率。
public synchronized static SingLetonDemo2 getInstance(){
if(instance == null){
instance = new SingLetonDemo2();
}
return instance;
}
}
改进懒汉式1
//使用双重检验方式保证线程安全
public class SingLetonDemo3 {
private static SingLetonDemo3 instance = null;
private SingLetonDemo3(){
}
//第一次的判断语句避免了每次线程都要检查这个方法是否加锁
//第二次的判断语句避免了极端情况下:第一次获取该实例:有多个线程同时进入第一个if语句
// 等待第一个进入第二个if语句线程释放锁,这种情况就会有多个这个类的实例
public static SingLetonDemo3 getInstance(){
if(instance == null){
synchronized (SingLetonDemo3.class){
if(instance == null){
instance = new SingLetonDemo3();
}
}
}
return instance;
}
}
改进懒汉式2 为instance 加入volatitle修饰符
volatitle的作用
public class SingLetonDemo4 {
private volatile static SingLetonDemo4 instance = null;
private SingLetonDemo4(){
}
public static SingLetonDemo4 getInstance(){
if(instance == null){
synchronized (SingLetonDemo3.class){
if(instance == null){
instance = new SingLetonDemo4();
}
}
}
return instance;
}
}
原子操作:
在 Java 中,原子操作是一种操作数或表达式,它不受其他线程的干扰,保证其执行的原子性。原子操作通常用于避免多线程并发访问时出现的竞争条件和锁定超时等问题。
在 Java 中,原子操作可以通过原子变量来实现。原子变量是一个受保护的变量,它只能通过原子操作来访问。原子操作可以确保多个线程同时访问原子变量时,它们的操作是原子的,即不会相互干扰。
例如,在以下代码中,atomicInteger
变量是一个原子变量,它可以通过原子操作来访问:
AtomicInteger atomicInteger = new AtomicInteger(0);
int value = atomicInteger.get(); // 获取值
atomicInteger.add(1); // 增加 1
在以上代码中,get()
方法是一个原子操作,它确保在多个线程同时访问 atomicInteger
变量时,获取的值是原子的。add()
方法也是一个原子操作,它确保在多个线程同时访问 atomicInteger
变量时,增加的值是原子的。
需要注意的是,原子操作并不总是最优的,因为它们可能会增加代码的复杂性和错误的风险。在某些情况下,使用线程安全的机制 (例如锁) 比使用原子操作更为有效。
指令重排序
指令重排是指在编译时或运行时,将程序中的指令重新排列以达到优化的目的。指令重排可以优化程序的性能,减少指令的数量,减少内存访问时间,提高程序的执行效率。
在指令重排中,程序中的指令会被重新排列,以便更好地利用计算机的硬件特性,例如缓存、内存、处理器等。例如,在编译时,编译器可以将相关的指令重排在一起,以便更好地利用缓存,减少指令的重复计算,提高程序的执行效率。在运行时,程序可以通过指令重排来减少内存访问时间,例如将常量表达式提前计算,以便在后续指令中使用,从而减少内存访问的次数。
指令重排的实现需要考虑到程序的语义和性能,不能影响程序的语义和正确性。因此,在实现指令重排时,需要谨慎处理指令重排的影响,以确保程序的正确性。
原因:
new对象时候就是把它需要的内存空间分配出来(就有内存地址)。
invokespecial方法调用,"<init>":()V调用无参构造方法。
所以创建对象和调用构造方法是两步操作,前面只是把对象占用的内存分配好了,但是里面有很多成员变量,成员变量的初始化在构造方法里面。
putstatic给静态变量赋值,把构造好的对象赋值给静态变量INSTANCE。
CPU可能会对指令的执行次序做出优化,如果指令之间没有因果关系,CPU可能调换他们的执行次序,第17和21条指令是有因果关系的,对象内存没有分配就没法初始化成员变量,所以他们两个顺序是确定的。
而构造方法是做赋值操作的putstatic也是做赋值操作的,他们两个谁先执行谁后执行没有因果关系,CPU就可能会对代码执行顺序做出调整,可能先执行putstatic再执行构造,在单线程下面没有影响
枚举饿汉式
//枚举饿汉式
public enum SingLetonDemo5 {
INSTACNE;
private SingLetonDemo5(){
}
public static SingLetonDemo5 getInstance(){
return INSTACNE;
}
}
内部类懒汉式
//内部类懒汉式
public class SingLetonDemo6 {
private SingLetonDemo6(){
}
//没有用到这个静态内部类不会触发他的加载、连接、初始化,也就不会初始化对象
private static class Holder{
// 会放到静态代码块中执行,静态代码块里面的线程安全由虚拟机来保证
static SingLetonDemo6 INSTANCE = new SingLetonDemo6();
}
// 懒汉式,不调用getInstance时候不会触发Holder的加载和初始化
// 既保证了线程安全static静态保证,避免了双检锁的缺点,又保证懒汉特性,第一次访问才用到
public static SingLetonDemo6 getInstance(){
return Holder.INSTANCE;
}
}