zoukankan      html  css  js  c++  java
  • 多线程下的单例实现方案

    什么是单例模式?

    在文章开始之前我们还是有必要介绍一下什么是单例模式。单例模式是为确保一个类只有一个实例,并为整个系统提供一个全局访问点的一种模式方法。

    从概念中体现出了单例的一些特点:

    (1)、在任何情况下,单例类永远只有一个实例存在

    (2)、单例需要有能力为整个系统提供这一唯一实例  

    为了便于读者更好的理解这些概念,下面给出这么一段内容叙述:

    在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例。这些应用都或多或少具有资源管理器的功能。每台计算机可以有若干个打印机,但只能有一个Printer Spooler,以避免两个打印作业同时输出到打印机中。每台计算机可以有若干通信端口,系统应当集中管理这些通信端口,以避免一个通信端口同时被两个请求同时调用。总之,选择单例模式就是为了避免不一致状态,避免政出多头。

    正是由于这个特点,单例对象通常作为程序中的存放配置信息的载体,因为它能保证其他对象读到一致的信息。例如在某个服务器程序中,该服务器的配置信息可能存放在数据库或文件中,这些配置数据由某个单例对象统一读取,服务进程中的其他对象如果要获取这些配置信息,只需访问该单例对象即可。这种方式极大地简化了在复杂环境 下,尤其是多线程环境下的配置管理,但是随着应用场景的不同,也可能带来一些同步问题。

    1、饿汉式单例

    饿汉式单例是指在方法调用前,实例就已经创建好了。下面是实现代码:

    package org.mlinge.s01;  
      
    public class MySingleton {  
          
        private static MySingleton instance = new MySingleton();  
          
        private MySingleton(){}  
          
        public static MySingleton getInstance() {  
            return instance;  
        }  
          
    }  

    以上是单例的饿汉式实现,我们来看看饿汉式在多线程下的执行情况,给出一段多线程的执行代码:

    package org.mlinge.s01;  
      
    public class MyThread extends Thread{  
          
        @Override  
        public void run() {   
            System.out.println(MySingleton.getInstance().hashCode());  
        }  
          
        public static void main(String[] args) {   
              
            MyThread[] mts = new MyThread[10];  
            for(int i = 0 ; i < mts.length ; i++){  
                mts[i] = new MyThread();  
            }  
              
            for (int j = 0; j < mts.length; j++) {  
                mts[j].start();  
            }  
        }  
    }  

    代码运行结果: 从运行结果可以看出实例变量额hashCode值一致,这说明对象是同一个,饿汉式单例实现了。

    1718900954  
    1718900954  
    1718900954  
    1718900954  
    1718900954  
    1718900954  
    1718900954  
    1718900954  
    1718900954  
    1718900954  

    2、懒汉式单例

    懒汉式单例是指在方法调用获取实例时才创建实例,因为相对饿汉式显得“不急迫”,所以被叫做“懒汉模式”。下面是实现代码:

    package org.mlinge.s02;  
      
    public class MySingleton {  
          
        private static MySingleton instance = null;  
          
        private MySingleton(){}  
          
        public static MySingleton getInstance() {  
            if(instance == null){//懒汉式  
                instance = new MySingleton();  
            }  
            return instance;  
        }  
    }  

    这里实现了懒汉式的单例,但是熟悉多线程并发编程的朋友应该可以看出,在多线程并发下这样的实现是无法保证实例实例唯一的,甚至可以说这样的失效是完全错误的,下面我们就来看一下多线程并发下的执行情况,这里为了看到效果,我们对上面的代码做一小点修改:

    package org.mlinge.s02;  
      
    public class MySingleton {  
          
        private static MySingleton instance = null;  
          
        private MySingleton(){}  
          
        public static MySingleton getInstance() {  
            try {   
                if(instance != null){//懒汉式   
                      
                }else{  
                    //创建实例之前可能会有一些准备性的耗时工作   
                    Thread.sleep(300);  
                    instance = new MySingleton();  
                }  
            } catch (InterruptedException e) {   
                e.printStackTrace();  
            }  
            return instance;  
        }  
    }  

    这里假设在创建实例前有一些准备性的耗时工作要处理,多线程调用:

    package org.mlinge.s02;  
      
    public class MyThread extends Thread{  
          
        @Override  
        public void run() {   
            System.out.println(MySingleton.getInstance().hashCode());  
        }  
          
        public static void main(String[] args) {   
              
            MyThread[] mts = new MyThread[10];  
            for(int i = 0 ; i < mts.length ; i++){  
                mts[i] = new MyThread();  
            }  
              
            for (int j = 0; j < mts.length; j++) {  
                mts[j].start();  
            }  
        }  
    } 

    代码运行结果如下:

    1210420568  
    1210420568  
    1935123450  
    1718900954  
    1481297610  
    1863264879  
    369539795  
    1210420568  
    1210420568  
    602269801  

    从这里执行结果可以看出,单例的线程安全性并没有得到保证,那要怎么解决呢?

    3、线程安全的懒汉式单例

    要保证线程安全,我们就得需要使用同步锁机制,下面就来看看我们如何一步步的解决 存在线程安全问题的懒汉式单例(错误的单例)。

    (1)、 方法中声明synchronized关键字

    出现非线程安全问题,是由于多个线程可以同时进入getInstance()方法,那么只需要对该方法进行synchronized的锁同步即可:

    package org.mlinge.s03;  
      
    public class MySingleton {  
          
        private static MySingleton instance = null;  
          
        private MySingleton(){}  
          
        public synchronized static MySingleton getInstance() {  
            try {   
                if(instance != null){//懒汉式   
                      
                }else{  
                    //创建实例之前可能会有一些准备性的耗时工作   
                    Thread.sleep(300);  
                    instance = new MySingleton();  
                }  
            } catch (InterruptedException e) {   
                e.printStackTrace();  
            }  
            return instance;  
        }  
    }  

    此时任然使用前面验证多线程下执行情况的MyThread类来进行验证,将其放入到org.mlinge.s03包下运行,执行结果如下:

    1689058373  
    1689058373  
    1689058373  
    1689058373  
    1689058373  
    1689058373  
    1689058373  
    1689058373  
    1689058373  
    1689058373  

    从执行结果上来看,问题已经解决了,但是这种实现方式的运行效率会很低。同步方法效率低,那我们考虑使用同步代码块来实现:

    (2)、 同步代码块实现

    package org.mlinge.s03;  
      
    public class MySingleton {  
          
        private static MySingleton instance = null;  
          
        private MySingleton(){}  
          
        //public synchronized static MySingleton getInstance() {  
        public static MySingleton getInstance() {  
            try {   
                synchronized (MySingleton.class) {  
                    if(instance != null){//懒汉式   
                          
                    }else{  
                        //创建实例之前可能会有一些准备性的耗时工作   
                        Thread.sleep(300);  
                        instance = new MySingleton();  
                    }  
                }  
            } catch (InterruptedException e) {   
                e.printStackTrace();  
            }  
            return instance;  
        }  
    }  

    这里的实现能够保证多线程并发下的线程安全性,但是这样的实现将全部的代码都被锁上了,同样的效率很低下。

    (3)、 针对某些重要的代码来进行单独的同步(可能非线程安全)

    针对某些重要的代码进行单独的同步,而不是全部进行同步,可以极大的提高执行效率,我们来看一下:

    package org.mlinge.s04;  
      
    public class MySingleton {  
          
        private static MySingleton instance = null;  
          
        private MySingleton(){}  
           
        public static MySingleton getInstance() {  
            try {    
                if(instance != null){//懒汉式   
                      
                }else{  
                    //创建实例之前可能会有一些准备性的耗时工作   
                    Thread.sleep(300);  
                    synchronized (MySingleton.class) {  
                        instance = new MySingleton();  
                    }  
                }   
            } catch (InterruptedException e) {   
                e.printStackTrace();  
            }  
            return instance;  
        }  
    } 

    此时同样使用前面验证多线程下执行情况的MyThread类来进行验证,将其放入到org.mlinge.s04包下运行,执行结果如下:

    1481297610  
    397630378  
    1863264879  
    1210420568  
    1935123450  
    369539795  
    590202901  
    1718900954  
    1689058373  
    602269801 

    从运行结果来看,这样的方法进行代码块同步,代码的运行效率是能够得到提升,但是却没能保住线程的安全性。

  • 相关阅读:
    Hystrix高可用系统容错框架,资源隔离,熔断,限流
    Leecode no.25 K 个一组翻转链表
    no.1 Web浏览器
    源码解析-JavaNIO之Buffer,Channel
    Leecode no.24 两两交换链表中的节点
    Kafka RocketMQ 是推还是拉?
    Leecode no.23 合并K个升序链表
    图解计算机底层IO过程及JavaNIO
    Leecode no.21 合并两个有序链表
    AcWing每日一题--摘花生
  • 原文地址:https://www.cnblogs.com/1234AAA/p/8510647.html
Copyright © 2011-2022 走看看