单例模式,顾名思义,在程序运行时有且仅有一个实例存在。最常见的一个应用场景就是网站访问量的计数器,试想如果允许创建多个实例,那还怎么计数,这个时候就得创建有且仅有的一个实例了。如何防止程序创建多个实例呢?首先就是不能直接new。不能new那就是要将构造函数实例化,那怎么来创建实例呢?我们还是从代码着手。
1 package day_5_singleton;
2
3 /**
4 * 单例
5 * @author turbo
6 *
7 * 2016年9月8日
8 */
9 public class Singleton {
10 private static Singleton instance;
11
12 private Singleton(){
13 }
14
15 public static Singleton GetInstance(){
16 if (instance == null){
17 instance = new Singleton();
18 }
19
20 return instance;
21 }
22 }
1 package day_5_singleton;
2
3 /**
4 * @author turbo
5 *
6 * 2016年9月8日
7 */
8 public class Main {
9
10 /**
11 * @param args
12 */
13 public static void main(String[] args) {
14 Singleton singleton = Singleton.GetInstance();
15 }
16
17 }
这就是单例模式的实现,看着好像挺简单的。它和传统的工具类有什么区别呢?一般的工具类也会将构造函数设为private,但是工具类不保存状态,仅提供一些静态方法或静态属性让你使用,而单例类是有状态的。
上面是针对单线程,单线程不会出现多个实例的情况,但是在多线程里就有可能会出现创建多个实例了。
不信我们来看。
首先,我们用单线程的方式来创建两个实例,看他们实际上是不是一个实例。
Singleton singleton1 = Singleton.GetInstance();
Singleton singleton2 = Singleton.GetInstance();
System.out.println(singleton1.hashCode());
System.out.println(singleton2.hashCode());
程序输出:
两个实例的hash值一样,说明他们是同一个实例。
我们再来看多线程的结果是怎样的,首先创建一个线程,在线程里实例化对象并输出hash值。
1 package day_5_singleton;
2
3 /**
4 * 多线程实例化单例类
5 * @author turbo
6 *
7 * 2016年9月8日
8 */
9 public class ThreadTest implements Runnable {
10
11 /* (non-Javadoc)
12 * @see java.lang.Runnable#run()
13 */
14 @Override
15 public void run() {
16 Singleton singleton = Singleton.GetInstance();
17 System.out.println(singleton.hashCode());
18 }
19
20 }
测试代码:
Thread singleton1 = new Thread(new ThreadTest());
Thread singleton2 = new Thread(new ThreadTest());
singleton1.start();
singleton2.start();
输出结果:
输出结果为两个实例的hash值确实不一样,说明确实创建了两个不同的实例。怎么办呢?加锁。对临界区共享资源的访问进行互斥访问,当一个线程进入临界区时,加锁,另一个线程进入临界区时则等待直到该对象被释放。
修改单例类。
1 package day_5_singleton;
2
3 /**
4 * 单例
5 *
6 * @author turbo
7 *
8 * 2016年9月8日
9 */
10 public class Singleton {
11 private static Singleton instance;
12
13 private Singleton() {
14 }
15
16 public static synchronized Singleton GetInstance() { //synchronized关键字
17
18 if (instance == null) {
19 instance = new Singleton();
20 }
21
22 return instance;
23 }
24 }
仅需对构造实例的方法添加synchronized关键字即可。
测试代码不变,看结果会发现两个线程创建的两个实例的hash值一样,说明它们是同一个实例。这样我们不管是在单线程还是多线程,所创建的这个单例类在程序里确实有且仅有一个。
我们其实可以继续引申synchronized关键字是什么,在方法上加和在给一个程序段加有什么区别?这会在以后开设一个多线程专栏来系统的介绍一下Java多线程。