1.设计模式
设计模式是在软件开发中解决常见问题的最佳实践或方案。使用设计模式可以实现可重用代码,帮助我们创建更灵活、可维护和可扩展的代码。而我们要介绍的单例模式就是设计模式中比较经典的一种模式。
2.单例模式
单例模式就是单个实例,在整个进程中的某个类,有且只有一个对象,这样的对象就被称为“单例”,那么如何保证这个类只有一个实例呢?这就需要编译器来帮我们做一个强制的检查,通过一些编程上的技巧,使编译器可以自动发现代码中是否含有多个实例,并且在尝试创建多个实例时,直接编译报错,从根本上保证对象是唯一实例,这样的代码就称为单例模式。
单例模式的实现方式有很多,我们主要介绍“饿汉模式”与“懒汉模式”
2.1 饿汉模式
唯一实例的创建时间非常早,在类加载的同时就创建实例(就像一个饿了很久的人,看到吃的就迫不及待地开始吃起来)
public class Singleton {
private static Singleton instance=new Singleton();//static成员在类加载时初始化
public static Singleton getInstance(){
return instance;
}
private Singleton(){
//类外的代码在尝试new的时候,需要调用构造方法,由于构造方法是私有的,无法调用,就会编译出错
//通过用private修饰构造方法,有效地禁止了外部代码创建该类的实例
}
}
class Hungry{
Singleton singleton1 = Singleton.getInstance();
Singleton singleton2 = Singleton.getInstance();
}
2.2 懒汉模式
不是在程序启动的时候创建实例,而是在第一次使用的时候才去创建实例
public class Singleton1 {
private static Singleton1 instance = null;
public static Singleton1 getInstance() {//什么时候调用,什么时候创建
if(instance == null) {
instance = new Singleton1();
}
return instance;
}
private Singleton1(){
}
}
class Lazy{
Singleton1 singleton1 = Singleton1.getInstance();
Singleton1 singleton2 = Singleton1.getInstance();
}
3.是否线程安全
在单线程环境下,上述两种模式的代码都够正常执行,但是当处于多线程环境中,有多个线程调用getInstance方法时,这两种模式会不会出现线程安全问题呢?
3.1 饿汉模式(线程安全)
在饿汉模式中,实例创建的时间是在java进程启动时(比main线程被调用还早),后续在代码中创建线程一定比实例的创建更加晚,所以当后续线程在执行getInstance方法时,实例早就已经创建完毕,而getInstance方法只是去读取了这个创建好的实例的值,相当于多个线程去读取同一个变量,不存在线程安全问题
3.2 懒汉模式(线程不安全)及改进
在懒汉模式的getInstance方法中,存在赋值(修改)操作,在多线程中就可能会线程安全问题,我们来举个例子
由于线程是随机调度,抢占式执行的,当t1线程执行到任何一个指令,t2线程都有可能抢占到cpu上继续执行,图中只是其中一种执行顺序,还会有其他执行顺序的可能。按照图中的执行顺序来看,t1线程和t2线程都分别创建了一个实例(都进行了new操作),当实例对象的内存数据较大时,多加载一次内存数据会有很大的时间开销
那么如何改进代码呢?
1.使用synchronized关键字,将if和new打包成一个原子操作,
2.由于只有第一次调用getInstance方法才会存在线程安全问题,一旦实例创建完毕,在后续调用中进行的只是读操作,不存在线程安全问题,这时进行加锁,就有点多余了,并且加锁操作本身也有一定开销(可能会使线程阻塞),所以需要添加一个if语句判断是否需要加锁
3.在两个if语句之间,synchronized会使线程阻塞,在阻塞过程中其他线程可能会修改instance的值,由于内存可见性问题,该线程可能没有感知到,所以需要给instance变量添加volatile关键字
4.可能会出现指令重排序问题,也需要给instance变量添加volatile关键字
public class Singleton1 { private static volatile Singleton1 instance = null; private static Object locker = new Object(); public static Singleton1 getInstance() { if(instance == null) {//判定是否要加锁 //实例化之前需要加锁 // 实例化之后,线程已经安全,无需加锁 synchronized (locker) { if (instance == null) { instance = new Singleton1(); } } } return instance; } private Singleton1(){ } } class Lazy{ Singleton1 singleton1 = Singleton1.getInstance(); Singleton1 singleton2 = Singleton1.getInstance(); }