java实现单例模式
1.最简单的方式:
public class Singleton {
private static final Singleton singleton = new Singleton();
private Singleton(){
}
public Singleton getInstance(){
return singleton;
}
}
分析:
优点:简单明了,预防多线程问题
缺点:项目启动时就要初始化,如果初始化很复杂,很可能造成启动超时。如果实例一直没有被用到,则造成资源浪费。
改进方式如下:
2.懒加载:
调用getInstance方法的时候,再去初始化对象。
public class Singleton {
private static Singleton singleton;
private Singleton(){}
public Singleton getInstance(){
if(singleton == null){
singleton = new Singleton();
}
return singleton;
}
}
分析:
优点:思路简单,而且在需要单例对象的时候再去初始化,达到延迟加载的效果
缺点:多线程情况下容易出问题:两个线程同时走到if(singleton==null)的时候,就可能会导致初始化两次。
改进方式如下:
3.加锁:
在if(getInstance == null)这段的时候加上锁,这样就不会有两个线程同时初始化对象啦。
public class Singleton {
private static Singleton singleton;
private SingletonLock(){}
public synchronized Singleton getInstance(){
if(singleton == null){
singleton = new Singleton();
}
return singleton;
}
}
分析:
优点:懒加载有了,线程同步也有了,总算可以长舒一口气了。
缺点:每次调用时都会进行线程同步,如果我调用的频率很高,这也太浪费性能了。
改进方式如下:
3.双重检查锁:
先判断是否为空,如果如果为空,再加锁进行初始化。
public class Singleton {
private static Singleton singleton;
private Singleton(){}
public Singleton getInstance(){
if(singleton == null){
synchronized (Singleton.class){
if(singleton == null){
singleton = new Singleton();
}
}
}
return singleton;
}
}
分析:
优点:懒加载和同步依然有,而且在大频率调用的情况下,不会出现性能问题。这下总完美了吧!
缺点:有个极端情况,如果初始化的时候要做很多操作(如下),会不会出现线程A正在“标记1“时进行赋值,,线程B在”标记2“处发现singleton不为null了(内存空间已经分配),程序就直接把没初始化完毕的singleton返回给线程B了?
public class Singleton {
private static Singleton singleton;
private String fileName; //ftp上要打印的文件名称
private Singleton(){
fileName = getFTPFile(); // 标记1
}
private String getFTPFile() {
String remoteFileName = "";// 很艰难地连接到ftp上,然后拿到文件
return remoteFileName;
}
public Singleton getInstance(){
if(singleton == null){ // 标记2
synchronized (Singleton.class){
if(singleton == null){
singleton = new Singleton();
}
}
}
return singleton;
}
}
改进如下:
private static volatile SingletonDoubleCheck singleton;
分析:
优点:用volatile修饰的变量,线程在每次使用变量的时候,都会读取变量修改后的最的值。就是说,当发现其它线程在对这个变量正在写的时候,当前线程就不忙着读它。这样就能完美解决了上面的问题。
缺点:volatile也需要耗费性能的好伐?而且volatile是JDK1.5之后才有的好不啦?
改进如下:
思考:
1.使用普通类的方式实现单例都有两个致命问题:Java反序列化时都是新生成一个对象的;使用反射是可以直接调用private的构造方法。因此,不能从根本上保证只生成一个实例.(话说谁那么无聊啊,费劲心机就是为了多序列化一个实例??)
2.如果只是想单纯实现纯单例,而没有其他复杂的要求的话,没有必要使用双重检查锁这么复杂的方式。
public enum SingletonEnum {
singleton;
public void doSomething(){}
}
分析:
优点:实现简单;jdk底层抗序列化;jdk底层类加载机制保证线程安全;底层是用abstract修饰,无法实例化,所以也无法使用反射来初始化。
缺点:不是懒加载;jdk1.5之后才支持;失去了类的特性,有时不太好用。
改进如下:
public class Singleton {
private static class SingletonClassInstance {
private static final Singleton instance = new Singleton();
}
public static Singleton getInstance() {
return SingletonClassInstance.instance;
}
private Singleton() {}
}
分析:
优点:懒加载;线程安全;任何jdk版本都可用
缺点:额外生成一个永生代的类,增加程序的复杂性;
评价:
没有最好的实现方式,只有最适合自己的方式。一个小小的单例,就包含了这么多的知识点,学无止境哇。