单例模式(Singleton Pattern):
通过单例模式你可以:
确保一个类只有一个实例被建立 ,
提供了一个对对象的全局访问指针 ,
提供了一个对对象的全局访问指针 ,
下面看个单例经典例子:
public class Singleton{
private static Singleton instance = null;
private Singleton(){}
public static Singleton getInstance(){
if(instance==null){
instance = new Singleton();//懒汉式单例
}
return instance;
}
}
在例1中的单例模式的实现很容易理解。Singleton类保持了一个对单独的单例实例的静态引用,并且从静态方法getInstance()中返回那个引用。
关于Singleton类,有几个让我们感兴趣的地方。首先,Singleton使用了一个众所周知的懒汉式实例化去创建那个单例类的引用;结果,这个单例类的实例直到getInstance()方法被第一次调用时才被创建。这种技巧可以确保单例类的实例只有在需要时才被建立出来。其次,注意Singleton实现了一个private的构造方法,这样客户端不能直接实例化一个Singleton类的实例。
关于Singleton类,有几个让我们感兴趣的地方。首先,Singleton使用了一个众所周知的懒汉式实例化去创建那个单例类的引用;结果,这个单例类的实例直到getInstance()方法被第一次调用时才被创建。这种技巧可以确保单例类的实例只有在需要时才被建立出来。其次,注意Singleton实现了一个private的构造方法,这样客户端不能直接实例化一个Singleton类的实例。
在单线程情况下,对该类添加main方法进行测试:
public
static
void
main(String[] agrs) {
for
(
int
i = 0; i < 5; i++) {
System.
out
.println(
"Singleton.getInstance().hashCode() --"
+ i +
" -- "
+Singleton.getInstance().hashCode());
}
}
测试结果如下:
可以看出在单线程情况下,调用多次 getInstance方法,得到的Singleton的实例的hash码都是一样的。说明只生成了一个实例对象。
但是我们仔细的分析代码 ,会发现其实前面的代码不是线程安全的,如果两个线程,我们称它们为线程1和线程2,在同一时间调用Singleton.getInstance()方法,如果线程1先进入if块,然后线程2进行控制,那么就会有Singleton的两个的实例被创建。尽管这个问题就在这段代码上:
if(instance==null){
instance = new Singleton();//懒汉式单例
}
如果一个线程在第二行的赋值语句发生之前切换,那么成员变量instance仍然是null,然后另一个线程可能接下来进入到if块中。在这种情况下,两个不同的单例类实例就被创建。不幸的是这种假定很少发生,这样这种假定也很难在测试期间出现。下面我们来模拟这个问题的产生。
public
class
Singleton {
private
static
Singleton
instance
=
null
;
private
static
boolean
flag
=
true
;
private
Singleton() {
}
public
static
Singleton getInstance() {
if
(
instance
==
null
) {
threadSleep();
instance
=
new
Singleton();
// 懒汉式单例
}
System.
out
.println(
"instance.hashCode() : "
+
instance
.hashCode());
return
instance
;
}
public
static
void
main(String[] agrs) {
new
Thread(
new
SingletonTestRunnable()).start();
new
Thread(
new
SingletonTestRunnable()).start();
}
private
static
void
threadSleep(){
if
(
flag
){
try
{
System.
out
.println(
"当前线程休眠
5秒
!"
);
Thread.currentThread().sleep(5000);
System.
out
.println(
"休眠
5秒结束
!"
);
}
catch
(InterruptedException e) {
e.printStackTrace();
}
}
}
}
class
SingletonTestRunnable
implements
Runnable {
public
void
run() {
Singleton s = Singleton.getInstance();
}
}
在Singleton中加入一个方法threadSleep
(),让当前线程休眠
5秒钟,
SingletonTestRunnable实现
Runnable接口,在
run方法中
调用
Singleton.getInstance()得到
Singleton的实例,运行
main方法,测试结果如下:
可以看出对于两个线程得到的Singleton的实例的hashCode是不同的,则可以知道—这是两个不同的实例。分析代码:在第一个线程运行到
if
(
instance
==
null
) {
threadSleep();
instance
=
new
Singleton();
// 懒汉式单例
}
时候休眠
5秒钟,随后第二线程进入该代码片段,运行到
threadSleep方法时候,也休眠
5秒钟,第一个线程醒来,继续向下执行代码
instance
=
new
Singleton(),创建第一个
Singleton对象实例,随后第二个线程也执行了
instance
=
new
Singleton(),这样创建了第二个实例对象。
那么我们可以看出,Songleton 不是线程安全的。
尝试修正:
第一种方案:在getInstance()方法前面加上 关键字 synchronized ,测试结果如下:
分析这个结果:开启两个线程,只打印了一次测试汉字,这就说明 只调用了一次 threadSleep()方法,而打印出来的hashCode也是相同的,表示引用了同一个Singleton对象实例。
第二种方案:一个简单、快速而又是线程安全的单例模式实现
public
class
Singleton {
public
static
Singleton
INSTANCE
=
new
Singleton();
private
Singleton() {
}
public
static
void
main(String[] agrs) {
new
Thread(
new
SingletonTestRunnable()).start();
new
Thread(
new
SingletonTestRunnable()).start();
}
}
class
SingletonTestRunnable
implements
Runnable {
public
void
run() {
System.
out
.println( Singleton.
INSTANCE
.hashCode());
}
}
测试结果看出两个线程创建的
Singleton对象引用指向是同一块堆内存,
hashCode相同。
这段代码是线程安全的是因为静态成员变量一定会在类被第一次访问时被创建。你得到了一个自动使用了懒汉式实例化的线程安全的实现。
public class Singleton
{
private static final Singleton sl = new Singleton();
private Singleton(){};
public static Singleton slt()
{
return sl;
}
}