(1)单例模式概念
就是在整个应用中保证只有一个类的实例存在,就像是java web中的application,也就是一个全局变量。
(2)最简单实现:可能浪费资源
1.思路
能够想到最简单的实现就是将构造函数写成private的,从而保证不能实例化此类,然后在类中提供一个静态的实例并能返回给使用者。
2.代码
public class Singleton
{
private static Singleton instance = new Singleton();
private Singleton()
{}
public static Singleton getInstance()
{
return instance;
}
}
3.问题
无论这个类是否被使用,都会创建一个instance对象,如果这个创建过程很耗时、耗资源,比如创建10000个数据库连接,并且这个对象并不被使用,那么就造成了极大的浪费。
(3)懒加载实现
1.思路
将静态变量instance初始化为null,那么在getInstance的时候先判断instance是否为null,为null的时候再生成一个实例
2.代码
public class Singleton
{
private static Singleton instance = null;
private Singleton()
{}
public static Singleton getInstance()
{
if(instance == null)
{
instance = new Singleton();
}
return instance;
}
}
3.问题
在单线程情况下,该程序不会有什么问题。但是如果是多线程,那么可能就会有问题。
假设线程A调用getInstance方法,因为是第一次调用,A发现instance是null的,所以通过了if判断,在将要创建instance的时候,cpu发生时间片切换,线程B得到执行,B也调用getInstance,因为A实际上并没有生成实例,那么B也通过了if判断,然后生成了instance。B创建完成后,A又得到执行,由于A已经通过了if判断,所以A也会创建一个instance。这样线程A、B都会有一个各自的Singleton
(4)同步问题
1.思路
因为getInstance方法出现了同步问题,所以直接加锁就可以了
2.代码
public class Singleton
{
private static Singleton instance = null;
private Singleton()
{}
public synchronized static Singleton getInstance()
{
if(instance == null)
{
instance = new Singleton();
}
return instance;
}
}
3.问题
synchronized修饰的同步代码块可能比一般的代码要慢上几倍,所以如果存在多次getInstance()调用,那么性能问题就必须要考虑了。
(5)同步优化问题
1.思路
现在来分析一下是整个方法需要加锁,还是其中某些加锁就可以了。为什么要加锁呢?就是因为判断null的操作盒生成对象的操作分离了,所以如果这两个操作能够实现原子性,那么单例也就能够保证了。
2.代码
public class Singleton
{
private static Singleton instance = null;
private Singleton()
{}
public static Singleton getInstance()
{
synchronized(Singleton.class)
{
if(instance == null)
{
instance = new Singleton();
}
}
return instance;
}
}
3.问题
synchronized写在if上虽然保证了单例,但是同样不能解决同步的性能问题,因为每次调用getInstance,还是会进入同步代码块判断。所以,如果事先先判断是否为null,再去进行同步操作,就能提高性能。
4.代码
public class Singleton
{
private static Singleton instance = null;
private Singleton()
{}
public static Singleton getInstance()
{
if(instance == null)
{
synchronized(Singleton.class)
{
if(instance == null)
{
instance = new Singleton();
}
}
}
return instance;
}
}
5.问题
因为每个线程都会有自己对变量的副本,每个线程对变量的修改可能不会及时通知到其他线程,造成其他线程读到错误的数据。
(6)最终方案1
1.思路
一般的线程都会对共享变量保存一个副本(为了提高效率),但是如果一个线程对该变量改变了,但是另一个线程使用的还是自己的副本,那么就会出错。使用volatile关键字,告诉jvm,这个变量是随时可能修改的,所以每个线程就不要保存副本了,直接取主存的
2.代码
public class Singleton
{
private volatile static Singleton instance = null;
private Singleton()
{}
public static Singleton getInstance()
{
if(instance == null)
{
synchronized(Singleton.class)
{
if(instance == null)
{
instance = new Singleton();
}
}
}
return instance;
}
}
(7)最终方案2
1.思路
使用静态内部类,因为是静态的,所以只会初始化一次,那么在这里直接返回内部静态类中维护的单例成员就可以了
2.代码
public class Singleton
{
private static class SingletonIns
{
private static Singleton instance = new Singleton();
}
private Singleton()
{}
public Singleton getInstance()
{
return SingletonIns.instance;
}
}