博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Java多线程编程(六)单例模式与多线程
阅读量:4921 次
发布时间:2019-06-11

本文共 12180 字,大约阅读时间需要 40 分钟。

  在使用多线程技术的单例模式时会出现一些意想不到的情况,这样的代码如果在生产环境中出现异常,有可能造成灾难性的后果。

  一、立即加载/“饿汉模式”

  立即加载就是使用类的时候已经将对象创建完毕,常见的实现方法就是直接new实例化。立即加载/“饿汉模式”是在调用方法前,实例已经被创建了。

  示例:从输出结果可以看出,虽然3个线程同时执行了同一个对象的同一个方法,但是输出的hashCode值是一样的,说明对象是同一个,因为private static MyObject myObject = new MyObject();所以也就实现了立即加载型单例设计模式。

package test;public class MyObject {    //立即加载 == 懒汉模式    private static MyObject myObject = new MyObject();    private MyObject() {    }    public static MyObject getInstance() {        //缺点是不能有其他实例变量,因为getInstance()方法没有同步,所以有可能出现非线程安全问题。        return myObject;    }}
package extthread;import test.MyObject;public class MyThread extends Thread {    @Override    public void run() {        System.out.println(MyObject.getInstance().hashCode());    }}
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();    }}
180612070180612070180612070

  二、延迟加载/“懒汉模式”

  延迟加载就是在调用get()方法时实例才被创建,常见的办法就是在get()方法中进行new实例化。

  (1)延迟加载/“懒汉模式”实现

  示例:由于只有一个线程,所以从结果来看确实取得了一个对象的实例,但是在多线程环境中,就会取出多个实例的情况,这与单例模式的初衷是相违背的。

package test;public class MyObject {    private static MyObject myObject;    private MyObject() {    }    public static MyObject getInstance() {        //延迟加载        if (myObject != null) {        } else {            myObject = new MyObject();        }        return myObject;    }}
package extthread;import test.MyObject;public class MyThread extends Thread {    @Override    public void run() {        System.out.println(MyObject.getInstance().hashCode());    }}
package test.run;import extthread.MyThread;public class Run {    public static void main(String[] args) {        MyThread t1 = new MyThread();        t1.start();    }}
51011016

   (2)延迟加载/“懒汉模式”缺点

  示例:验证多线程中延迟加载的错误性。从输出可以看出来,3个线程创建了3个不同的实例,这是因为3个线程没有实现同步,即一开始都判断没有实例,所以都创建了实例。

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;    }}
package extthread;import test.MyObject;public class MyThread extends Thread {    @Override    public void run() {        System.out.println(MyObject.getInstance().hashCode());    }}
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();    }}
37935332010268100211839450825

   (3)延迟加载/“懒汉模式”解决方案

  ①声明synchronized关键字

  示例:修改getInstance()方法,加上synchronized关键字,既然多个线程可以同时进入getInstance()方法,那么只需要对getInstance()方法声明synchronized关键字即可,这样就可以得到相同实例的对象,但此种方法的运行效率非常低下,是同步运行的,下一个线程想要取得对象,就必须等待上一个线程释放锁之后,才可以继续执行。执行完需要花费9秒的时间。

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;    }}
379353320379353320379353320

   ②尝试同步代码块

  示例:之前的加synchronized关键字是对整个方法上锁,这里虽然使用了同步代码块,但是几乎是整个方法里面的代码,虽然可以达到效果,但这对运行效率来说也是不利的。执行总时间也是9秒。

package test;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;    }}
621856954621856954621856954

   ③针对某些重要的代码进行单独的同步

  示例:同步代码块可以针对某些重要的代码进行单独的同步,而其他的代码则不需要同步。这样在运行时,效率完全可以得到大幅提升。虽然不对sleep(3000)上锁这个思路很对,但是还是无法解决问题啊,3个线程还是分别都创建了一个实例。

package test;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;    }}
180612070621856954686587801

   ④使用DCL双检查锁机制

  示例:采用双重检查锁机制,成功解决了延迟加载遇到多线程的问题。DCL也是大多数多线程结合单例模式使用的解决方案。整个代码执行时间远少于9秒。

package test;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}
686587801686587801686587801

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

  除了DCL外,其他的方法也能达到同样的效果。

  示例:通过使用静态内之类的方式也可以实现3个线程只创建一个对象的需求。

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

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

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

  示例1:不使用readResolve()方法时,得到的不是同一个对象。

package test;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;//    }}
package test.run;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) {            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();        }    }}
1975012498990368553

   示例2:在反序列化中使用readResolve()方法时,就会得到同一个对象。

package test;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;    }}
1975012498调用了readResolve方法1975012498

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

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

  示例:从输出结果也可以看出,使用静态代码块也可以解决问题。

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

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

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

  示例:利用public enum MyObject{}枚举类,可以实现单例设计模式。

package test;import java.sql.Connection;import java.sql.DriverManager;import java.sql.SQLException;public enum MyObject {    connectionFactory;    private Connection connection;    private MyObject() {        try {            System.out.println("调用了MyObject的构造器");            String url = "jdbc:sqlserver://localhost:1079;databaseName=ghydb";            String username = "sa";            String password = "";            String driverName = "com.microsoft.sqlserver.jdbc.SQLServerDriver";            Class.forName(driverName);            connection = DriverManager.getConnection(url, username, password);        } catch (ClassNotFoundException e) {            e.printStackTrace();        } catch (SQLException e) {            e.printStackTrace();        }    }    public Connection getConnection() {        return connection;    }}
package extthread;import test.MyObject;public class MyThread extends Thread {    @Override    public void run() {        for (int i = 0; i < 5; i++) {            System.out.println(MyObject.connectionFactory.getConnection()                    .hashCode());        }    }}
调用了MyObject的构造器 686587801686587801686587801686587801686587801686587801686587801686587801686587801686587801686587801686587801686587801686587801686587801

  七、完善使用enum枚举实现单例模式

  示例:六中的示例将枚举类进行曝露,违反了“职责单一原则”,修改MyObject类进行完善。

package test;import java.sql.Connection;import java.sql.DriverManager;import java.sql.SQLException;public class MyObject {    public enum MyEnumSingleton {        connectionFactory;        private Connection connection;        private MyEnumSingleton() {            try {                System.out.println("创建MyObject对象");                String url = "jdbc:sqlserver://localhost:1079;databaseName=y2";                String username = "sa";                String password = "";                String driverName = "com.microsoft.sqlserver.jdbc.SQLServerDriver";                Class.forName(driverName);                connection = DriverManager.getConnection(url, username,                        password);            } catch (ClassNotFoundException e) {                e.printStackTrace();            } catch (SQLException e) {                e.printStackTrace();            }        }        public Connection getConnection() {            return connection;        }    }    public static Connection getConnection() {        return MyEnumSingleton.connectionFactory.getConnection();    }}

 

  

转载于:https://www.cnblogs.com/BigJunOba/p/8991353.html

你可能感兴趣的文章
一篇linux的通讯文章
查看>>
关于oracle中对已建表格进行约束的一些操作
查看>>
如何在CRichEditCtrl控件中直接读如RTF格式的文件
查看>>
win32 DirectUI控件开发与调用指南
查看>>
Linux系统通过AWS命令行上传文件至S3
查看>>
FlashFXP 上传文件错误
查看>>
Delphi 的内存操作函数(2): 给数组指针分配内存
查看>>
三款SDR平台对比:HackRF,bladeRF和USRP
查看>>
通过js获得html标签的值
查看>>
Sqoop 1.4.7安装与配置
查看>>
浅谈数据库资源使用的按需分配方法
查看>>
linux运维面试题1
查看>>
fabric网关模式文件上传与执行
查看>>
转:一千行 MySQL 学习笔记
查看>>
hadoop控制map个数(转)
查看>>
SVProgressHUD 用法
查看>>
【BZOJ】【1046】/【POJ】【3613】【USACO 2007 Nov】Cow Relays 奶牛接力跑
查看>>
Django表单
查看>>
ini 文件操作记要(2): 使用 TMemIniFile
查看>>
(第3篇)HDFS是什么?HDFS适合做什么?我们应该怎样操作HDFS系统?
查看>>