zoukankan      html  css  js  c++  java
  • 利用单例模式解决全局访问问题

          在面向对象编程中,我们无时无刻都可能在产生对象,因为我们的代码需要对象,但值得注意的是,我们有时候也有可能是在无谓的产生对象,更加可怕的是,这些累赘的对象会造成难以排查的BUG,尤其是在多线程编程中。

          所以,合理的产生对象也是一个学问。

         有些对象我们只需要一个,像是线程池,缓冲等,这类对象只能有一个实例,一旦产生多个实例就会出现问题。所以,我们必须找到一种方法来确保我们的代码中只有一个实例。

         首先我们想到的第一个解决方法就是声明一个全局变量,然后将对象赋值给该全局变量,但是这意味着我们必须在程序一开始的时候就要创建好该对象,但我们应该是在需要的时候才创建对象,而且如果这个对象本身非常耗费资源的话,这就是一种浪费。

         提到创建对象,这里有一点必须要提到:

    static BluetoothSocket socket = null;

         因为java鼓励人们在声明对象的时候赋予对象初始值以免出现问题,但对象这时候并没有被创建!真正的创建对象应该是通过new和反射机制来产生。
         使用全局变量的时候,我们必须确保整个程序中只有一份,因为使用全局变量的目的就是为了共享资源,而共享资源必须到达两个条件:不变和共享。不变,指的是这个资源在整个程序中必须只有一份,而共享是指它的变化必须对共享它的所有对象是可见的,否则,所有对象都有自己的一份,何来共享呢?

         因此,我们必须确保一件事:一个类只有一个实例,其他类无法自行产生它的实例。

         使用单例模式就能确保这点。

         单例模式的意图是:确保一个类只有一个实例,并且提供一个全局访问点。它到底是怎么做到这点的呢?

         我们知道,一般类是通过构造器来产生的,而构造器一般都是public,也就是可访问的,但如果我们将构造器设为private,那么也就能阻止其他类产生该对象的实例,但问题也就来了:私有的构造器只有该类才能访问,但是我们无法产生该类的实例,又如何能得到该类的实例呢?

         这时我们就需要在该类中声明一个自己本身的静态实例,然后通过静态方法返回。我们知道,静态实例在程序中只有一份,所以这也就能确保该实例在程序中只有一份,并且因为构造器是私有的,其他类也就无法产生实例。

          下面是使用单例模式的经典用法:

    private static BluetoothSocket bluetoothSocket;
    
    private BluetoothSocket(){}
    
    public static BluetoothSicket getBluetoothSocket(){
         if(blueSocket == null){
               bluetoothSocket = new BluetoothSokcet();
         }
         return bluetoothSocket;
    }

          因为getBluetoothSocket()是一个静态方法,因为我们不需要通过创建实例来调用该方法,而且这里还使用了创建对象时经常使用到的方法:延迟实例化,又叫滞后初始化,利用这点,我们可以先判断程序中是否已经创建了实例,如果没有才创建实例,这样就能确保程序中永远只有一份实例了。使用延迟实例化的最大好处就是我们可以在需要的时候才创建对象。

          单例模式是为了消除程序员无谓的创建全局变量,尤其是新手,像是我一开始编程的时候,就喜欢创建全局变量,因为不用考虑命名空间这些东西真心舒服,因为那时候我还在学习C和C++,即使java表面上没有命名空间的说法,但其实内部的机制也是基于命名空间,只不过是用包导入机制确保我们不需要为这个问题烦恼而已。

          无论是哪种程序语言,都不鼓励使用全局变量,那意味着编程结构不好。

          如果是一般的编码,单例模式好像有点大材小用了,但如果是多线程编程这种让人纠结的东西,单例模式在一定程度上给予了线程安全。

          同步这个话题是我们学习java跳不过的,而且也是一个非常重要的难点,要想写出一个线程安全的代码,是需要我们不断努力的。

          就算是单例模式,我们也无法确保两个线程不会同时创建实例,最糟糕的的情况就是多个线程同时调用静态方法同时产生实例,这在多线程中是非常常见的现象。因此,我们可以在静态方法前添加synchronized关键字来迫使每个线程在进入这个方法前,要先等候别的线程离开该方法,以确保不会有多个线程同时调用该方法。

          但问题还是来了:只有第一次执行该方法的时候我们才真正需要同步,因为静态方法在第一次被调用后就能确保不会再产生实例了。synchronized关键字这时就是个累赘了,它只在第一次时有用,以后每次调用该方法的时候都要为此付出无谓的线程消耗。

          所以,同步一个方法并不是一个好的做法,它会让我们的程序存在一个无限等待的后台,导致我们的程序效率非常低下。

          我们只要稍微改动一下代码就行:

    private static BluetoothSocket bluetoothSocket = new BluetoothSocket();
    
    private BluetoothSocket(){}
    
    public static BluetoothSocket getBluetooth(){
        return bluetoothSocket;
    }

          为什么这段代码就能保证线程安全呢?明明它就只是废弃了延迟实例化而已。因为这样JVM能够确保在任何线程调用静态方法前就已经创建好该实例了。

          我们还可以利用线程的其他知识来完成这个任务:

    private volatile static BluetoothSocket bluetoothSocket;
    
    private BluetoothSocket(){}
    
    public static BluetoothSicket getBluetoothSocket(){
         if(blueSocket == null){
               synchronized(BluetoothSocket.class){
                   if(blueSocket == null){
                      bluetoothSocket = new BluetoothSokcet();
                   }
               }
         }
         return bluetoothSocket;
    }

           这就是多重检查加锁的做法。我们首先检查实例是否已经创建,如果尚未创建,才进行同步。
           volatile关键字能够确保实例被创建时,所有线程都能正确处理该变量。什么叫正确的处理,就是我们利用volatile同步了该实例,在该实例发生变化的时候,其他共享线程都能知道从而进行相应的处理。多重检查加锁需要利用到对象锁,每个java对象身上都有一个锁,当一个线程获取到该锁后,就会防止其他线程试图访问该对象,除非该线程释放了这个对象的锁。volatile一般都是要和对象锁一起搭配才能发挥真正的作用,一个控制住了变化,另一个则是控制住了访问。

           单例模式之所以能够确保只有一个实例,也是建立在只有一个类加载器的前提下,如果有两个以上的类加载器,就有可能产生多个单例并存的奇怪现象。所以,这时我们必须指定类加载器。但一般的程序都只有一个类加载器,但涉及到多线程的话,这个可就不知道了。

           总结一下,单例模式的最大作用就是将一个对象的职责全部集中在一个实例上,这样就能避免无谓的浪费,但单例模式也并不是一个可以随便乱用,它就和全局变量的使用是一样的,将所有职责交给一个实例,这对实例本身就是一个不公平,尤其是职责非常重大的时候。


          

          
        

  • 相关阅读:
    2015531 网络攻防 Exp1 PC平台逆向破解(5)M
    2017-2018-1 20155331 嵌入式C语言
    20155330 《网络对抗》 Exp9 web安全基础实践
    20155330 《网络对抗》 Exp8 Web基础
    20155330 《网络对抗》 Exp7 网络欺诈防范
    20155330 《网络对抗》 Exp6 信息搜集与漏洞扫描
    20155330 《网络对抗》 Exp5 MSF基础应用
    20155330 《网络攻防》 Exp4 恶意代码分析
    20155330 《网络攻防》 Exp3 免杀原理与实践
    20155330 《网络对抗》 Exp2 后门原理与实践
  • 原文地址:https://www.cnblogs.com/wenjiang/p/3247745.html
Copyright © 2011-2022 走看看