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

今天要介绍一下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;
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值