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

xiaoxiao2021-02-28  93

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

        对大数据、分布式、高并发等知识的学习必须要有多线程的基础。这里讨论一下如何在多线程的情况下设计单例模式。在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

    由此可见,使用static代码块也可以实现单例模式,因为静态代码块在使用类的时候已经执行了。

转载请注明原文地址: https://www.6miu.com/read-24543.html

最新回复(0)