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