• 如何保证单例模式在多线程中的线程安全性


       如何保证单例模式在多线程中的线程安全性

            对大数据、分布式、高并发等知识的学习必须要有多线程的基础。这里讨论一下如何在多线程的情况下设计单例模式。在23中设计模式中单例模式是比较常见的,在非多线程的情况下写单例模式,考虑的东西会很少,但是如果将多线程和单例模式结合起来,考虑的事情就变多了,如果使用不当(特别是在生成环境中)就会造成严重的后果。所以如何使单例模式在多线程中是安全的显得尤为重要,下面介绍各个方式的优缺点以及可用性:

           1.立即加载(饿汉模式)

            立即加载模式就是在调用getInstance()方法前,实例就被创建了,例:

    public class MyObject {
     // 立即加载方式  ==饿汉模式
    private static MyObject myObject=new MyObject();
    private MyObject(){
    }
    public static MyObject getInstance(){
    return myObject;
    }
    }

    -------------------------------------------------------------------

    public class MyThread extends Thread{
    public void run(){
    System.out.println(MyObject.getInstance().hashCode());
    }
    }

    ------------------------------------------------------------------

    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();
    }
    }

         控制台打印:

    714682869
    714682869
    714682869

        控制台打印出3个相同的hashCode,说明只有一个对象,这就是立即加载的单例模式。但是这种模式有一个缺点,就是不能有其他的实例变量,因为getInstance()方法没有同步,所以可能出现非线程安全问题。

       2.延迟加载(懒汉模式)

        延迟加载就是在getInstance()方法中创建实例,例:

         public class MyObject {
    private static MyObject myObject;
    private MyObject(){
         }
     public static MyObject getInstance(){
    // 延迟加载
     if(myObject!=null){  
    }else{
    myObject=new MyObject();
     }
    return myObject;
         }
    }

    -------------------------------------------------------------------

    public class MyThread extends Thread{
    public void run(){
    System.out.println(MyObject.getInstance().hashCode());
    }
    }

    -------------------------------------------------------------------

    public class Run {
    public static void main(String[] args) {
    MyThread t1=new MyThread();
    t1.start();
    }
    }

         控制台打印:

    1701381926

        控制台打印出一个实例。缺点:在多线程的环境中,就会出现取多个实例的情况,与单例模式的初衷相背离。所以在多线程的环境中,此实例代码是错误的。

        3.延迟加载中使用synchronized修饰方法

        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) {
    // TODO: handle exception
    e.printStackTrace();
    }
    return myObject;
    }
    }

     -------------------------------------------------------------------

    public class MyThread extends Thread{
    public void run(){
    System.out.println(MyObject.getInstance().hashCode());
    }
    }      

    -------------------------------------------------------------------

    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();
    }
    }

         控制台打印:

     1069480624
    1069480624
    1069480624

         虽然得到了相同的实例,但是我们知道synchronized是同步的,一个线程必须等待另一个线程释放锁之后才能执行,影响了效率。

          4.延迟加载中使用同步代码块,对类加锁

            public class MyObject {
    private static MyObject myObject;
    private MyObject(){
    }
    public static MyObject getInstance(){
    try {
    synchronized(MyObject.class){
    if(myObject!=null){
    }else{
    Thread.sleep(3000);
    myObject=new MyObject();
    }
    }
    } catch (InterruptedException e) {
    // TODO: handle exception
    e.printStackTrace();
    }
    return myObject;
    }
    }

     -------------------------------------------------------------------

    public class MyThread extends Thread {
    public void run(){
    System.out.println(MyObject.getInstance().hashCode());
    }
    }

     -------------------------------------------------------------------

    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();
    }
    }

           控制台打印:

    1743911840
    1743911840
    1743911840

           此代码虽然是正确的,但getInstance()方法里的代码都是同步的了,其实也和第三种方式一样会降低效率

           5.使用DCL双检查锁机制

            DCL双检查锁机制即使用volatile关键字(使变量在多个线程中可见)修改对象和synchronized代码块

           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();
    // TODO: handle exception
    }
        return myObject;
        }
    }

     -------------------------------------------------------------------

    public class MyThread extends Thread {
    public void run(){
    System.out.println(MyObject.getInstance().hashCode());
    }
    }    

     -------------------------------------------------------------------

    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();
    }
    }

         控制台打印:

    798941612
    798941612
    798941612

          使用DCL双检查锁机制,成功解决了延迟加载模式中遇到的多线程问题,实现了线程安全。其实大多数多线程结合单例模式情况下使用DCL是一种好的解决方案。

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

           public class MyObject {
    // 内部类方式
    private static class MyObjectHandler{
    private static MyObject myObject=new MyObject();
    }
    private MyObject(){

    }
    public static MyObject getInstance(){
    return MyObjectHandler.myObject;
    }
    }

    -------------------------------------------------------------------

    public class MyThread extends Thread {
    public void run(){
    System.out.println(MyObject.getInstance().hashCode());
    }
    }

     -------------------------------------------------------------------

    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();

    }
    }

        控制台打印:

    1743911840
    1743911840
    1743911840

             使用静态内置类可以解决多线程中单例模式的非线程安全的问题,实现线程安全,但是如果对象是序列化的就无法达到效果了。

           7.序列化与反序列化的单例模式

     需要readResolve方法

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

    -------------------------------------------------------------------

    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: handle exception
    } 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) {
    // TODO: handle exception
    } catch(IOException e){
    e.printStackTrace();
    } catch(ClassNotFoundException e){
    e.printStackTrace();
    }
    }
    }   

          控制台打印:

     1988716027
    调用了readResolve方法
    1988716027

          调用了readResolve方法后就是单例了,如果我们注释掉readResolve方法,

          控制台打印:

    977199748
    536468534

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

            public class MyObject {
    private static MyObject instance=null;
    private MyObject(){

    }
    static {
    instance=new MyObject();
    }
    public static MyObject getInstance(){
    return instance;
    }
    }

    -------------------------------------------------------------------

    public class MyThread extends Thread{

    public void run(){
    for (int i = 0; i <5; i++) {
    System.out.println(MyObject.getInstance().hashCode());
    }
    }
    }

    -------------------------------------------------------------------

    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();

    }
    }

       控制台打印:

    798941612
    798941612
    798941612

    https://blog.csdn.net/gan785160627/article/details/81946242

    应用单例模式时,类只能有一个对象实例,这么做的目的是避免不一致状态。

    饿汉式单例:(立即加载)

    1.  
      // 饿汉式单例
    2.  
      public class Singleton1 {
    3.  
       
    4.  
      // 指向自己实例的私有静态引用,主动创建
    5.  
      private static Singleton1 singleton1 = new Singleton1();
    6.  
       
    7.  
      // 私有的构造方法
    8.  
      private Singleton1(){}
    9.  
       
    10.  
      // 以自己实例为返回值的静态的公有方法,静态工厂方法
    11.  
      public static Singleton1 getSingleton1(){
    12.  
      return singleton1;
    13.  
      }
    14.  
      }

    懒汉式单例:(延迟加载)

    1.  
      // 懒汉式单例
    2.  
      public class Singleton2 {
    3.  
       
    4.  
      // 指向自己实例的私有静态引用
    5.  
      private static Singleton2 singleton2;
    6.  
       
    7.  
      // 私有的构造方法
    8.  
      private Singleton2(){}
    9.  
       
    10.  
      // 以自己实例为返回值的静态的公有方法,静态工厂方法
    11.  
      public static Singleton2 getSingleton2(){
    12.  
      // 被动创建,在真正需要使用时才去创建
    13.  
      if (singleton2 == null) {
    14.  
      singleton2 = new Singleton2();
    15.  
      }
    16.  
      return singleton2;
    17.  
      }
    18.  
      }

    多线程下线程安全的懒汉式单例(饿汉式本身是线程安全的):

    1)、同步延迟加载 — synchronized方法

    1.  
      // 线程安全的懒汉式单例
    2.  
      public class Singleton2 {
    3.  
       
    4.  
      private static Singleton2 singleton2;
    5.  
       
    6.  
      private Singleton2(){}
    7.  
       
    8.  
      // 使用 synchronized 修饰,临界资源的同步互斥访问
    9.  
      public static synchronized Singleton2 getSingleton2(){
    10.  
      if (singleton2 == null) {
    11.  
      singleton2 = new Singleton2();
    12.  
      }
    13.  
      return singleton2;
    14.  
      }
    15.  
      }

    2)、同步延迟加载 — synchronized块

    1.  
      // 线程安全的懒汉式单例
    2.  
      public class Singleton2 {
    3.  
       
    4.  
      private static Singleton2 singleton2;
    5.  
       
    6.  
      private Singleton2(){}
    7.  
       
    8.  
       
    9.  
      public static Singleton2 getSingleton2(){
    10.  
      synchronized(Singleton2.class){ // 使用 synchronized 块,临界资源的同步互斥访问
    11.  
      if (singleton2 == null) {
    12.  
      singleton2 = new Singleton2();
    13.  
      }
    14.  
      }
    15.  
      return singleton2;
    16.  
      }
    17.  
      }

    3)、同步延迟加载 — 使用内部类实现延迟加载

    1.  
      // 线程安全的懒汉式单例
    2.  
      public class Singleton5 {
    3.  
       
    4.  
      // 私有内部类,按需加载,用时加载,也就是延迟加载
    5.  
      private static class Holder {
    6.  
      private static Singleton5 singleton5 = new Singleton5();
    7.  
      }
    8.  
       
    9.  
      private Singleton5() {
    10.  
       
    11.  
      }
    12.  
       
    13.  
      public static Singleton5 getSingleton5() {
    14.  
      return Holder.singleton5;
    15.  
      }
    16.  
      }

    4)双重检测

    1.  
      // 线程安全的懒汉式单例
    2.  
      public class Singleton3 {
    3.  
       
    4.  
      //使用volatile关键字防止重排序,因为 new Instance()是一个非原子操作,可能创建一个不完整的实例
    5.  
      private static volatile Singleton3 singleton3;
    6.  
       
    7.  
      private Singleton3() {
    8.  
      }
    9.  
       
    10.  
      public static Singleton3 getSingleton3() {
    11.  
      // Double-Check idiom
    12.  
      if (singleton3 == null) {
    13.  
      synchronized (Singleton3.class) { // 1
    14.  
      // 只需在第一次创建实例时才同步
    15.  
      if (singleton3 == null) { // 2
    16.  
      singleton3 = new Singleton3(); // 3
    17.  
      }
    18.  
      }
    19.  
      }
    20.  
      return singleton3;
    21.  
      }
    22.  
      }

    5)ThreadLocal

    1.  
      public class Singleton {
    2.  
       
    3.  
      // ThreadLocal 线程局部变量,将单例instance线程私有化
    4.  
      private static ThreadLocal<Singleton> threadlocal = new ThreadLocal<Singleton>();
    5.  
      private static Singleton instance;
    6.  
       
    7.  
      private Singleton() {
    8.  
       
    9.  
      }
    10.  
       
    11.  
      public static Singleton getInstance() {
    12.  
       
    13.  
      // 第一次检查:若线程第一次访问,则进入if语句块;否则,若线程已经访问过,则直接返回ThreadLocal中的值
    14.  
      if (threadlocal.get() == null) {
    15.  
      synchronized (Singleton.class) {
    16.  
      if (instance == null) { // 第二次检查:该单例是否被创建
    17.  
      instance = new Singleton();
    18.  
      }
    19.  
      }
    20.  
      threadlocal.set(instance); // 将单例放入ThreadLocal中
    21.  
      }
    22.  
      return threadlocal.get();
    23.  
      }
    24.  
      }
  • 相关阅读:
    Oracle架构实现原理、含五大进程解析(图文详解)
    Oracle架构实现原理、含五大进程解析(图文详解)
    mysqldump --flush-logs
    mysql dump 参数
    Windows 8.1 PLSQL_32连接到RHEL6.1 Oracle10gr2_64
    Windows 8.1 PLSQL_32连接到RHEL6.1 Oracle10gr2_64
    echarts-单柱状图
    echarts-all.js:1 Dom’s width & height should be ready before init.
    如果是在有master上开启了该参数,记得在slave端也要开启这个参数(salve需要stop后再重新start),否则在master上创建函数会导致replaction中断。
    mysql 创建函数 error Code: 1227. Access denied;
  • 原文地址:https://www.cnblogs.com/whymoney1000/p/11420593.html
Copyright © 2020-2023  润新知