单例模式

单例模式:
懒汉式:延迟加载模式(饿了在吃)
饿汉式:立即加载(我怕我饿着 先垫着)
单例模式使用不当,就容易引起线程安全问题
1.饿汉式不存在线程安全问题,但是它一般不被使用,因为它会浪费内存的空间
2.懒汉式会合理使用内存空间,只有第一次被加载的时候,才会真正创建对象。但是这种方式存在线程安全问题。

懒汉式单例模式
1.双重检查锁方式(DCL)
2.静态内部类
3.枚举方式

饿汉式:

public class Student1 {
    //2.成员变量初始化本身对象
    private  static Student1 student1=new  Student1();
    //1.构造方法私有
    private Student1(){}
    //3.对外提供公共方法获取对象
    public static Student1 getSingletonInstance(){
        return student1;
    }
}

Student stu=new Student();有3步指令
1.new->申请内存空间
2.完成属性的初始化(赋值)
3.将对象内存地址交给变量来保存
可能132

饿汉式

public class Student2 {
    //1:构造私有
    private Student2(){}

    //2:定义私有静态成员变量,先不初始化
    private static Student2 student = null;

    //3:定义公开静态方法,获取本身对象
    public static Student2 getSingletonInstance(){
        //没有对象,再去创建
        if (student == null) {
            student = new Student2();
        }
        //有对象就返回已有对象
        return student;
    }
}

这个类可以满足基本要求,但是,懒汉式单例实现,存在线程安全隐患,如何解决?

public class Student3 {

    private Student3(){}

    private static Student3 student = null;

    // 此处考验对synchronized知识点的掌握情况
    public static synchronized Student3 getSingletonInstance(){
        if (student == null) {
            student = new Student3();
        }
        return student;
    }

}

/**

  • 对getSingletonInstance方法加synchronized关键字
  • synchronized关键字锁住的是这个对象,这样的用法,在性能上会有所下降
  • 因为每次调用getInstance(),都要对对象上锁
  • 事实上,只有在第一次创建对象的时候需要加锁,之后就不需要了
  • 所以,这个地方需要改进
    */
public class Student4 {
    private static Student4 student = null;
    private Student4() {}
    // 此处考验对synchronized知识点的掌握情况
    public static Student4 getSingletonInstance() {
        if (student == null) {
            // 采用这种方式,对于对象的选择会有问题
            // JVM优化机制:先分配内存空间,再初始化
            synchronized (Student4.class) {
                if (student == null) {
                    student = new Student4();
                    //student.setName("ss")
                    //new ---- 开辟JVM中堆空间---产生堆内存地址保存到栈内存的student引用中---创建对象
                    // 存在的问题:
                }
            }
        }
        return student;
    }
}

似乎解决了之前提到的问题,将synchronized关键字加在了内部,也就是说当调用的时候是不需要加锁的,只有在instance为null,并创建对象的时候才需要加锁,性能有一定的提升。
但是,这样的情况,还是有可能有问题的,看下面的情况:
在Java指令中创建对象和赋值操作是分开进行的,也就是说instance = new Singleton();语句是分两步执行的。
但是JVM并不保证这两个操作的先后顺序,也就是说有可能JVM会为新的Singleton实例分配空间,然后直接赋值给instance成员,然后再去初始化这个Singleton实例。
这样就可能出错了,我们以A、B两个线程为例:
a>A、B线程同时进入了第一个if判断
b>A首先进入synchronized块,由于instance为null,所以它执行instance = new Singleton();
c>由于JVM内部的优化机制(指令重排序),JVM先画出了一些分配给Singleton实例的空白内存,并赋值给instance成员(注意此时JVM没有开始初始化这个实例),然后A离开了synchronized块。
d>B进入synchronized块,由于instance此时不是null,因此它马上离开了synchronized块并将结果返回给调用该方法的程序。
e>此时B线程打算使用Singleton实例,却发现它没有被初始化,于是错误发生了。

–双重检查加锁

public class Student5 {

    private volatile static Student5 student;

    private Student5() {
    }

    public static Student5 getSingletonInstance() {
        if (student == null) {
            // B线程检测到student不为空
            synchronized (Student5.class) {
                if (student == null) {
                    student = new Student5();
                    // A线程被指令重排了,刚好先赋值了;但还没执行完构造函数。
                }
            }
        }
        return student;// 后面B线程执行时将引发:对象尚未初始化错误。
    }

}

–静态内部类

public class StaticSingleton {

	private StaticSingleton() {}

	/*
	 * 此处使用一个内部类来维护单例 JVM在类加载的时候,是互斥的,所以可以由此保证线程安全问题
	 */
	private static class SingletonFactory {
		private static StaticSingleton student = new StaticSingleton();
	}

	/* 获取实例 */
	public static StaticSingleton getSingletonInstance() {
		return SingletonFactory.student;
	}

}

class Test {

	public static void main(String[] args) {
		StaticSingleton student = StaticSingleton.getSingletonInstance();
		System.out.println(student);
	}
}

–枚举

public enum EnumSingleton {
	INSTANCE;

	public void talk() {
		System.out.println("This is an EnumSingleton " + this.hashCode());
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

qq_38325017

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值