结合 Android 看看单例模式怎么写

BriNUQA 7年前
   <h2><strong>定义及使用场景</strong></h2>    <p><strong>定义</strong></p>    <p>单例模式,就是在整个系统中 某一个类的实例只有一个,并且自行实例化向整个系统提供 ;简单来说,就是某个类被实例化的方式是唯一的;同时他它必须向系统自动提供这个实例。</p>    <p><strong>使用场景</strong></p>    <ul>     <li>可以避免产生多个对象消耗过多的资源,如I/O访问等。</li>     <li>某些类的对象就是应该只有,多个对象将导致逻辑错误或混乱。</li>    </ul>    <h2><strong>常见的实现方式</strong></h2>    <p>下面是单例模式常见的两种实现方式 <strong>饿汉模式和 双重锁模式</strong></p>    <ul>     <li>饿汉模式</li>    </ul>    <pre>  <code class="language-java">public class HungrySingleton {        private static HungrySingleton mInstance = new HungrySingleton();        private HungrySingleton() {        }        public static HungrySingleton getInstance() {          return mInstance;      }  }</code></pre>    <p>不得不说, <strong>饿汉模式</strong> 这个名字起得的确很巧,这种方式,不管你用不用得着这个实例,先给你创建(new)出来,生怕将来创建没机会似得,完全就是今朝有酒今朝醉的节奏。</p>    <p>与上面对应的还有一种就是 <strong>懒汉模式</strong> ,就是在用的时候才在getInstance 方法中完成实例的创建(new),真是“懒”,同时给这个方法添加synchronized 关键字,可以确保在多线程情况下单例依旧唯一,但是懒汉模式每次调用getInstance 方法时由于synchronized 的存在,需要进行同步,造成不必要的资源开销。因此便有了下面 <strong>双重锁模式</strong> 的实现方式。</p>    <ul>     <li>双重锁模式(DCL 实现)</li>    </ul>    <pre>  <code class="language-java">public class LazySingleton {      private static LazySingleton mInstance = null;        private LazySingleton() {        }        public static LazySingleton getInstance() {          if (mInstance == null) {              synchronized (LazySingleton.class) {                  if (mInstance == null) {                      mInstance = new LazySingleton();                  }              }          }            return mInstance;      }  }</code></pre>    <p>这样既避免了饿汉模式的缺点,又解决了懒汉模式的不足;确保单例只在第一次真正需要的时候创建。</p>    <h2><strong>Android 中的使用</strong></h2>    <p>在日常的Android开发中,也可以见到单例模式的身影。</p>    <ul>     <li> <p>Glide</p> <p>使用Glide加载图片非常方便,大家应该不陌生,可以看一下它的源码中单例模式的实现方式。</p> </li>    </ul>    <pre>  <code class="language-java">Glide.with(this).load(url).into(imageView);      //Glide.with()      public static RequestManager with(FragmentActivity activity) {          RequestManagerRetriever retriever = RequestManagerRetriever.get();          return retriever.get(activity);      }     //RequestManagerRetriever.get()       /** The singleton instance of RequestManagerRetriever. */      private static final RequestManagerRetriever INSTANCE = new RequestManagerRetriever();       /**       * Retrieves and returns the RequestManagerRetriever singleton.       */      public static RequestManagerRetriever get() {          return INSTANCE;      }</code></pre>    <p>可以看到,当我们写下Glide.with(..) 这行代码时,就完成了RequestManagerRetriever 这个类的实例化,这个类的单例模式是使用 <strong>饿汉模式</strong> 实现的。</p>    <ul>     <li>EventBus</li>    </ul>    <pre>  <code class="language-java">public static EventBus getDefault() {          if (defaultInstance == null) {              synchronized (EventBus.class) {                  if (defaultInstance == null) {                      defaultInstance = new EventBus();                  }              }          }          return defaultInstance;      };</code></pre>    <p>很明显,EventBus的单例模式使用双重锁模式实现的。</p>    <ul>     <li>InputMethodManager <pre>  <code class="language-java">static InputMethodManager sInstance    public static InputMethodManager getInstance() {        synchronized (InputMethodManager.class) {            if (sInstance == null) {                IBinder b = ServiceManager.getService(Context.INPUT_METHOD_SERVICE);                IInputMethodManager service = IInputMethodManager.Stub.asInterface(b);                sInstance = new InputMethodManager(service, Looper.getMainLooper());            }            return sInstance;        }    }</code></pre> InputMethodManager 的单例模式是使用懒汉模式实现。</li>    </ul>    <p>可以看到,关于单例模式的实现方式,面对不同的场景,我们可以做出不同的选择</p>    <ul>     <li> <p>Glide的单例模式虽然是使用饿汉模式实现,但理论上来说并不会造成内存资源的浪费,因为当我们通过gradle的配置引入Glide的库时,就是为了加载图片,必然会使用Glide.with进行相关的操作。同时RequestManagerRetriever 这个类应该是一个网络请求的管理类(Glide源码没有研究过,这里只是猜测),这样的一个类必然需要使用单列模式,试想如果存在多个管理类的实例,那么谈何管理,那么的多Request到底听哪个manger 的,这就是前面提到必须使用单列模式的情景。</p> </li>     <li> <p>EventBus 作为事件总线的更要使用单例模式了,如果说EventBus的实例不是单例模式,那么他就无法实现它的功能了。对于EventBus不了解的同学,可以看看 <a href="/misc/goto?guid=4959746813921526326" rel="nofollow,noindex">EventBus 3.0 相见恨晚</a> ,EventBus真的很强大。</p> </li>     <li> <p>InputMethodManager 使用懒汉模式实现单例也是无可厚非的,毕竟谁会去频繁的获取那么多他的实例呢;同时作为一个系统的输入法管理器,他也必须是唯一的,因此这个类也需要单例模式来实现它唯一的实例供外部使用。</p> </li>    </ul>    <p>由上可见,关于单例模式的实现,没有说哪一种方式最好,只有最合适的实现方式;实际开发中,单例模式应该怎么写,还需要根据业务场景做最合适的选择,无论是饿汉懒汉实用才是好汉。个人感觉,饿汉模式是一种简单又方便的实现方式, 一个类既然已经写成了单例模式,必然是要使用的呀,谁会去创建一个饿汉模式的单例,又不去使用这个单例呢?</p>    <p>之前在使用Volley的时候,就是使用饿汉模式创建整个应用的RequestQueue单例,所有需要网络请求的地方,把request添加到RequestQueue单例中即可。</p>    <pre>  <code class="language-java">public class MyApplication extends Application{      // 建立请求队列      public static RequestQueue queue;        @Override      public void onCreate() {          super.onCreate();          queue = Volley.newRequestQueue(getApplicationContext());      }        public static RequestQueue getHttpQueue() {          return queue;      }  }</code></pre>    <p>在应用Application的onCreate方法中创建了属于整个应用的queue,之后每一次网络请求时,只需要queue.add(Request)即可,这里使用单例模式,可以有效的避免在多个地方创建RequestQueue 的实例,浪费系统资源。</p>    <h2><strong>更多</strong></h2>    <p>在某些复杂的场景中,上述的两种方式都或多或少的存在一些缺陷。因此便有了以下两种单例模式的实现方式。</p>    <p><strong>静态内部类</strong></p>    <pre>  <code class="language-java">public class StaticSingleton {      private StaticSingleton(){        }        public static StaticSingleton getInstance(){          return SingletonHolder.mInstance;      }        /**       * 静态内部类       */      private static class SingletonHolder{          private static final StaticSingleton mInstance=new StaticSingleton();      }  }</code></pre>    <p>可以说,这是最安全的实现方式了,无论怎样,这样产生的单例必然是单例。</p>    <p><strong>枚举单例</strong></p>    <pre>  <code class="language-java">public enum  EnumSingleton {      INSTANCE;  }</code></pre>    <p>定义一个枚举元素,而他就是单例;可以说,这是实现单例最简单最实惠的方式;可以有效的避免单例在反序列化的过程中被创建,从而让单例变得不唯一。但是,Google官方是不建议在Android开发中使用枚举的,所以使用具体使用哪种方式实现单例模式,仁者见仁智者见智了。</p>    <p>单例模式是设计模式中最简单的一种,因为他最容易理解;但通过上述分析可以看到,简单不意味着随意,针对不同的业务场景,需要我们仔细斟酌单例模式的实现方式</p>    <p>好了,关于单例模式就是这些了。</p>    <p> </p>    <p>来自:https://juejin.im/post/58e5053a2f301e0062294ba9</p>    <p> </p>