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
  • 相关阅读:
    动态规划——Best Time to Buy and Sell Stock IV
    动态规划——Split Array Largest Sum
    动态规划——Burst Ballons
    动态规划——Best Time to Buy and Sell Stock III
    动态规划——Edit Distance
    动态规划——Longest Valid Parentheses
    动态规划——Valid Permutations for DI Sequence
    构建之法阅读笔记05
    构建之法阅读笔记04
    构建之法阅读笔记03
  • 原文地址:https://www.cnblogs.com/qf123/p/9767687.html
Copyright © 2011-2022 走看看