12.单例模式
单例模式分为
1.饿汉式
2.懒汉式
基本模式:构造器私有,提供对应的静态方法去获取!
1.饿汉式样例:
public class Hungry {
//重点1:构造器私有,外部将无法创建该对象
private Hungry(){
}
//重点2:自己直接创建一个该对象,引用变量被final修饰之后,虽然不能再指向其他对象,但是它指向的对象的内容是可变的
private final static Hungry HUNGRY = new Hungry();
//重点3;提供对应的获取方法
private static Hungry getInstance(){
return HUNGRY;
}
}
懒汉式总结:
1.直接在类中创建该对象,可能会造成资源浪费,因为如果该类在代码中没有用到,但是该类在加载时已经创建了对象,这样就造成了内存浪费!
2.懒汉式样例:
public class LazyMan {
//重点1:构造器私有
private LazyMan(){
}
//重点2:创建LazyMan的变量,并没有具体引用,这是区别于饿汉式的地方!避免了浪费资源,因为如果该类没有用的话,不创建,调用方法时再创建!
private static LazyMan lazyMan;
public static LazyMan GetInstance(){
//重点3:先判断是否为null,再决定是否创建!
if (lazyMan == null) {
lazyMan = new LazyMan();
}
return lazyMan;
}
}
3.静态内部类创建单例模式
public class Holder {
重点1:构造器私有
private Holder(){
}
//重点4:创建public方法获取内部类属性!
public static Holder getInstance(){
return InnnerClass.HOLDER;
}
重点2:创建内部类
public static class InnnerClass{
重点3:私有的创建内部类属性
private static final Holder HOLDER=new Holder();
}
}
--------------------------------------------------------------------------------
3.饿汉式的并发问题:
饿汉式的并发问题!
public class LazyMan {
private LazyMan(){
//重点1:私有的构造器方法中打印该句!
System.out.println(Thread.currentThread().getName()+"懒汉式创建对象!");
}
private static LazyMan lazyMan;
public static LazyMan GetInstance(){
if (lazyMan == null) {
lazyMan = new LazyMan();
}
return lazyMan;
}
//重点2:启动20个线程创建该对象
public static void main(String[] args) {
for (int i = 0; i < 20; i++) {
new Thread(()->{
LazyMan.GetInstance();
},"线程"+i).start();
}
}
}
输出:发现有并发执行的问题,即创建的对象可能不是单例的!
线程2懒汉式创建对象!
线程3懒汉式创建对象!
线程1懒汉式创建对象!
2.如何解决呢:
方法1:加单层锁:
public static LazyMan GetInstance() {
重点1:锁住的是class类
synchronized (LazyMan.class) {
if (lazyMan == null) {
lazyMan = new LazyMan();
}
}
return lazyMan;
}
方法2:双重检测锁模式 又称之为DCL更安全!
public static LazyMan GetInstance() {
//重点1:加一层if判断
if (lazyMan == null) {
//重点2:加锁,锁住类,这个类的所有对象将使用同一把锁!
synchronized (LazyMan.class) {
if (lazyMan == null) {
lazyMan = new LazyMan();//不是原子性操作
会经历三个步骤:
1.分配内存空间
2.执行构造方法,初始化对象
3.把这个对象指向这个空间
这里可能会发生指令重排(JVM底层执行时,在不影响其结果的情况下,会优化执行顺序,例如132执行顺序!)
}
}
}
return lazyMan;
}
问题:创建对象在底层并不是个原子性操作,可能会发生指令重排,所以方法2也不是个安全的方法!
方法3:DCL模式和volatile 原子性操作
重点1:使用volatile 避免指令重排
private static volatile LazyMan lazyMan;
重点2:再使用DCL双重检测锁!
public static LazyMan GetInstance() {
if (lazyMan == null) {
synchronized (LazyMan.class) {
if (lazyMan == null) {
lazyMan = new LazyMan();
}
}
}
return lazyMan;
}
反射可以破坏上述的所有安全单例模式