为什么需要单例模式
1.在日常web系统开发中,如果使用spring框架作为ioc容器,默认情况下所有的bean都是以单例模式构建的,如我们写的service,dao,controller这样的类,其实一般都是与线程无关的,也就是无状态的,我们没有必要在每一次用户请求时都去新生成一个controller,一个service。。。spring为全局生成一个唯一实例,所有请求都复用就可以了,节约内存。
2.如果某各类本身的实例化过程需要涉及很多的计算,很多的资源初始化开销,我们肯定要让其在全局只初始化一次,节约性能。
3.当我们的代码是有状态的,涉及到并发时,我们一定会加锁进行并发访问控制来保证线程安全性。
public class ThreadSafeTest {
private static int VARIBLE = 1;
public synchronized void add(){
VARIBLE += 1;
}
}
以上代码用synchronized来保证了VARIBLE增加的安全性,但是如果ThreadSafeTest类在系统中存在大于一个的实例,VARIBLE变量是类静态属性,而synchronized锁住的是实例对象本身,这个add方法将不再安全,以上代码要变成线程安全有几种改法:1.VARIBLE设置为原子变量,2.add方法的锁改为类锁,3.把ThreadSafeTest类改为单例。
n种实现方法
1.最基本的非线程安全的懒汉式
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static Singleton getInstance() {
if (instance == null) { //这里先判断后执行非原子操作,线程不安全
instance = new Singleton();
}
return instance;
}
}
2.线程安全的懒汉式
public class Singleton {
private static Singleton instance;
private Singleton (){}
//synchronized控制了线程安全性,但是直接锁住整个方法,性能将会严重下降
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
3.饿汉式,线程安全
public class Singleton {
//类装载时进行实例化,如果实例化过程特别耗费资源,将会导致启动缓慢
private static Singleton instance = new Singleton();
private Singleton (){}
public static Singleton getInstance() {
return instance;
}
}
4.饿汉式变种
public class Singleton {
private static Singleton instance;
{ //没啥好说的,和3一毛一样
instance = new Singleton();
}
private Singleton (){}
public static Singleton getInstance() {
return instance;
}
}
5.静态内部类实现
public class Singleton {
//static修饰该类,为类级别内部类,调用时才进行装在,初始化
private static class SingletonHolder {
//jvm保证只会初始化一次INSTANCE属性
private static final Singleton INSTANCE = new Singleton();
}
private Singleton (){}
public static final Singleton getInstance() {
//调用SingletonHolder.INSTANCE时,jvm才会装载初始化SingletonHolder类,保证了线程安全。
return SingletonHolder.INSTANCE;
}
}
6.双重锁检测
public class Singleton {
//volatile关键字保证singleton被修改后内存可见性
private volatile static Singleton singleton;
private Singleton (){}
//不对方法进行加锁
public static Singleton getSingleton() {
//先判断singleton是否为空,volatile关键字保证为空可见性,只有前几次请求会走到下面的分支
if (singleton == null) {
//对类进行加锁,并准备对singleton进行初始化
synchronized (Singleton.class) {
//获取到锁后需要再次判断singleton是否已经被实例化过了,因为在自己排队获得锁的过程中很有可能别的线程已经得到锁并实例化过了
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
7.枚举实现(effectiv java中推荐的高级方式,牛逼闪闪)
public enum Singleton {
INSTANCE;
//以下是Singleton类本身具有的各个方法具体逻辑
public void whateverMethod1() {
}
// ...其他的更多的自身方法
}
这种方式确实叼,脑洞大开啊。。。。它不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象,而且实现起来非常非常简单,但是很多人都觉得枚举里面有太多的业务逻辑很奇怪~~~确实。。
反序列化和不同的类加载器可能破坏单例模式的解决方式
1.反序列化。
public class Singleton implements java.io.Serializable {
//这里只是以饿汉式举例,其他任何方式都可以
public static Singleton INSTANCE = new Singleton();
protected Singleton() {
}
//readResolve返回的对象替换反序列化创建的实例;
private Object readResolve() {
return INSTANCE;
}
}
2.classloader。
stackverflow等地方给出的答案,但是我没能理解怎么用。。。。。:
private static Class getClass(String clazz) throws ClassNotFoundException {
ClassLoader loader = Thread.currentThread().getContextClassLoader();
if(loader == null)
loader = YourSingleton.class.getClassLoader();
return (loader.loadClass(clazz));
}
}
3.clone
单例类,不应该实现clone方法,只要不实现,就没办法clone的
网友评论