设计模式课程 设计模式精讲 8-3 单例设计模式-DoubleCheck双重检查实战及原理解析

1    课程讲解

1.1  为何要使用双重检查

1.2  双重检查的缺点

1.3  指令重排序讲解

1.4  指令重排序比喻(自己理解)

1.5  如何解决指令重排序问题

2    代码演练

2.1  代码演练1(双重检查  解决对象锁和类锁的问题)

2.2  代码演练2(volatile 应用:解决重排序问题)

1    课程讲解
1.1  为何要使用双重检查

在上节课的时候,多线程的时候,由于一个线程被锁,其他的线程无法访问该类,被堵塞。性能大大降低,双重检查主要是应用解决此类问题。

双重检查可以使更多的线程堵塞在方法中,而不是在类之外,这样的话,当锁被释放的时候,能够更快的执行,可以大大的提高效率。

1.2  双重检查的缺点

java在执行过程中,可能会出现指令重排序问题,(后边语句参考本节2.1代码演练2)导致a线程(先来的)的对象已经赋值,但是还没有初始化完成,这时线程b(后到的)经过判断,也开始访问对象(因为现在对象不为空),导致线程b访问的是线程a还未初始化完成的对象。由于对象并没有被完整的初始化上,系统会报异常。

1.3  指令重排序讲解

初始化的时候,实际进行了三个步骤:

a  给该对象分配内存

b  初始化该对象

c  设置该对象指向给该对象分配的内存

一般情况下,按照abc的顺序执行,但是也会有一定几率bc 颠倒。

这在单线程中执行的时候并没有问题,而且能够提高运行的效率。

1.4  指令重排序比喻(自己理解)

可以比喻如下:

人们来吃饭,

a  首先食堂拿出一份饭,

b  确定这个人是谁,

c  这个人拿走这份饭

1.5  如何解决指令重排序问题

两种方法:

a  使重排序不再发生,每个执行的进程都按照初始化的正常步骤进行 参见  本节代码2.2

b  不允许后来的线程 看到 先来的线程进行的重排序问题

2    代码演练
2.1  代码演练1(解决对象锁和类锁的问题)

测试类:

package com.geely.design.pattern.creational.singleton;

public class Test {

    /*public static void main(String [] args){
        //这样写异常,因为构造方法私有
//        LazySingleton lazySingleton = new LazySingleton();
       LazySingleton lazySingleton = LazySingleton.getInstance();
       System.out.println(lazySingleton);
    }*/

    public static void main(String [] args){
        Thread thread1 = new Thread(new T());
        Thread thread2 = new Thread(new T());
        thread1.start();
        thread2.start();
        System.out.println("结束了!!!");
    }
}

线程类:

package com.geely.design.pattern.creational.singleton;

/**
 * 注:该类为线程类,调用LazySingleton
 */
public class T implements Runnable{

    /*@Override
    public void run() {
        LazySingleton lazySingleton = LazySingleton.getInstance();
        System.out.println(Thread.currentThread().getName()+"==="+lazySingleton);

    }*/

    @Override
    public void run() {
        LazyDoubleCheckSingleton lazyDoubleCheckSingleton = LazyDoubleCheckSingleton.getInstance();
        System.out.println(Thread.currentThread().getName()+"==="+lazyDoubleCheckSingleton);
    }
}

实体类:

package com.geely.design.pattern.creational.singleton;

public class LazyDoubleCheckSingleton {
    /*
    属性私有,其他外部类,无法调用该属性,安全
     */
    private static LazyDoubleCheckSingleton lazyDoubleCheckSingleton = null;

    /**
     * 构造方法私有,其他类无法实例化该类
     */
    private LazyDoubleCheckSingleton(){
    }


    /**
     * 换一种写法,
     *
     * @return
     */
      public static LazyDoubleCheckSingleton getInstance(){
          if(lazyDoubleCheckSingleton == null){
              synchronized (LazyDoubleCheckSingleton.class){
                  if(lazyDoubleCheckSingleton == null){
                      lazyDoubleCheckSingleton = new LazyDoubleCheckSingleton();
                  }
              }
          }
          return lazyDoubleCheckSingleton;
    }


}

打印结果:

"C:\Program Files\Java\jdk1.7.0_79\bin\java.exe" -agentlib:jdwp=transport=dt_socket,address=127.0.0.1:9096,suspend=y,server=n -javaagent:C:\Users\weijingli\.IdeaIC2018.1\system\captureAgent\debugger-agent.jar=file:/C:/Users/weijingli/AppData/Local/Temp/capture.props -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.7.0_79\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.7.0_79\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.7.0_79\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.7.0_79\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.7.0_79\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.7.0_79\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.7.0_79\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.7.0_79\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.7.0_79\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.7.0_79\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.7.0_79\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.7.0_79\jre\lib\jce.jar;C:\Program Files\Java\jdk1.7.0_79\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.7.0_79\jre\lib\jfxrt.jar;C:\Program Files\Java\jdk1.7.0_79\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.7.0_79\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.7.0_79\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.7.0_79\jre\lib\resources.jar;C:\Program Files\Java\jdk1.7.0_79\jre\lib\rt.jar;F:\xiangmu3\Xin\Idea\design_pattern\target\classes;D:\java\devolopKit\idea\anZh\IntelliJ IDEA Community Edition 2018.1.4\lib\idea_rt.jar" com.geely.design.pattern.creational.singleton.Test
Connected to the target VM, address: '127.0.0.1:9096', transport: 'socket'
结束了!!!
Thread-0===com.geely.design.pattern.creational.singleton.LazyDoubleCheckSingleton@8fea539
Disconnected from the target VM, address: '127.0.0.1:9096', transport: 'socket'
Thread-1===com.geely.design.pattern.creational.singleton.LazyDoubleCheckSingleton@8fea539

Process finished with exit code 0
2.2  代码演练2(volatile 应用:解决重排序问题)

java语言规范中规定:所有线程执行java程序时,必须要遵守intra-thread semantics

intra-thread semantics 保证重排序不会改变单线程内的程序执行结果。

换句话说,intra-thread semantics 允许那些在单线程内,不会改变单线程程序执行结果的重排序。

package com.geely.design.pattern.creational.singleton;

public class LazyDoubleCheckSingleton {
    /*
    1  volatile关键字的作用
       将当前处理器缓存行的数据写回到内存,该操作会使在其他cpu内存中缓存了该内存地址的数据无效。它们又从共享内存同步数据。  如此操作保存内存的可见性。j
    2
     */
    private volatile static LazyDoubleCheckSingleton lazyDoubleCheckSingleton = null;

    /**
     * 构造方法私有,其他类无法实例化该类
     */
    private LazyDoubleCheckSingleton(){
    }


    /**
     * 换一种写法,
     *
     * @return
     */
      public static LazyDoubleCheckSingleton getInstance(){
          if(lazyDoubleCheckSingleton == null){
              synchronized (LazyDoubleCheckSingleton.class){
                  if(lazyDoubleCheckSingleton == null){
                      lazyDoubleCheckSingleton = new LazyDoubleCheckSingleton();
                  }
              }
          }
          return lazyDoubleCheckSingleton;
    }


}

intra-thread semantics 保证重排序不会改变单线程内的程序执行结果。

换句话说,intra-thread semantics 允许那些在单线程内,不会改变单线程程序执行结果的重排序。

猜你喜欢

转载自www.cnblogs.com/1446358788-qq/p/11368146.html