5种单例模式的介绍与比较

来源:互联网 发布:中国云计算方案大会 编辑:程序博客网 时间:2024/06/06 12:37

今天要介绍一下java(和安卓)的5中单例模式及相互的比较。
下面我会通过代码进行详细的介绍和比较,代码中会带有注释,如果觉得在这里看比较乱,可以直接复制到自己创建的.java文件中进行阅读(会有错误提示,如果强迫症,将部分相同的函数用”//”注释就好了),代码如下:

package com.tcl.adclient.utils.others;import java.io.ObjectStreamException;import android.content.Context;import android.media.MediaPlayer;public class MMediaPlayer {    /**    * 用volatile修饰的变量,线程在每次使用变量的时候,都会读取变量修改后的最的值。    * volatile很容易被误用,用来进行原子性操作。对于volatile修饰的变量,    * jvm虚拟机只是保证从主内存加载到线程工作内存的值是最新的。    * 您只能在有限的一些情形下使用 volatile 变量替代锁。    * 要使 volatile 变量提供理想的线程安全,必须同时满足下面两个条件:    * 1.对变量的写操作不依赖于当前值。    * 2.该变量没有包含在具有其他变量的不变式中。    * 只有在状态真正独立于程序内其他内容时才能使用 volatile ——     * 这条规则能够避免这些模式扩展到不安全的用例。    * 使用 volatile 变量次要原因是其性能:某些情况下,volatile 变量同步机制的性能要优于锁    **/    private volatile static MediaPlayer mp;    private static MediaPlayer mps = new MediaPlayer();    private static Object lock=new Object();    private MMediaPlayer() {    }    /**     * 1.1懒汉,线程不安全     * @return     */    public static MediaPlayer getInstance() {        if (mp == null) {            mp = new MediaPlayer();        }        return mp;//    }    /**     * // 1.2.懒汉,线程安全,这种写法是对getInstance()加锁,绝大情况下都不需要同步,效率低     * @param context     * @return     */    public static synchronized MediaPlayer getInstance() {        if (mp == null) {            mp = new MediaPlayer();        }        return mp;    }    /**     * 2.饿汉,这种方式基于classloder机制避免了多线程的同步问题,不过,instance在类装载时就实例化,虽然导致类装载的原因有很多种,在单例模式中大多数都是调用getInstance方法,     *  但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化instance显然没有达到lazy loading的效果。     * @return     */    public static MediaPlayer getInstance() {        return mps;//    }    /**     * 3.1.自己根据对synchronized的理解写出来的,这里的context可以保证此对象在当前运行环境中的同步单例,在安卓中效率比1.2高。     * 但第一次执行时在当前环境(调用此函数的Activity环境)加锁,且多线程中不能保证单例!要保证单例可以借鉴3.2的双重锁校验。     * 适用于安卓在主线程中使用。。粒度过大,不建议!     * @param context     * @return     */    public static MediaPlayer getInstance(Context context) {        if (mp == null) {            synchronized (context) {                mp = new MediaPlayer();            }        }        return mp;    }    /**     * 3.2.双重校验锁 单例模式 不建议使用,类似于3.3和3.1,静态方法中synchronized但多了在同步块内部判断,效率比3.1高,比3.3低,     * 这个方法表面上看起来很完美,你只需要付出一次同步块的开销,但它依然有问题。除非你声明_instance变量时使用了volatile关键字。     * 没有volatile修饰符,可能出现Java中的另一个线程看到个初始化了一半的_instance的情况,     * 但使用了volatile变量后,就能保证先行发生关系(happens-before relationship)。     * 对于volatile变量_instance,所有的写(write)都将先行发生于读(read),     * 在Java 5之前不是这样,所以在这之前使用双重检查锁有问题。现在,有了先行发生的保障(happens-before guarantee),你可以安全地假设其会工作良好。     * 另外,这不是创建线程安全的单例模式的最好方法,你可以使用枚举实现单例模式,这种方法在实例创建时提供了内置的线程安全。另一种方法是使用静态持有者模式(static holder pattern)。     *      * 要实现真正的断面,你必须同步一个全局对象或者对类进行同步     * @return     */    public MediaPlayer getMyInstance() {        if (mp == null) {            synchronized (MMediaPlayer.this) {//如果在静态函数中此句会编译错误:Cannot use this in a static context,            //要改为MMediaPlayer.class                if (mp == null) {                    mp = new MediaPlayer();                }            }        }        return mp;    }    /**     * 3.3. 比3.2的做法要好一点,虽然都是双重校验锁。     * 3.2中的加锁是针对类定义的,一个类只能有一个类定义,而同步的一般原理是应该尽量减小同步的粒度以到达更好的性能。     * @return     */    public MediaPlayer getMyInstance() {        if (mp == null) {            synchronized (lock) {                if (mp == null) {                    mp = new MediaPlayer();                }            }        }        return mp;    }    /**     * 4.静态内部类。这种方式同样利用了classloder的机制来保证初始化instance时只有一个线程,     * 它跟第三种和第四种方式不同的是(很细微的差别):第三种和第四种方式是只要Singleton类被装载了,那么instance就会被实例化(没有达到lazy loading效果),     * 而这种方式是Singleton类被装载了,instance不一定被初始化。     * 因为SingletonHolder类没有被主动使用,只有显示通过调用getInstance方法时,才会显示装载SingletonHolder类,     * 从而实例化instance。想象一下,如果实例化instance很消耗资源,我想让他延迟加载,     * 另外一方面,我不希望在Singleton类加载时就实例化,因为我不能确保Singleton类还可能在其他的地方被主动使用从而被加载,     * 那么这个时候实例化instance显然是不合适的。这个时候,这种方式相比第三和第四种方式就显得很合理。     * @author mylab     */     @SuppressWarnings("unused")    private static class SingletonHolder {         private static final MediaPlayer INSTANCE = new MediaPlayer();         private SingletonHolder(){}         public static final MediaPlayer getInstance() {             return SingletonHolder.INSTANCE;         }    }     /**      * 5.枚举, 在安卓中不建议使用,占用资源最多,效率最低!      * 在JAVA中,这种方式是Effective Java作者Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象,可谓是很坚强的壁垒啊      * 个人认为1.5中才加入enum特性,不直观,在实际的JAVA开发中也很少见到有人使用。      * public enum Singleton {      *   INSTANCE;      *   public void whateverMethod() {      *     ...      *   }      * }      */     /**      * 将对象置为空,再此之前请确保该对象已停止播放并释放相关资源      */    public static void setNull() {        if (mp != null) {            mp = null;        }    }    /**     * 为了实现流转换复写Object函数,如果不涉及反序列化操作不用复写     * @return     * @throws ObjectStreamException     */    private Object readResolve() throws ObjectStreamException {        return mp;    }}

0 0