什么是单例模式?
所谓单例模式就是一种经典设计模式,关键要点是借助java语法保证某个类只能够创建出一个实例,而不能再通过构造方法new出多个实例。在java中,单例模式有很多种写法,本文只探讨了经典的“饿汉模式”和“懒汉模式”两种写法
注意:
如果用洗碗来做类比的话,吃完饭立刻就去洗碗这个就是属于“饿汉模式”的范畴,相反如果等到下次再使用碗的时候才开始洗碗这就是“懒汉模式”的范畴,类比于创建实例,“饿汉模式”就是提前把这个实例创建好不管之后会不会去使用,而“懒汉模式”则是非必要不创建实例
1.基于饿汉模式实现线程安全的单例模式:
package threading;
class Singleton{
private static Singleton instance=new Singleton();//唯一实例的本体
public static Singleton getInstance(){
return instance;
}
private Singleton(){//把这个构造方法用private修饰,禁止外部new实例
}
}
public class ThreadDemo13 {
public static void main(String[] args) {
Singleton s1=Singleton.getInstance();
Singleton s2=Singleton.getInstance();
//Singleton s3=new Singleton();//这里就会报错·
}
}
注解:
1.此时的饿汉模式就是天生的线程安全,因为在SingleTon的getInstance方法中只设计了return instance这个操作,也就是只有读取操作所以在多线程同时执行的时候也是线程安全的
2.以上代码是如何实现单例模式的呢?就是用private修饰SingleTon内部的构造方法,让在类外面无法通过new来创建实例
3.在main方法里面创建的s1和s2实例本质上是一个对象,这也是单例模式的内涵体现,但是如果使用new SingleTon来创建实例就会报错
2.基于懒汉模式实现线程安全的单例模式:
class SingletonLazy{
private static SingletonLazy instance=null;//先置为null,需要创建的时候再创建
public static SingletonLazy getInstance(){
if (instance == null) {
instance = new SingletonLazy();
}
return instance;
}
private SingletonLazy(){//保证不能再在外部new
}
}
“懒汉模式”是线程不安全的,有读也有写(解决办法1.加锁,把if和new变成原子操作2.双重if,减少不必要的加锁操作3.使用volatile关键字禁止指令重排序,保证后续线程拿到的是完整对象)
原因:
(1)if里面的判空和new操作不是原子的
解决方案:给这个方法整体部位使用synchronize加锁
但是根据“非必要不加锁”的准则,此处的线程不安全只出现在首次创建对象(new)这里,一旦对象new好了后续调用getinstance就只是单纯的读操作,就没有线程安全问题了,就没有必要再加锁了 ,所以以上代买可以加一个判定条件(看是否是首次创建对象,如果不是首次创建那么就直接返回这个实例)
(2)会出现指令重排序的问题
上面new对象的操作可以分为 1.分配对象空间 2. 实例化对象 3. 把对象的引用返回栈三个步骤,如果在多线程并发执行的时候可能因为编译器优化,把上面三个步骤顺序打乱也就是会出现指令重排序,类比装修房子,就是说t1线程如果房子还没有装修完就交给了t2线程那么t2线程拿到的就是一个清水房。
解决方案:使用volatile关键字去修饰这个instance
综上所述“懒汉模式”的正确代码如下:
class SingletonLazy{
volatile private static SingletonLazy instance=null;//先置为null,需要创建的时候再创建
public static SingletonLazy getInstance(){
if(instance==null) {
synchronized (SingletonLazy.class) {//加锁位置
if (instance == null) {
instance = new SingletonLazy();
}
}
}
return instance;
}
private SingletonLazy(){//保证不能再在外部new
}
}