好记性不如烂笔头
今天写一篇单例模式的博客,主要介绍懒汉模式、饿汉模式、枚举模式。
所谓的单例模式就是这个单例对象从程序的创建到死亡有且仅有一个对象。
- 懒汉模式
package com.fx.t;
public class Singleton2 {
private static Singleton2 singleton2;
private Singleton2(){}
public static Singleton2 getInstance(){
if(singleton2 == null){
singleton2 = new Singleton2();
}
return singleton2 ;
}
public void test2(){
System.out.println("test2");
}
}
测试
好像没什么问题,还挺简单的啊。事实上这段代码还是有问题的,这里的单例在单线程是没问题的,但是在多线程那就不一定了。
注意观察这一段代码
public static Singleton2 getInstance(){
if(singleton2 == null){
singleton2 = new Singleton2();
}
return singleton2 ;
}
到底有什么问题呢,不卖关子了。
请问如果A线程执行到if(singleton2 == null)代码块和 singleton2 = new Singleton2()之间时,cup资源突然被B线程抢去了。很巧的是此时B线程中也会调用getInstance(),尴尬的事情马上就要发生了。刚才A线程还没有执行到singleton2 = new Singleton2(),所有singleton2还是等于null的。于是B线程就进入if代码块,还不犹豫的将singleton2实例化了。等会cup资源被A抢回去的时候,singleton2又要被实例化一个。这就违背了单例的定义。
说了这么多的理论,上个例子吧。
此时在if块中加了一个 Thread.sleep(1000),这个主要是模拟多线程竞争cup资源【休息了就会让出cup的资源给别的线程】
package com.fx.t;
public class Singleton2 {
private static Singleton2 singleton2;
private Singleton2(){}
public static Singleton2 getInstance(){
if(singleton2 == null){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
singleton2 = new Singleton2();
}
return singleton2 ;
}
public void test2(){
System.out.println("test2");
}
}
测试代码【假设A线程先抢到CUP】
解释:A线程执行到if 和 singleton2 = new Singleton2()之间时休息1000毫秒,
然后B线程也执行到if 和 singleton2 = new Singleton2()之间时休息1000毫秒,
然后A线程继续执行,实例化singleton2。
然后B线程继续执行,又实例化singleton2了。
static Singleton2 s1 = null;
static Singleton2 s2 = null;
public static void main(String[] args){
//A线程
new Thread(new Runnable() {
@Override
public void run() {
s1 = Singleton2.getInstance();
}
}).start();
//B线程
new Thread(new Runnable() {
@Override
public void run() {
s2 = Singleton2.getInstance();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2000);
System.out.println(s1==s2);
s1.test2();
s2.test2();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
结果 :结果说明我的猜想还是正确的,s1并不等于s2
false
test2
test2
改进:
package com.fx.t;
public class Singleton2 {
private static Singleton2 singleton2;
private Singleton2(){}
public static Singleton2 getInstance(){
if(singleton2 == null){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (Singleton2.class){
if(singleton2 == null){
singleton2 = new Singleton2();
}
}
}
return singleton2 ;
}
public void test2(){
System.out.println("test2");
}
}
可以看到我们加了这么一段代码
synchronized (Singleton2.class){
if(singleton2 == null){
singleton2 = new Singleton2();
}
}
我们分析一下这段代码的作用
synchronized,加了一把锁。
A线程执行到了synchronized,获取到了这个对象的锁【在A没有释放这把锁的时候,别的资源想要获取这把锁是不可能的,只能乖乖到外面等着】,即使在这个时候B线程获取到cup了,也只能执行到synchronized上面的一行代码,然后等待获取锁。直到A又获取到了cup,将singleton2实例化,并且把这段代码执行完之后,就会释放锁。然后B在某个时刻又获取到了cup,执行剩下的代码,发现singleton2 == null 是false,所以if代码块就不会执行了,直接跳过,返回A执行时实例化的singleton2。懒汉模式就到这里了
- 饿汉模式
这个没什么好说的
package com.fx.t;
/**
* 饿汉式
是否 Lazy 初始化:否
是否多线程安全:是
实现难度:易
描述:这种方式比较常用,但容易产生垃圾对象。
优点:没有加锁,执行效率会提高。
缺点:类加载时就初始化,浪费内存。
它基于 classloder 机制避免了多线程的同步问题,
不过,instance 在类装载时就实例化,虽然导致类装载的原因有很多种,
在单例模式中大多数都是调用 getInstance 方法, 但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,
这时候初始化 instance 显然没有达到 lazy loading 的效果。
*/
public class Singleton {
private static Singleton singleton = new Singleton();
private Singleton(){}
public static Singleton getInstance(){
return singleton ;
}
public void test(){
System.out.println("test");
}
}
- 枚举模式
直接上代码,这个是看着别人的例子写的,记录一下,对enum不太熟
package com.fx.t;
/**
* JDK 版本:JDK1.5 起
是否 Lazy 初始化:否
是否多线程安全:是
实现难度:易
描述:这种实现方式还没有被广泛采用,但这是实现单例模式的最佳方法。它更简洁,自动支持序列化机制,绝对防止多次实例化。
*/
public class EnumSingleton {
private EnumSingleton(){}
public static EnumSingleton getInstance(){
return Singleton.INSTANCE.getSingleton();
}
private static enum Singleton{
INSTANCE;
private EnumSingleton singleton ;
//JVM会保证此方法绝对只调用一次
private Singleton(){
singleton = new EnumSingleton();
}
public EnumSingleton getSingleton() {
return singleton;
}
}
}
调用
EnumSingleton singleton1 = EnumSingleton.getInstance();
~完~