JAVA设计模式笔记整理(三)

5.单件模式

定义:

确保一个类只有一个实例,并提供一个全局访问点。

有一些对象其实我们只需要一个,比方说线程池(threadpool)、缓存(cache)、对话框、处理偏好设置和注册表的对象、日志对象……如果这类对象制造出多个实例,就会导致许多问题产生。

虽然利用全局变量也可以确保只有一个实例会被创建,但这必须在程序一开始就创建好对象(跟JVM具体实现有关)。利用单件模式,我们可以在需要时才创建对象。

单件模式的类图:

一个经典的单件模式:

public class Singleton {private static Singleton uniqueInstance;private Singleton(){}public static Singleton getInstance(){if(uniqueInstance==null){uniqueInstance=new Singleton();}return uniqueInstance;}//这里是其他的有用方法}

在getInstance()方法中,if语句的作用是,如果uniqueInstance是空的,表示还没有创建实例,此时我们就利用私有的构造器产生一个Singleton实例并把它赋值到uniqueInstance静态变量中。如果我们不需要这个实例,它就永远不会产生,这就是“延迟实例化”(lazy instantiaze)。如果uniqueInstance不是null,就表示之前已经创建过对象。我们就直接跳到return语句。

*这样的实现会有一些问题

示例代码:

public class Singleton {private static Singleton uniqueInstance;private long id;private Singleton(long id){this.id=id;}public static Singleton getInstance(){if(uniqueInstance==null){uniqueInstance=new Singleton(Thread.currentThread().getId());}return uniqueInstance;}public void getId(){System.out.println("The singletonis id is "+id);}//这里是其他的有用方法}
public class SingletonRun implements Runnable {public void run(){Singleton.getInstance().getId();}}
public class TestSingleton {public static void main(String[] args){Thread t1=new Thread(new SingletonRun());Thread t2=new Thread(new SingletonRun());t1.start();t2.start();}}

所期望的的结果应类似:

The singletonis id is 8The singletonis id is 8

但往往会出现类似如下的情况:

The singletonis id is 9The singletonis id is 8

出现这样的情况是因为

一开始 线程t1和线程t2的uniqueInstance变量为null

此时当线程t1执行到

if(uniqueInstance==null)

时,if条件刚判断结束进入if后面的代码块后,操作系统中断了t1线程的执行,另一线程t2开始被调度。此时若t2也进入了if的语句块,并完整执行了语句块的内容,即

uniqueInstance=new Singleton(Thread.currentThread().getId());

并return后,线程t2的getId()方法返回的便是t2的id号,

之后操作系统继续调度t1执行,t1也完整执行了if语句块中的内容,即

uniqueInstance=new Singleton(Thread.currentThread().getId());

并return后,线程t1的getId()方法返回的便是t1的id号。(若t2同t1一样if条件判断结束后便被中断,t1完整执行了if语句块内容并return后,也会出现错误情况。)

如何改善多线程:

1) 如果getInstance()的性能对应用程序不是很关键,可以将getInstance()生命为同步方法

public static synchronized Singleton getInstance(){if(uniqueInstance==null){uniqueInstance=new Singleton(Thread.currentThread().getId());}return uniqueInstance;}

但是你必须知道,同步一个方法可能造成程序执行效率下降100倍。如果程序中频繁用到getInstance(),你就得重新考虑了。

2)使用“急切”创建实例,而不用延迟实例化的做法

public class Singleton{private static Singleton uniqueInstance=new Singleton();private Singleton(){}public static Singleton getInstance(){return uniqueInstance;}}

利用这个做法,我们依赖JVM在加载这个类时马上创建此唯一的单件实例。JVM保证在任何线程访问uniqueInstance静态变量之前,一定先创建此实例。

3)用“双重检查加锁”,在getInstance()中减少使用同步

public class Singleton{private volatile static Singleton uniqueInstance;private Singleton(){}public static Singleton getInstance(){if(uniqueInstance ==null){synchronized(Singleton.class){if(uniqueInstance==null){uniqueInstance=new Singleton();}}}return uniqueInstance;}}

如果性能是你关心的的重点,那么这个做法可以帮你大大地减少getInstance()的时间耗费。

*在1.4及更早版本的Java中,许多关于JVM对于volatile关键字的实现会导致双重检查加锁的失效

*关于volatile关键字,可以看这里

要点:

单件模式确保程序中一个类最多只有一个实例

单件模式也提供访问这个实例的全局点

在Java中实现单件模式需要私有的构造器、一个静态方法和一个静态变量

确定在性能和资源上的限制,然后小心的选择适当的方案来实现单件,以解决多线程的问题

如果不是采用jdk1.5,双重检查加锁实现会失效

小心,你如果使用多个类加载器,可能导致单件失效而产生多个实例(解决办法,自行指定类加载器,并指定同一个类加载器)

自说自话:原来单件并不像想象的那么简单,本以为

public class Singleton {private static Singleton uniqueInstance;private Singleton(){}public static Singleton getInstance(){if(uniqueInstance==null){uniqueInstance=new Singleton();}return uniqueInstance;}//这里是其他的有用方法}

就是单件模式了,没想到还要考虑多线程的问题。早点知道ocjp这题也就不会错了,呵呵。

当眼泪流尽的时候,留下的应该是坚强。

JAVA设计模式笔记整理(三)

相关文章:

你感兴趣的文章:

标签云: