单例模式:保证一个类仅有一个实例,并提供一个访问其实例的一个全局访问点
根据单例模式的定义,写一个单例模式的例子需要注意两点:
1.实例有该类自己生成
为了防止客户代码通过 new Singleton()来实例一个对象,需要将 Singleton的默认构造函数定义为private
2. 提供一个访问其实例的全局访问点
将访问方法getInstance()定义为 static ,则直接通过Singleton.getInstance() 来获取到实例
3.单例只能有一个实例
在Singleton类中,将instance 定义为 static ,则保证了不管多少个 Singleton都只有并仅有一个instance 静态变量。
单例的实现有两种方式:
饿汉单例:在类被加载时就初始化实例
懒汉单例:类加载之后,第一次调用getInstance()方法时,才初始化实例。
不过懒汉式单例的getInstance () 方法需要注意到线程的同步问题:在单例类已经被加载进来,但还没有被调用生成实例之前,有多个线程同时调用getInstance() 方法,那么则可能多次调用 instance = new Singelton_lazy()语句,导致出现多个实例的结果。
所以懒汉单例的getInstance() 方法要使用synchronized 来保证其线程安全性。
现在我们来模拟懒汉单例模式的getInstance() 如果不注意线程安全性时出现的线程不安全现象:
现在将Singleton_lazy 改变一下: 1. 去掉synchronized
2. 认为的改变getInstance() 方法,但传入的参数为0时,则访问SingletonFull的线程需要休眠500毫秒, 当500毫秒一过,则返回一个单例的实例
现在编写两个线程类:
编写测试用的代码:
输出结果:
从输出结果中,我们可以看到Mythread 和 Mythread2 两个线程先后访问getInstance()方法,Mythread 在访问getInstance ()时,因为带了参数 0, 所以getInstance()在还没有建立单例实例时停顿了500毫秒。而在这短时间里,Mythread2 趁虚而入,访问 getInstance()方法,立马生成一个单例实例,并返回给Mythread2 。之后500毫秒过了,Mythread所访问的getInstance 线程苏醒,继而执行 instance = new SingletonFull() 方法,返回另外一个单例实例。
那么换个思路,我是否可以将测试用的代码换成:
SingletonFull sFull = SingletonFull.getInstance(0);
SingletonFull sFull2 = SingletonFull.getInstance(1);
System.out.println(sFull);
System.out.println(sFull2);
结果显示只生成了一个单例实例,为什么呢?
前面提到了在SingletonFull 中的 Thread.currentThread.sleep(500),是使访问Singleton 类的线程休眠,而不是让SingletonFull 休眠,而且SingletonFull 不是线程类,无法休眠。
通过将getInstance() 修改成下面的代码,仅仅是添加了一个输出当前访问SingletonFull 类的线程名字。
接着改变测试代码,也添加了一条输出当前线程名称的代码
最后结果输出:
从结果中可以看到在整个测试代码运行过程中,只有main 线程在运行。并且这里,由于main线程先休眠了500毫秒,然后才依次打印出单例实例的名称。而在前面的测试代码中,程序一执行,就先输出了一个单例实例名称,过了500毫秒后,才输出另外一个单例实例名称。