设计模式,作用,单例模式,作用,实例,new多个实例会报错
,饿汉模式1.类成员2.构造方法私有;懒汉模式,区别,比较,线程不安全,
时间消耗,问题出现场景,加锁,加锁后的问题,阻塞->效率,解决;
指令重排序(编译器优化),3步 ,解决,volatile(面试题)
1.设计模式
设计模式:开发中有多个重复的问题场景,把这些步骤总结为固定的解决方案,应用在开发中。这些解决方案就是设计模式。设计模式可以提升开发效率。
设计模式包含单例模式。
2.单例模式
单例模式:一个类在当前进程中只能存在一个实例。多个进程之间无法保证只有一个实例。因为进程之间有独立的存储空间,互不影响,各个进程维护自己的单例对象。
为什么要用到单例模式?
比如:服务器要加载100G的数据到内存中。
在代码中有一个类A的构造方法中进行上述的数据加载(读硬盘文件,把数据加载到内存中),那么在一个对象创建好,就会消耗100G内存空间。这当然是不可取的!
所以在应用中有些类只用创建一次就足够了,只用new一次即可。但是呢,人为因素不可控,所以有了单例模式以确保一个进程中某个类中只有一个实例。
3.饿汉模式
在类加载的时候,在方法区生成类对象,执行初始化静态变量,静态代码块。
类加载:JVM把.class文件加载到JVM内存上,解析这个类
1.触发类加载的时机---只要JVM第一次遇到这个类,就触发类加载
1.程序运行中,第一次访问这个类的静态成员
2.new这个类的对象
3.反射加载这个类

4.懒汉模式
在类加载时部创建对象,在第一次调用getInstance时才new对象。通常认为懒汉模式优于饿汉模式,因为如果代码逻辑中不需要创建instance,那么就省下了创建instance所用的内存空间。
但是,懒汉模式会出现线程安全问题。
5.懒汉模式下的线程安全问题
1.原子性


如图:

在t1执行完后,最终进程里还是只有一个instance,但是,如果new一个对象需要花费10min,又或者同一个程序里保存了两份单例对象,s2修改数据,s1看不到更新的数据,造成业务逻辑错乱。
解决方法:将if与new操作打包为原子的:

public static SingletonLazy getInstance(){
synchronized (locker) {
if (instance == null) {
instance = new SingletonLazy();
}
}
return instance;
}
2.加锁阻塞导致的效率问题
上述1问题只出现在实例化对象时,一旦实例化完成后,后续操作只是读instance。如果每次调用getInstance方法都阻塞,就会导致效率低下。
所以优化为:
public static SingletonLazy getInstance(){
if(instance==null) {
synchronized (locker) {
if (instance == null) {
instance = new SingletonLazy();
}
}
}
return instance;
}
3.指令重排序引起的问题
instance = new SingletonLazy();
这行代码涉及到很多指令,将其分为三个步骤:
1.在堆内存上开辟一块内存空间
2.初始化
3.将堆内存的首地址赋值给引用变量
而这三步中涉及很多指令。
编译器优化,使指令重排序,1->2->3可能变为1->3->2,在多线程下就会出现问题:

注意在分配堆内存空间,成员变量此时都是默认初始值,如int型为0。在分配内存空间后,instance!=null了。
所以用volatile关闭编译器优化

package thread;
class SingletonLazy{
private volatile static SingletonLazy instance=null;
private static Object locker=new Object();
public static SingletonLazy getInstance(){
if(instance==null) {
synchronized (locker) {
if (instance == null) {
instance = new SingletonLazy();
}
}
}
return instance;
}
private SingletonLazy(){
}
}
public class demo22_singletonLazy {
public static void main(String[] args) {
// SingletonLazy s1=SingletonLazy.getInstance();
// SingletonLazy s2=SingletonLazy.getInstance();
//System.out.println(s1==s2);
Thread t1=new Thread(()->{
SingletonLazy s1=SingletonLazy.getInstance();
});
Thread t2=new Thread(()->{
SingletonLazy s2=SingletonLazy.getInstance();
});
t1.start();
t2.start();
}
}