【多线程学习笔记】单例模式- 延迟加载/懒汉模式的解决方案:DCL双重检测锁 + 单例模式的其他实现方案


立即加载/“饿汉模式”

什么是立即加载?立即加载就是使用类的时候已经将对象创建完毕,常见的实现办法就是直接new实例化。而立即加载从中文的语境来看,有“着急”、“急迫”的含义,所以也称为“饿汉模式”。 立即加载/“饿汉模式”是在调用方法前,实例已经被创建了,来看一下实现代码。 创建类MyObject.java代码如下:

package test;
public class MyObject {
    
    
    // 立即加载方式==饿汉模式
    private static MyObject myObject = new MyObject();
    private MyObject(){
    
    }
    public static MyObject getInstance() {
    
    
        // 此代码版本为立即加载
        // 此版本代码的缺点是不能有其他实例变量
        // 因为getInstance()方法没有同步
        // 所以有可能出现非线程安全问题
        return myObject;
    }

}
创建线程类MyThread.java代码如下:

package extthread;
import test.MyObject;
public class MyThread extends Thread {
    
    
    @Override
    public void run() {
    
    
        System.out.println(MyObject.getInstance().hashCode());
    }
}  

创建运行类Run.java代码如下:

package test.run;
import extthread.MyThread;
public class Run {
    
    
    public static void main(String[] args) {
    
    
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        MyThread t3 = new MyThread();
        t1.start();
        t2.start();
        t3.start();
    }

控制台打印的hashCode是同一个值,说明对象是同一个,也就实现了立即加载型单例设计模式。

延迟加载/“懒汉模式”

什么是延迟加载?延迟加载就是在调用get()方法时实例才被创建,常见的实现办法就是在get()方法中进行new实例化。而延迟加载从中文的语境来看,是“缓慢”、“不急迫”的含义,所以也称为“懒汉模式”。 1.延迟加载/“懒汉模式”解析 延迟加载/“懒汉模式”是在调用方法时实例才被创建。一起来看一下实现代码。 创建测试用的项目singleton_1,创建类MyObject.java代码如下: package test;
public class MyObject {
private static MyObject myObject;
private MyObject() {
}
public static MyObject getInstance() {
// 延迟加载
if (myObject != null) {
} else {
myObject = new MyObject();
}
return myObject;
}
} 创建线程类MyThread.java代码如下:
package extthread;
import test.MyObject;
public class MyThread extends Thread {
@Override
public void run() {
System.out.println(MyObject.getInstance().hashCode());
}
}
创建运行类Run.java代码如下:
package test.run;
import extthread.MyThread;
public class Run {
public static void main(String[] args) {
MyThread t1 = new MyThread();
t1.start();
MyThread t2 = new MyThread();
t2.start();
}
}
结果:
在这里插入图片描述

前面两个实验虽然使用“立即加载”和“延迟加载”实现了单例设计模式,但在多线程的环境中,前面“延迟加载”示例中的代码完全就是错误的,根本不能实现保持单例的状态。
来看一下如何在多线程环境中结合“错误的单例模式”创建出“多例”。

  package test;
public class MyObject {
    
    
    private static MyObject myObject;
    private MyObject() {
    
    
    }
    public static MyObject getInstance() {
    
    
        try {
    
    
if (myObject != null) {
    
    
            } else {
    
    
                // 模拟在创建对象之前做一些准备性的工作
                Thread.sleep(3000);
                myObject = new MyObject();
            }
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
        return myObject;
    }
    }

创建线程类MyThread.java代码如下:

package extthread;
import test.MyObject;
public class MyThread extends Thread {
    
    
    @Override
    public void run() {
    
    
        System.out.println(MyObject.getInstance().hashCode());
    }
}  

创建运行类Run.java代码如下:

package test.run;
import extthread.MyThread;
public class Run {
    
    
    public static void main(String[] args) {
    
    
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        MyThread t3 = new MyThread();
        t1.start();
        t2.start();
        t3.start();
    }

}
在这里插入图片描述

延迟加载/“懒汉模式”的解决方案

(1)声明synchronized关键字

既然多个线程可以同时进入getInstance()方法,那么只需要对getInstance()方法声明synchronized关键字即可。

package test;
public class MyObject {
    
    
    private static MyObject myObject;
    private MyObject() {
    
    
    }
    // 设置同步方法效率太低了
    // 整个方法被上锁
    synchronized public static MyObject getInstance() {
    
    
        try {
    
    
            if (myObject != null) {
    
    
            } else {
    
    
                // 模拟在创建对象之前做一些准备性的工作
                Thread.sleep(3000);
                myObject = new MyObject();
            }
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
        return myObject;
    }
}

此方法加入同步synchronized关键字得到相同实例的对象,但此种方法的运行效率非常低下,是同步运行的,下一个线程想要取得对象,则必须等上一个线程释放锁之后,才可以继续执行。

(2)尝试同步代码块

同步方法是对方法的整体进行持锁

public class MyObject {
    
    
    private static MyObject myObject;
    private MyObject() {
    
    
    }
    public static MyObject getInstance() {
    
    
        try {
    
    
            // 此种写法等同于:
            // synchronized public static MyObject getInstance()
            // 的写法,效率一样很低,全部代码被上锁
            synchronized (MyObject.class) {
    
    
                if (myObject != null) {
    
    
                } else {
    
    
                    // 模拟在创建对象之前做一些准备性的工作
                    Thread.sleep(3000);
                    myObject = new MyObject();
                }
            }
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
        return myObject;
    }

}

(3)针对某些重要的代码进行单独的同步

同步代码块可以针对某些重要的代码进行单独的同步,而其他的代码则不需要同步。这样在运行时,效率完全可以得到大幅提升。 修改上面代码:

;public class MyObject {
    
    
    private static MyObject myObject;
    private MyObject() {
    
    
    }
    public static MyObject getInstance() {
    
    
        try {
    
                if (myObject != null) {
    
    
            } else {
    
    
                // 模拟在创建对象之前做一些准备性的工作
                Thread.sleep(3000);
                // 使用synchronized (MyObject.class)
                // 虽然部分代码被上锁
                // 但还是有非线程安全问题
                synchronized (MyObject.class) {
    
    
                    myObject = new MyObject();
                }
            }
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
        return myObject;
    }

此方法使同步synchronized语句块,只对实例化对象的关键代码进行同步,从语句的结构上来讲,运行的效率的确得到了提升。**但如果是遇到多线程的情况下还是无法解决得到同一个实例对象的结果。
**到底如何解决“懒汉模式”遇到多线程的情况呢?

(4)使用DCL双检查锁机制

在最后的步骤中,使用的是DCL双检查锁机制来实现多线程环境中的延迟加载单例设计模式。

public class MyObject {
    
    
    private volatile static MyObject myObject;
    private MyObject() {
    
    
    }
    // 使用双检测机制来解决问题,既保证了不需要同步代码的异步执行性
    // 又保证了单例的效果
    public static MyObject getInstance() {
    
    
        try {
    
    
            if (myObject != null) {
    
    
            } else {
    
    
                // 模拟在创建对象之前做一些准备性的工作
                Thread.sleep(3000);
                synchronized (MyObject.class) {
    
    
                    if (myObject == null) {
    
    
                        myObject = new MyObject();
                    }
                }
            }
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
        return myObject;
    }
    // 此版本的代码称为双重检查Double-Check Locking
}

结果:
在这里插入图片描述

使用静态内置类实现单例模式

DCL可以解决多线程单例模式的非线程安全问题。当然,使用其他的办法也能达到同样的效果。
创建类MyObject.java代码如下:

public class MyObject {
    
    
    // 内部类方式
    private static class MyObjectHandler {
    
    
        private static MyObject myObject = new MyObject();
    }
    private MyObject() {
    
    
    }
    public static MyObject getInstance() {
    
    
        return MyObjectHandler.myObject;
    }
} 

线程类:

package extthread;
import test.MyObject;
public class MyThread extends Thread {
    
    
    @Override
    public void run() {
    
    
        System.out.println(MyObject.getInstance().hashCode());
    }
} 

运行类和上面的一样
结果是三个hashcode一样。

序列化与反序列化的单例模式实现

静态内置类可以达到线程安全问题,但如果遇到序列化对象时,使用默认的方式运行得到的结果还是多例的。

import java.io.ObjectStreamException;
import java.io.Serializable;
public class MyObject implements Serializable {
    
    
    private static final long serialVersionUID = 888L;
    // 内部类方式
    private static class MyObjectHandler {
    
    
        private static final MyObject myObject = new MyObject();
    }
    private MyObject() {
    
    
}
    public static MyObject getInstance() {
    
    
        return MyObjectHandler.myObject;
    }
    // protected Object readResolve() throws ObjectStreamException {
    
    
    // System.out.println("调用了readResolve方法!");
    // return MyObjectHandler.myObject;
    // }
    }

创建业务类SaveAndRead.java代码如下:

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import test.MyObject;
public class SaveAndRead {
    
    
    public static void main(String[] args) {
    
    
        try {
    
    
            MyObject myObject = MyObject.getInstance();
            FileOutputStream fosRef = new FileOutputStream(new File(
                    "myObjectFile.txt"));
            ObjectOutputStream oosRef = new ObjectOutputStream(fosRef);
            oosRef.writeObject(myObject);//在这里进行序列化
            oosRef.close();
            fosRef.close();
            System.out.println(myObject.hashCode());
        } catch (FileNotFoundException e) {
    
    
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
    
    
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        try {
    
    
            FileInputStream fisRef = new FileInputStream(new File(
                    "myObjectFile.txt"));
            ObjectInputStream iosRef = new ObjectInputStream(fisRef);
            MyObject myObject = (MyObject) iosRef.readObject();//进行反序列化
            iosRef.close();
            fisRef.close();
            System.out.println(myObject.hashCode());
        } catch (FileNotFoundException e) {
    
    
            e.printStackTrace();
        } catch (IOException e) {
    
    
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
    
    
            e.printStackTrace();
        }
    }

在这里插入图片描述

}解决办法就是在反序列化中使用readResolve()方法。 去掉如下代码的注释:

 protected Object readResolve() throws ObjectStreamException {
    
    
    System.out.println("调用了readResolve方法!");
    return MyObjectHandler.myObject;
} 

在这里插入图片描述
当JVM从内存中反序列化地"组装"一个新对象时,就会自动调用这个 readResolve方法来返回我们指定好的对象了, 单例规则也就得到了保证.

使用static代码块实现单例模式

静态代码块中的代码在使用类的时候就已经执行了,所以可以应用静态代码块的这个特性来实现单例设计模式。

public class MyObject {
    
    
    private static MyObject instance = null;
    private MyObject() {
    
    
    }
    static {
    
    
        instance = new MyObject();
    }
    public static MyObject getInstance() {
    
    
        return instance;
    }
} 

Guess you like

Origin blog.csdn.net/qq_41358574/article/details/120818890