单例模式与多线程

    技术2022-07-16  84

    立即加载/“饿汉模式”

    立即加载就是在使用类的时候,对象已经创建,常见方法就是直接New实例化。立即有急迫的意思,所以叫饿汉模式。

    public class singleDemo { static class MyObject{ //立即加载方式==饿汉模式 private static MyObject myObject = new MyObject(); public MyObject() { } //该方法没有同步,多线程环境最好根据实际业务做一下同步 public static MyObject getInstance() { return myObject; } } static class MyThread extends Thread{ @Override public void run() { //输出MyObject对象的hashcode System.out.println(MyObject.getInstance().hashCode()); } } public static void main(String[] args) { MyThread myThreadA = new MyThread(); MyThread myThreadB = new MyThread(); MyThread myThreadC = new MyThread(); myThreadA.start(); myThreadB.start(); myThreadC.start(); } } 1413474096 1413474096 1413474096 输出hashCode一致,是同一对象,多线程下单例模式成功

    延迟加载/“懒汉模式

    延迟加载是在调用get()方法时再实例化对象,最常见的方法是在get()方法中new对象,延迟有"缓慢","不急迫"的意思,所以叫懒汉模式。

    public class singleDemo { static class MyObject{ //立即加载方式==饿汉模式 private static MyObject myObject; public MyObject() { } //该方法没有同步,多线程环境最好根据实际业务做一下同步 public static MyObject getInstance() { try { //如果myObject没有实例化就new if(myObject == null){ //模拟处理业务时间耗时1s Thread.sleep(1000); myObject = new MyObject(); } } catch (InterruptedException e) { e.printStackTrace(); } return myObject; } } static class MyThread extends Thread{ @Override public void run() { //输出MyObject对象的hashcode System.out.println(MyObject.getInstance().hashCode()); } } public static void main(String[] args) { MyThread myThreadA = new MyThread(); MyThread myThreadB = new MyThread(); MyThread myThreadC = new MyThread(); myThreadA.start(); myThreadB.start(); myThreadC.start(); } } 1340881131 1081640504 466602589 上述懒加载在多线程环境下,创建出了多个对象,单例模式失败。

    解决方法1:synchronized 关键字同步getInstance方法

    synchronized public static MyObject getInstance() { try { //如果myObject没有实例化就new if(myObject == null){ Thread.sleep(1000); myObject = new MyObject(); } } catch (InterruptedException e) { e.printStackTrace(); } return myObject; }

    解决方法2:synchronized 关键字同步代码块

    public static MyObject getInstance() { try { synchronized (MyObject.class){ //如果myObject没有实例化就new if(myObject == null){ Thread.sleep(1000); myObject = new MyObject(); } } } catch (InterruptedException e) { e.printStackTrace(); } return myObject; }

    解决方法3:使用DCL双检查锁机制

    public static MyObject getInstance() { try { //如果myObject没有实例化就new if(myObject == null){ Thread.sleep(1000); //synchronized 同步代码块 synchronized (MyObject.class){ //再次判断myObject 是否被初始化 if(myObject == null) { myObject = new MyObject(); } } } } catch (InterruptedException e) { e.printStackTrace(); } return myObject; }

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

    public class singleDemo { static class MyObject{ //类加载时初始化,只执行一次 private static MyObject myObject = new MyObject(); public MyObject() { } public static MyObject getInstance() { return MyObject.myObject; } } static class MyThread extends Thread{ @Override public void run() { //输出MyObject对象的hashcode System.out.println(MyObject.getInstance().hashCode()); } } public static void main(String[] args) { MyThread myThreadA = new MyThread(); MyThread myThreadB = new MyThread(); MyThread myThreadC = new MyThread(); myThreadA.start(); myThreadB.start(); myThreadC.start(); } } 712355351 712355351 712355351

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

    java 对象的序列化操作是实现Serializable接口,我们就可以把它往内存地写再从内存里读出而"组装"成一个跟原来一模一样的对象。但是当我们遇到单例序列化的时候,就出现问题了。当从内存读出而组装的对象破坏了单例的规则,会创建新的对象。单例要求JVM只有一个对象,而通过反序化时就会产生一个克隆的对象,这就打破了单例的规则。

    public class singleDemo { static class MyObject implements Serializable{ private static final long serialVersionUID = 1L; //立即加载方式==饿汉模式 private static MyObject myObject = new MyObject(); public MyObject() { } //该方法没有同步,多线程环境最好根据实际业务做一下同步 public static MyObject getInstance() { return MyObject.myObject; } //readResolve()方法会在序列化之后,用原myObject对象替换myObject对象 /* protected Object readResolve() throws ObjectStreamException { System.out.println("调用了readResolve方法!"); return MyObject.myObject; }*/ } static class MyThread extends Thread{ @Override public void run() { //输出MyObject对象的hashcode System.out.println(MyObject.getInstance()); } } 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) { e.printStackTrace(); } catch (IOException e) { 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(); } } } 666988784 1338668845 hashCode不同,myObject不同

    解决方法:实现readResolve()方法,对于Serializable and Externalizable classes,方法readResolve允许class在反序列化返回对象前替换、解析在流中读出来的对象。实现readResolve方法,一个class可以直接控制反序化返回的类型和对象引用。

    方法readResolve会在ObjectInputStream已经读取一个对象并在准备返回前调用。ObjectInputStream 会检查对象的class是否定义了readResolve方法。如果定义了,将由readResolve方法指定返回的对象。返回对象的类型一定要是兼容的,否则会抛出ClassCastException 。

    打开readResolve()方法 protected Object readResolve() throws ObjectStreamException { System.out.println("调用了readResolve方法!"); return MyObject.myObject; } 666988784 调用了readResolve方法! 666988784 hashCode相同,myObject相同

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

    public class singleDemo { static class MyObject { //立即加载方式==饿汉模式 private static MyObject myObject; public MyObject() { } //静态代码块实现初始化myObject static{ myObject = new MyObject(); } //该方法没有同步,多线程环境最好根据实际业务做一下同步 public static MyObject getInstance() { return myObject; } } static class MyThread extends Thread{ @Override public void run() { //输出MyObject对象的hashcode System.out.println(MyObject.getInstance().hashCode()); } } public static void main(String[] args) { MyThread threadA = new MyThread(); MyThread threadB = new MyThread(); MyThread threadC = new MyThread(); threadA.start(); threadB.start(); threadC.start(); } } 58615885 58615885 58615885

    使用enum枚举数据类型实现单例模式

    枚举enum和静态代码块的特性相似,在使用枚举数据类型时,构造方法会被自动调用,可以利用这个特性实现单例设计模式。

    public class SingleDemo { enum MyObject { //每一个枚举对象都会调用一遍构造函数,每个对象仅且仅调用一次,该枚举类只有connectionFactory一个枚举对象 //枚举对象默认static,调用MyObject()构造函数完成connection初始化 connectionFactory; private Connection connection; MyObject() { try { System.out.println("调用了构造函数,每个枚举对象只调用一次!"); String url = "jdbc:mysql://localhost:3306/mysql"; String user = "root"; String password = "******"; //加载mysql驱动 Class.forName("com.mysql.jdbc.Driver"); //返回mysql连接 connection = DriverManager.getConnection(url, user, password); } catch (Exception e) { e.printStackTrace(); } } //connectionFactory枚举对象下的方法 public Connection getConnection(){ return connection; } } static class MyThread extends Thread{ @Override public void run() { //输出枚举对象的getConnection()获取连接,输出hashCode System.out.println(MyObject.connectionFactory.getConnection().hashCode()); } } public static void main(String[] args) { MyThread threadA = new MyThread(); MyThread threadB = new MyThread(); MyThread threadC = new MyThread(); threadA.start(); threadB.start(); threadC.start(); } } 调用了构造函数,每个枚举对象只调用一次! 85383474 85383474 85383474

    总结

    介绍了6种多线程环境下的单例模式的实现方式,常见问题及解决方式。

    Processed: 0.033, SQL: 9