zoukankan      html  css  js  c++  java
  • 单例模式和多线程

    单例模式常分为:“懒汉模式”和”饿汉模式“

    懒汉模式就是立即加载,指的是在调用方法前实例对象就已经创建完成了

    饿汉模式就是延迟加载,指的是在调用方法时实例对象才会被创建

    常见的懒汉模式

     1 public class MyObject {
     2     private static MyObject myObject = new MyObject();
     3     private MyObject(){}
     4     /**
     5      *  该版本是立即加载
     6      *  缺点是不能有其他实例变量
     7      *  getInstance方法没有同步
     8      *  可能出现线程不安全问题
     9      * @return
    10      */
    11     public static MyObject getInstance(){
    12         return myObject;
    13     }
    14 
    15     public static void main(String[] args) {
    16         Runnable runnable = new Runnable() {
    17             @Override
    18             public void run() {
    19                 System.out.println(com.qf.test02.MyObject.getInstance().hashCode());
    20             }
    21         };
    22         Thread a = new Thread(runnable);
    23         a.start();
    24         Thread b = new Thread(runnable);
    25         b.start();
    26     }
    27 }
    View Code

    ------------------------------------------------------console------------------------------------------------------

    635845204
    635845204

    实际上getInstance获得的都是同一个对象,实现了立即加载的单例设计模型

    常见的懒汉模式

     1 public class MyObject {
     2     private static MyObject myObject ;
     3     private MyObject(){}
     4 
     5     public static MyObject getInstance(){
     6         if (myObject == null) {
     7             myObject = new MyObject();
     8         }
     9         return myObject;
    10     }
    11 
    12     public static void main(String[] args) {
    13 
    14         Thread t = new Thread(new Runnable() {
    15             @Override
    16             public void run() {
    17                 System.out.println(MyObject.getInstance().hashCode());
    18             }
    19         });
    20         t.start();
    21     }
    22 }
    View Code

    ------------------------------------------------------console------------------------------------------------------

    1700405589

    本次虽然是取得一个实例,但是在多线程环境下很可能出现多个实例的情况,与单例模式初衷相背离

    模拟出现多个实例的环境:

     1 public class MyObject {
     2     private static MyObject myObject ;
     3     private MyObject(){}
     4 
     5     public static MyObject getInstance(){
     6         try {
     7             if (myObject == null) {
     8                 Thread.sleep(1000);
     9                 myObject = new MyObject();
    10             }
    11         } catch (InterruptedException e) {
    12             e.printStackTrace();
    13         }
    14         return myObject;
    15     }
    16 
    17     public static void main(String[] args) {
    18 
    19         for (int i = 0;i < 5;i++) {
    20             Thread t = new Thread(new Runnable() {
    21                 @Override
    22                 public void run() {
    23                     System.out.println(MyObject.getInstance().hashCode());
    24                 }
    25             });
    26             t.start();
    27         }
    28     }
    29 }
    View Code

    ------------------------------------------------------console------------------------------------------------------

    635845204
    2075012821
    635845204
    100758745
    1833680959

    出现了不同的hashcode,说明创建出了不同的对象

    解决方案1:声明时采用synchronized关键字

     1 public class MyObject {
     2     private static MyObject myObject ;
     3     private MyObject(){}
     4 
     5     public synchronized static MyObject getInstance(){
     6         try {
     7             if (myObject == null) {
     8                 Thread.sleep(1000);
     9                 myObject = new MyObject();
    10             }
    11         } catch (InterruptedException e) {
    12             e.printStackTrace();
    13         }
    14         return myObject;
    15     }
    16 
    17     public static void main(String[] args) {
    18 
    19         for (int i = 0;i < 5;i++) {
    20             Thread t = new Thread(new Runnable() {
    21                 @Override
    22                 public void run() {
    23                     System.out.println(MyObject.getInstance().hashCode());
    24                 }
    25             });
    26             t.start();
    27         }
    28     }
    29 }
    View Code

    ------------------------------------------------------console------------------------------------------------------

    123504074
    123504074
    123504074
    123504074
    123504074

    得到了相同的实例对象。

    但是这种方法采用同步运行,下一个线程想要取得对象必须等上一个线程释放对象,效率比较低

    解决方案2:同步代码块1

     1 public class MyObject {
     2     private static MyObject myObject ;
     3     private MyObject(){}
     4 
     5     public static MyObject getInstance(){
     6         try {
     7             synchronized (MyObject.class) {
     8                 if (myObject == null) {
     9                     Thread.sleep(1000);
    10                     myObject = new MyObject();
    11                 }
    12             }
    13         } catch (InterruptedException e) {
    14             e.printStackTrace();
    15         }
    16         return myObject;
    17     }
    18 
    19     public static void main(String[] args) {
    20 
    21         for (int i = 0;i < 5;i++) {
    22             Thread t = new Thread(new Runnable() {
    23                 @Override
    24                 public void run() {
    25                     System.out.println(MyObject.getInstance().hashCode());
    26                 }
    27             });
    28             t.start();
    29         }
    30     }
    31 }
    View Code

    ------------------------------------------------------console------------------------------------------------------

    1883420913
    1883420913
    1883420913
    1883420913
    1883420913

    和synchronized声明方法的方式一样都是同步运行的,效率很低

    解决方案3:同步代码块,同步部分重要代码

     1 public class MyObject {
     2     private static MyObject myObject ;
     3     private MyObject(){}
     4 
     5     public static MyObject getInstance(){
     6         try {
     7             if (myObject == null) {
     8                 Thread.sleep(1000);
     9                 //有非线程安全问题
    10                 synchronized (MyObject.class) {
    11                     myObject = new MyObject();
    12                 }
    13             }
    14         } catch (InterruptedException e) {
    15             e.printStackTrace();
    16         }
    17         return myObject;
    18     }
    19 
    20     public static void main(String[] args) {
    21 
    22         for (int i = 0;i < 5;i++) {
    23             Thread t = new Thread(new Runnable() {
    24                 @Override
    25                 public void run() {
    26                     System.out.println(MyObject.getInstance().hashCode());
    27                 }
    28             });
    29             t.start();
    30         }
    31     }
    32 }
    View Code

    ------------------------------------------------------console------------------------------------------------------

    100758745
    1460486463
    635845204
    161899259
    1833680959

    效率得到了提高,但是会出现非线程安全的问题

    解决方案4:DCL双锁检查

     1 public class MyObject {
     2     private static MyObject myObject ;
     3     private MyObject(){}
     4 
     5     public static MyObject getInstance(){
     6         try {
     7             if (myObject == null) {
     8                 Thread.sleep(1000);
     9                 //有非线程安全问题
    10                 synchronized (MyObject.class) {
    11                     if(myObject == null) {
    12                         myObject = new MyObject();
    13                     }
    14                 }
    15             }
    16         } catch (InterruptedException e) {
    17             e.printStackTrace();
    18         }
    19         return myObject;
    20     }
    21 
    22     public static void main(String[] args) {
    23 
    24         for (int i = 0;i < 5;i++) {
    25             Thread t = new Thread(new Runnable() {
    26                 @Override
    27                 public void run() {
    28                     System.out.println(MyObject.getInstance().hashCode());
    29                 }
    30             });
    31             t.start();
    32         }
    33     }
    34 }
    View Code

    ------------------------------------------------------console------------------------------------------------------

    1833680959
    1833680959
    1833680959
    1833680959
    1833680959

    成功解决”懒汉模式“遇到多线程的问题,常用方案

    静态内置类实现单例模式

    1) 如何保证线程安全: 因为内部的静态类只会被加载一次,只会有一个实例对象,所以是线程安全的

    2) 内部类的加载机制: java中的内部类是延时加载的,只有在第一次使用时加载;不使用就不加载

     1 public class MyObject {
     2     private static class MyObjectHandler{
     3         private static MyObject myObject = new MyObject();
     4     }
     5     private MyObject(){}
     6 
     7     public static MyObject getInstance(){
     8         return MyObjectHandler.myObject;
     9     }
    10 
    11     public static void main(String[] args) {
    12         for (int i = 0;i < 5;i++) {
    13             Thread t = new Thread(new Runnable() {
    14                 @Override
    15                 public void run() {
    16                     System.out.println(com.qf.test02.MyObject.getInstance().hashCode());
    17                 }
    18             });
    19             t.start();
    20         }
    21     }
    22 }
    View Code

    ------------------------------------------------------console------------------------------------------------------

    1654248691
    1654248691
    1654248691
    1654248691
    1654248691

    序列化与反序列化实现

    反序列化中反射会破坏单例模式:

      一般来说, 一个类实现了 Serializable接口, 我们就可以把它往内存地写再从内存里读出而"组装"成一个跟原来一模一样的对象.不过当序列化遇到单例时,这里边就有了个问题: 从内存读出而组装的对象破坏了单例的规则. 单例是要求一个JVM中只有一个类对象的, 而现在通过反序列化,一个新的对象克隆了出来,与以前序列化的对象不能equlas

    静态内部类可以实现线程安全,但是如果遇到序列化对象时,使用默认方式运行的结果还是多例的

    多例环境复现:

     1 public class MyObject implements Serializable {
     2     private static class MyObjectHandler{
     3         private static MyObject myObject = new MyObject();
     4     }
     5     private MyObject(){}
     6 
     7     public static MyObject getInstance(){
     8         return MyObjectHandler.myObject;
     9     }
    10 
    11     public static void main(String[] args) {
    12         try {
    13             MyObject myObject = getInstance();
    14             FileOutputStream fos = new FileOutputStream(new File("test.txt"));
    15             ObjectOutputStream oos = new ObjectOutputStream(fos);
    16             oos.writeObject(myObject);
    17             oos.close();
    18             fos.close();
    19             System.out.println(myObject.hashCode());
    20         } catch (FileNotFoundException e) {
    21             e.printStackTrace();
    22         } catch (IOException e) {
    23             e.printStackTrace();
    24         }
    25 
    26         try {
    27             FileInputStream fis = new FileInputStream(new File("test.txt"));
    28             ObjectInputStream ois = new ObjectInputStream(fis);
    29             MyObject mm = (MyObject) ois.readObject();
    30             ois.close();
    31             fis.close();
    32             System.out.println(mm.hashCode());
    33         } catch (FileNotFoundException e) {
    34             e.printStackTrace();
    35         } catch (IOException e) {
    36             e.printStackTrace();
    37         } catch (ClassNotFoundException e) {
    38             e.printStackTrace();
    39         }
    40     }
    41 }
    View Code

    ------------------------------------------------------console------------------------------------------------------

    21685669
    1480010240

    解决方案:反序列化时使用readResolve()方法

      序列化操作提供了一个很特别的钩子(hook):类中具有一个被实例化的方法readresolve(),这个方法可以确保类的开发人员在序列化将会返回怎样的object上具有发言权。足够奇怪的,readresolve()并不是静态的,但是在序列化创建实例的时候被引用

     1 public class MyObject implements Serializable {
     2     private static class MyObjectHandler{
     3         private static MyObject myObject = new MyObject();
     4     }
     5     private MyObject(){}
     6 
     7     public static MyObject getInstance(){
     8         return MyObjectHandler.myObject;
     9     }
    10 
    11     protected Object readResolve(){
    12         System.out.println("调用了readResolve方法");
    13         return MyObjectHandler.myObject;
    14     }
    15 
    16     public static void main(String[] args) {
    17         try {
    18             MyObject myObject = getInstance();
    19             FileOutputStream fos = new FileOutputStream(new File("test.txt"));
    20             ObjectOutputStream oos = new ObjectOutputStream(fos);
    21             oos.writeObject(myObject);
    22             oos.close();
    23             fos.close();
    24             System.out.println(myObject.hashCode());
    25         } catch (FileNotFoundException e) {
    26             e.printStackTrace();
    27         } catch (IOException e) {
    28             e.printStackTrace();
    29         }
    30 
    31         try {
    32             FileInputStream fis = new FileInputStream(new File("test.txt"));
    33             ObjectInputStream ois = new ObjectInputStream(fis);
    34             MyObject mm = (MyObject) ois.readObject();
    35             ois.close();
    36             fis.close();
    37             System.out.println(mm.hashCode());
    38         } catch (FileNotFoundException e) {
    39             e.printStackTrace();
    40         } catch (IOException e) {
    41             e.printStackTrace();
    42         } catch (ClassNotFoundException e) {
    43             e.printStackTrace();
    44         }
    45     }
    46 }
    View Code

    ------------------------------------------------------console------------------------------------------------------

    21685669
    调用了readResolve方法
    21685669

     static代码块实现单例模式

     1 public class MyObject {
     2     private static MyObject myObject = null;
     3     private MyObject(){}
     4     static {
     5         myObject = new MyObject();
     6     }
     7     public static MyObject getInstance(){
     8         return myObject;
     9     }
    10 
    11     public static void main(String[] args) {
    12         Runnable runnable = new Runnable() {
    13             @Override
    14             public void run() {
    15                 for (int i = 0; i < 5; i++) {
    16                     System.out.println(MyObject.getInstance().hashCode());
    17                 }
    18             }
    19         };
    20 
    21         Thread t1 = new Thread(runnable);
    22         Thread t2 = new Thread(runnable);
    23         Thread t3 = new Thread(runnable);
    24 
    25         t1.start();
    26         t2.start();
    27         t3.start();
    28     }
    29 }
    View Code

    ------------------------------------------------------console------------------------------------------------------

    161899259
    161899259
    161899259
    161899259
    161899259
    161899259
    161899259
    161899259
    161899259
    161899259
    161899259
    161899259
    161899259
    161899259
    161899259

    enum枚举数据类型实现单例模式

    枚举和静态代码块的特性相似,在使用枚举时,构造方法会被自动调用

     1 public class MyObject {
     2     private MyObject(){}
     3     public static MyObject getInstance(){
     4         return EnumMyObject.INSTANCE.getInstance();
     5     }
     6     enum EnumMyObject{
     7         INSTANCE;
     8         private MyObject myObject;
     9         private EnumMyObject(){
    10             myObject = new MyObject();
    11         }
    12 
    13         public MyObject getInstance(){
    14             return myObject;
    15         }
    16     }
    17 
    18     public static void main(String[] args) {
    19         Runnable runnable = new Runnable() {
    20             @Override
    21             public void run() {
    22                 System.out.println(MyObject.getInstance().hashCode());
    23             }
    24         };
    25         int i = 0;
    26         while (i<3) {
    27             Thread t1 = new Thread(runnable);
    28             Thread t2 = new Thread(runnable);
    29             t1.start();
    30             t2.start();
    31             i++;
    32         }
    33     }
    34 }
    View Code

    ------------------------------------------------------console------------------------------------------------------

    2075012821
    2075012821
    2075012821
    2075012821
    2075012821
    2075012821
  • 相关阅读:
    oracle 索引分区处于不可用状态怎么解决 规格严格
    去IOE 遇到Jdbc mysql sql_mode的坑[转载] 规格严格
    【java】高并发之限流 RateLimiter使用 规格严格
    信息泄露引发的资产失陷与检测分析 规格严格
    一种失陷设备识别与设备失陷度评估的方法、装置 规格严格
    加快ios的出包速度
    为游戏接入ios sdk的oc学习笔记
    缩小ios的包体
    python2排序
    Sentinel 控制台
  • 原文地址:https://www.cnblogs.com/qf123/p/9767687.html
Copyright © 2011-2022 走看看