zoukankan      html  css  js  c++  java
  • (六)单例模式与多线程时的安全问题以及解决办法

    单例模式:

    首先明白单例模式是什么,简单来讲,就是说多个线程获取到的对象是同一个对象,只new了一次,那么创建单例有两种方式:

    1.立即加载:即在程序一开始就new了一个对象,之后用的时候直接进行获取,这种一般是定义静态对象,因为静态对象会预加载。

    2.延迟加载:顾名思义,指在第一次用的时候才创建对象,除了第一次获取以外的是直接获取。

    所以,当我们将单例模式和多线程结合,会有什么问题呢?

    如下是一个延迟加载和多线程的例子:

    MyObject.java:延迟加载方式创建对象

    package 第六章;
    
    public class MyObject {
        private static MyObject myObject;
        public MyObject(){
    
        }
        public static MyObject getInstance(){
            if(myObject == null){    //只有第一次创建对象
                myObject = new MyObject();
            }
            return myObject;
        }
    }
    View Code

    MyThread.java:  输出当前线程获取到的对象的hashcode

    package 第六章;
    
    public class MyThread extends Thread {
        public void run(){
            System.out.println("线程"+Thread.currentThread().getName()+"@@@"+MyObject.getInstance().hashCode());
        }
    }
    View Code

    test.java: 运行类,创建三个线程并运行,

    package 第六章;
    
    public class test {
        public static void main(String[] args){
            MyThread myThread1 = new MyThread();
            MyThread myThread2 = new MyThread();
            MyThread myThread3 = new MyThread();
            myThread1.start();
            myThread2.start();
            myThread3.start();
        }
    }
    View Code

    运行结果如下:

    很明显,我们可以看到三个线程获取到的对象并不都是同一个,原因很简单,由于在创建对象的代码没有加锁,是异步的,所以有多个线程if条件判断成立,new了多个对象,解决方法也很简单,将相应的代码或者方法变成同步的。如下, 

    MyObject.java:  sleep模拟创建过程

    package 第六章;
    
    public class MyObject {
        private static MyObject myObject;
        public MyObject(){
    
        }
        public static MyObject getInstance(){
            synchronized (MyLock.lock) {
                try{
                    if (myObject == null) {    //只有第一次创建对象
                        Thread.sleep(1000);
                        myObject = new MyObject();
                        System.out.println("时间"+System.currentTimeMillis());
                    }
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
                return myObject;
            }
        }
    }
    View Code

    运行之后可以看到实现了单例效果,对象只创建了一次

    但是,这种方法很蠢,因为每一次一个线程想要获取实例,就必须等待上一个线程之内的同步代码块执行完毕才行,这样子效率很低,事实上,我们只是想在第一次加载对象的时候使用同步,之后我们都不需要同步,因为对象已经创建好了,异步直接获取就行。

    解决办法:

    DCL双检查机制:只同步创建对象的代码块,并且进行两次检查,防止多次创建,

    更改MyObject.java

    package 第六章;
    
    public class MyObject {
        private static MyObject myObject;
        public MyObject(){
    
        }
        public static MyObject getInstance(){
                try{
                    if (myObject == null) {    //只有第一次创建对象
                        Thread.sleep(1000);
                        synchronized (MyLock.lock) {
                            if(myObject==null){
                                myObject = new MyObject();
                                System.out.println("时间" + System.currentTimeMillis());
                            }
                        }
                    }
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
                return myObject;
        }
    }
    View Code

    个人感觉还是比较巧妙的吧,这样子相比原来尽可能的异步执行了更多的代码块。

    当然也可以使用enum,static代码块实现单例,因为枚举类的构造函数是预加载的,static代码块也是预加载的,不过这种就是立即加载了,实现方法,

    static代码块,将new对象的语句写在static{}之中就ok

    还有静态内置类,但是这种在序列化和反序列化时候会出现一定的问题,需要用readResolve()方法,emmm,这块没太看懂想干什么,日后再说

  • 相关阅读:
    053-1
    多项式ADT笔记(数据结构c版)
    052-188
    052-187
    052-186
    052-185
    052-184
    052-183
    052-182
    JS中的垃圾回收(GC)
  • 原文地址:https://www.cnblogs.com/eenio/p/11394947.html
Copyright © 2011-2022 走看看