欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

java设计模式 - 单例模式(干货)

程序员文章站 2022-03-10 16:15:58
深度讲解23种设计模式,力争每种设计模式都刨析到底。废话不多说,开始第一种设计模式 - 单例。 作者已知的单例模式有8种写法,而每一种写法,都有自身的优缺点。 1,使用频率最高的写法,废话不多说,直接上代码 /** * @author xujp * 饿汉式 静态变量 单例 */ public cla ......

  深度讲解23种设计模式,力争每种设计模式都刨析到底。废话不多说,开始第一种设计模式 - 单例。

  作者已知的单例模式有8种写法,而每一种写法,都有自身的优缺点。

1,使用频率最高的写法,废话不多说,直接上代码

/**
* @author xujp
* 饿汉式 静态变量 单例
*/
public class singleton  implements serializable {

private static final long serialversionuid = 1l;

private final static singleton instance = new singleton();

private singleton(){}
public static singleton getsingleton(){
return instance;
}

private string tmp;

public string gettmp() {
return tmp;
}

public void settmp(string tmp) {
this.tmp = tmp;
}
}

 new singleton() 的执行时机 - > 类加载时

 这种方法是最通用的单例实现,也是笔者常用的,但这种方法有一些缺点

 1)内存方面,如果单例中的内容很多,会在类加载时,就占用java虚拟机(这里专指hotspot)空间。

 2)序列化以及反序列化问题,如果这个单例类实现了序列化接口serializable,那么可以通过反序列化来破坏单例。

 通过反序列化破坏单例:

public static void main(string[] args) throws ioexception, classnotfoundexception {
singleton singleton=null;
singleton singletonnew=null;

singleton=singleton.getsingleton();

singleton.settmp("123");

bytearrayoutputstream bos=new bytearrayoutputstream();
objectoutputstream oos=new objectoutputstream(bos);
oos.writeobject(singleton);

bytearrayinputstream bis=new bytearrayinputstream(bos.tobytearray());
objectinputstream ois=new objectinputstream(bis);
singletonnew= (singleton) ois.readobject();

singleton.settmp("456");

system.out.println(singletonnew.gettmp());
system.out.println(singleton.gettmp());
system.out.println(singleton==singletonnew);
}

  输出结果为:

  false

  123

  456

  从这里例子中我们可以看到单例被破坏了,也就不能保证单例的唯一性。

2,第一种方案的变种

/**
* @author xujp
* 饿汉式 静态代码块 单例
*/
public class singleton implements serializable {

private static final long serialversionuid = 1l;

private final static singleton instance;

static {
instance = new singleton();
}

private singleton(){}

public static singleton getsingleton(){
return instance;
}
}

 其实这种方法和第一种方法,几乎没有什么区别。

3,线程不安全的写法 - 1

/**
* @author xujp
* 懒汉式 单例 线程不安全
*/
public class singleton implements serializable {

private static final long serialversionuid = 1l;

private static singleton instance;

private singleton(){}

public static singleton getsingleton(){
if(null == instance) {
instance = new singleton();
}
return instance;
}
}

 这种写法,虽然实现了懒加载,节省了内存,但线程不安全。

 假设有两个线程,并假设 new singleton() 耗时2秒,0秒时,线程1执行new,然后去等待,1秒时,线程2执行if判断,

这个时候判断结果就是true,这样就会出现两个singleton对象,完美破坏掉了单例。

4,线程不安全的写法 - 2

/**
* @author xujp
* 懒汉式 单例 代码块加锁 线程不安全
*/
public class singleton implements serializable {

private static final long serialversionuid = 1l;

private static singleton instance;

private singleton(){}

public static singleton getsingleton(){
if(null == instance) {
synchronized (singleton.class) {
instance = new singleton();
}
}
return instance;
}
}

 这种写法虽然在new single()时,增加了锁,但这个锁,并不能阻止单例被破坏,所以这种写法错误。

 同样,假设有两个线程,线程1执行到synchronized时,线程2执行if判断,这个时候判断结果就是true,

这样就会出现两个singleton对象,同样完美破坏掉了单例。

5,线程安全,但资源消耗过多

/**
* @author xujp
* 懒汉式 单例 方法加锁 线程不安全
*/
public class singleton implements serializable {

private static final long serialversionuid = 1l;

private static singleton instance;

private singleton(){}

synchronized public static singleton getsingleton(){
if(null == instance) {
instance = new singleton();
}
return instance;
}
}

 这种写法确实能够保证线程安全,但synchronized属于方法锁,而方法锁回锁定对象,导致性能低下。

6,相对完美的写法 - 1

/**
* @author xujp
* 懒汉式 单例 代码加锁 线程安全
*/
public class singleton implements serializable {

private static final long serialversionuid = 1l;

private static volatile singleton instance;

private singleton(){}

public static singleton getsingleton(){
if(null == instance) {
synchronized (singleton.class) {
if(null == instance) {
instance = new singleton();
}
}
}
return instance;
}
}

双检查这种写法,在多线程问题上,属实没有问题,synchronized也没有锁定对象,而且也优化了锁资源开销问题。

7,相对完美的写法 - 2

/**
* @author xujp
* 懒汉式 单例 静态内部类 线程安全
*/
public class singleton implements serializable {

private static final long serialversionuid = 1l;

private static class singletoninstance{
private static singleton instance = new singleton();
}
private singleton(){}

public static singleton getsingleton(){
return singletoninstance.instance;
}
}

使用静态内部类来实现单例,主要借助jvm机制,静态内部类初始化的时候,其他线程无法进入,从而避免了多线程问题。

而且静态内部类不会直接初始化,从而减轻了内存开销。

8,完美写法

/**
* @author xujp
* 枚举实现单例
*/
public enum singleton {
singleton;
private string property = "hello ca fe ba be";
public void dosomething(){
system.out.println(property);
}
}

这种写法用枚举解决多线程问题,而且时唯一一种解决序列化问题的写法。

改写法出自大神josh bloch,如果有兴趣可以去查看一下他的资料。

总结:

1,1和2写法虽然是饿汉式,没有实现懒加载,也没有100%保证单例,但却是我们最常用的写法,

 因为,单例对象通常占用空间不会很大,而且程序都由程序员自己管理,被反序列的危险性不高。

2,3和4写法实现了懒加载,减少了内存开销,但不能使用,因为多线程开发,是我们常见的开发。

3,5写法使用了方法锁,会将对象锁住,会导致性能大打折扣。

4,6和7写法,懒加载、性能都非常完美,缺点只有一个,那就是序列化问题。

5,8写法,笔者暂未发现缺点。

实际开发中,无论是使用1、2写法,还是使用6、7写法,亦或是使用8写法,都是可以的。