美文网首页
设计模式-单例模式(二)

设计模式-单例模式(二)

作者: 巨子联盟 | 来源:发表于2018-05-22 06:58 被阅读0次

    单例模式,目标就是在JVM内创建线程安全的单个实例.用途很多,比如加载资源配置文件.
    在Java中实现单例的方法有很多种.有些是线程安全有不是.线程安全的实现方式有:

    1. 双重检查机制的懒汉式单例
    2. 静态内部类实现的单例
    3. 静态代码块实现的单例
    4. 使用枚举类实现的单例
      其实还有一种有一个线程安全的Map登记实现的单例

    下面线程安全和不安全的都分析下:

    • 方法1 饿汉式 线程安全待定,个人觉得是安全,网上都说不安全
    package com.byedbl.singleton.unsafe.method1;
    
    /**
     * 饿汉模式
     * 这种测得为啥一直是线程安全的??
     *
     * @author : zengzhijun
     * @date : 2018/5/18 17:56
     **/
    public class MyObject {
    
        // 立即加载方式==饿汉模式
        private static MyObject myObject = new MyObject();
    
        private MyObject() {
        }
    
        public static MyObject getInstance()  {
            // 此代码版本为立即加载
            // 此版本代码的缺点是不能有其它实例变量
            // 因为getInstance()方法没有同步
            // 所以有可能出现非线程安全问题
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return myObject;
        }
    
    }
    

    这种方法据说是不安全,但是实际测试的时候每次都是一个实例.具体测得代码如下:
    先创建一个线程类

    package com.byedbl.singleton.unsafe.method1;
    
    
    public class MyThread extends Thread {
    
        @Override
        public void run() {
            System.out.println(MyObject.getInstance().hashCode());
        }
    
    }
    

    再用多个线程一起去跑

    package com.byedbl.singleton.unsafe.method1;
    
    
    import java.util.concurrent.CountDownLatch;
    
    public class Run {
    
        public static void main(String[] args) {
    
            int len = 1000;
            CountDownLatch latch = new CountDownLatch(len);
            MyThread[] threads = new MyThread[len];
            for(int i =0 ;i< len;i++) {
                threads[i] = new MyThread();
                latch.countDown();
            }
    
            try {
                latch.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            for(int i= 0;i<len;i++) {
                threads[i].start();
            }
    
        }
    
    }
    
    

    结果都是只有一个实例出来,从类的加载流程来看,private static MyObject myObject = new MyObject(); 这句代码是只执行一次的.我们看JDK源码里java.lang.Runtime这个类的实现也是这种方式.
    真正的缺点是不能有实例变量吧.

    • 方法1 懒汉式 线程不安全
    package com.byedbl.singleton.unsafe.method2;
    
    /**
     * 懒汉式,没加锁,不安全,加锁性能也低
     * @author : zengzhijun
     * @date : 2018/5/18 17:55
     **/
    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;
        }
    
    }
    

    这个是真的不安全,在没有用CountDownLatch的时候就可以测试出来,代码如下:

    package com.byedbl.singleton.unsafe.method2;
    
    
    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();
        }
    
    }
    
    

    MyThread 和上面方法1一个套路.
    结果如下:

    1770731361
    903341402
    186668687
    

    打印出3个实例


    • 方法3 懒汉式升级版1 线程安全,但性能比较低
    package com.byedbl.singleton.safe.normal.method3;
    
    /**
     * 这个虽然线程安全,但是效率太低了
     * @author : zengzhijun
     * @date : 2018/5/18 19:00
     **/
    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效率比较低,不推荐.

    • 方法4 懒汉式升级版2 线程安全,但性能比较低
    package com.byedbl.singleton.safe.normal.method4;
    
    /**
     * 这个虽然线程安全,但是效率太低了
     * @author : zengzhijun
     * @date : 2018/5/18 19:00
     **/
    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;
        }
    
    }
    
    

    虽然这个不是在方法上加锁,但是将整个代码块锁起来了,效率还是一样的低.不推荐.

    • 方法5 懒汉式升级版3 线程不安全,
    package com.byedbl.singleton.unsafe.method5;
    
    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;
        }
    
    }
    
    
    

    那我们能不能少加一行代码的锁呢?答案是否定的,这样就线程不安全了.而要解决这个问题,就得用下面这种比较推荐的,双重检查机制的懒汉式

    • 方法6 双重检查机制的懒汉式 线程安全,推荐指数4颗星

    package com.byedbl.singleton.safe.suggest.method6;
    
    /**
     * 线程安全之双检测机制
     * DCL 双检查锁机制
     * @author : zengzhijun
     * @date : 2018/5/18 19:08
     **/
    public class MyObject {
    
        //要声明为 volatile
        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
    
    }
    
    
    

    这个代码我们用多线程去跑也是没问题的.

    package com.byedbl.singleton.safe.suggest.method6;
    
    
    import java.util.concurrent.CountDownLatch;
    
    public class Run {
    
        public static void main(String[] args) {
            int len = 100;
            CountDownLatch latch = new CountDownLatch(len);
            MyThread[] threads = new MyThread[len];
            for(int i =0 ;i< len;i++) {
                threads[i] = new MyThread();
                latch.countDown();
            }
    
            try {
                latch.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            for(int i= 0;i<len;i++) {
                threads[i].start();
            }
    
        }
    
    }
    
    
    • 方法7 静态内部类实现的单例,线程安全,推荐指数2颗星

    package com.byedbl.singleton.safe.suggest.method7;
    
    import java.io.ObjectStreamException;
    import java.io.Serializable;
    
    /**
     * 线程安全之静态内部类
     *
     *
     * @author : zengzhijun
     * @date : 2018/5/18 19:08
     **/
    public class MyObject implements Serializable{
    
        // 内部类方式
        private static class MyObjectHandler {
            private static 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;
        }
    }
    
    
    

    用这种方法需要注意的是要实现 Serializable接口并覆盖readResolve方法,主要是为了解决持久化在反序列化时得到的是不同的实例问题,可以注释readResolve方法,用下面的代码测试看到效果:

    package com.byedbl.singleton.safe.suggest.method7;
    
    import java.io.*;
    
    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();
            }
    
        }
    
    }
    
    
    • 方法8 静态代码块实现的单例,线程安全,推荐指数3颗星

    package com.byedbl.singleton.safe.suggest.method8;
    
    
    /**
     * 静态代码块中的代码在使用类的时候就已经执行了,所以可以利用这个特性来实现单例
     * @author : zengzhijun
     * @date : 2018/5/18 19:21
     **/
    public class MyObject {
    
        private static MyObject instance = null;
    
        private MyObject() {
        }
    
        static {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            instance = new MyObject();
        }
    
        public static MyObject getInstance() {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return instance;
        }
    
    }
    
    
    • 方法9 使用枚举类实现的单例 线程安全,推荐指数5颗星

    package com.byedbl.singleton.safe.suggest.method9;
    
    import org.apache.commons.lang3.RandomUtils;
    
    /**
     * 使用枚举类实现单例
     * 终极方法,能用静态枚举类的就用静态枚举类
     * @author : zengzhijun
     * @date : 2018/5/18 19:27
     **/
    public class MyObject {
    
        private enum MyEnumSingleton {
            INSTANCE;
            String arg ;
            private MyObject myObject = null;
            MyEnumSingleton() {
                //这里可以初始化变量,当然也可以在一个 static代码块里面初始化
                arg = "get arg from properties" + RandomUtils.nextInt();
                myObject = new MyObject();
            }
    
    
            public MyObject getInstance() {
                return myObject;
            }
        }
    
        public static MyObject getConnection() {
            return MyEnumSingleton.INSTANCE.getInstance();
        }
        public String getArg() {
            return MyEnumSingleton.INSTANCE.arg;
        }
    }
    

    枚举类在JVM里面天然就是单例的.这种方法比较推荐.

    相关文章

      网友评论

          本文标题:设计模式-单例模式(二)

          本文链接:https://www.haomeiwen.com/subject/lfandftx.html