单例模式的设计有懒汉式和饿汉式。
懒汉式指该实例的创建是在调用该类对外开放的get接口时才生成。
饿汉式指该实例的创建在为调用时就已经存在。
下面介绍集中单例模式的写法
1.
public class SinglePattern {
private static SinglePattern singlePattern = null;
private SinglePattern(){
System.out.println("Hi");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static SinglePattern getSinglePattern(){
if( singlePattern == null ){
init();
}
System.out.println("获取实例成功:" + singlePattern.toString());
return singlePattern;
}
private static void init(){
singlePattern = new SinglePattern();
}
}
这是最简单的一种懒汉式写法,该类的实例会在第一次调用的时候生成。但是,这种方法是一种线程不安全的写法,在多线程中,一但两个线程同时发起,同时进入了实例化的过程时,那么将会在不同的线程有两个实例。
测试代码如下, 注意下面的几种方法如果没有特别声明测试代码都是使用这一段
public class Test extends Thread{
public void run(){
SinglePattern.getSinglePattern();
}
public static void main(String[] args){
Thread thread1 = new Test();
Thread thread2 = new Test();
thread1.start();
thread2.start();
}
}
结果
Hi
Hi
获取实例成功:SinglePattern@527c6768
获取实例成功:SinglePattern@65690726
可以发现SinglePattern的实例初始化了两次。
2.
public class SinglePattern {
private static SinglePattern singlePattern = null;
private SinglePattern(){
System.out.println("Hi");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static synchronized SinglePattern getSinglePattern(){
if( singlePattern == null ){
init();
}
System.out.println("获取实例成功:" + singlePattern.toString());
return singlePattern;
}
private static void init(){
singlePattern = new SinglePattern();
}
}
这是第一种方法的简单变形,将整个函数加锁保证整个函数只能被调用一次,但是会造成效率的降低,因为我们第二次以后的调用只需要获取前面实例化出来的对象,但由于整个函数都被加锁刘,所以该函数的调用还是需要等待,造成不必要的效率浪费。
测试代码还是上面那个Test类。
结果如下
Hi
获取实例成功:SinglePattern@55f33675
获取实例成功:SinglePattern@55f33675
此处注意一点,如果是给init() 函数加上锁而不是getSinglePattern()函数是没有作用的,因为它们会在init函数前面等待,两个线程会依次执行init函数,实例化出两个对象。当然,给init()函数加上锁的思路是正确的,因为我们只需要保证在创建的时候保持同步即可,而不是整个getSinglePattern()方法都加上锁。看下文
3.
public class SinglePattern {
private static SinglePattern singlePattern = null;
private SinglePattern(){
System.out.println("Hi");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static SinglePattern getSinglePattern(){
if( singlePattern == null ){
init();
}
System.out.println("获取实例成功:" + singlePattern.toString());
return singlePattern;
}
private static synchronized void init(){
if( singlePattern == null )
singlePattern = new SinglePattern();
}
}
这里使用了双重校验锁的方法,即使有多个线程进入了init()方法,但是除了第一个线程会实例化对象外,其它进入的线程都会发现singlePattern对象已经被实例了。
测试结果
Hi
获取实例成功:SinglePattern@55f33675
获取实例成功:SinglePattern@55f33675
4.
public class SinglePattern {
private static SinglePattern singlePattern = init();
private SinglePattern(){
System.out.println("Hi");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static SinglePattern getSinglePattern(){
System.out.println("获取实例成功:" + singlePattern.toString());
return singlePattern;
}
private static SinglePattern init(){
return new SinglePattern();
}
}
这是一种饿汉式的单例模式写法,这种方式基于类加载器的机制,利用了类装载器自身的锁机制保证了singlePattern对象拥有单一实例。关于更多类装载器的知识可以看我的其他博文这里,和这里还有这里,当然,装载SinglePattern这个类使用的是AppClassLoader系统类装载器,但是这种方法有一定的缺陷,即该类的装载可能会发生在很多的情况下,比如将该类放在了ExtClassLoader扩展类加载器的路径下,那么该类会自动装载。即,即使你在整个程序中没有使用到该对象,该对象也依然会实例化,造成了内存空间的浪费。
测试代码
public class Test extends Thread{
public void run(){
SinglePattern.getSinglePattern();
}
public static void main(String[] args){
try {
Class c =Class.forName("SinglePattern");
System.out.println(c.getClassLoader());
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Thread thread1 = new Test();
Thread thread2 = new Test();
thread1.start();
thread2.start();
}
}
测试结果
可以发现,在系统装载器装载完后,singlePattern对象的实例化已经完成。
Hi
sun.misc.Launcher$AppClassLoader@3326b249
获取实例成功:SinglePattern@55f33675
获取实例成功:SinglePattern@55f33675
5.
public class SinglePattern {
private static SinglePattern singlePattern;
private SinglePattern(){
System.out.println("Hi");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static SinglePattern getSinglePattern(){
if( singlePattern == null )
singlePattern = SinglePatternHolder.singlePatternHolder;
System.out.println("获取实例成功:" + singlePattern.toString());
return singlePattern;
}
private static class SinglePatternHolder{
static{
System.out.println("我被加载了");
}
private static SinglePattern singlePatternHolder = init();
private static SinglePattern init(){
return new SinglePattern();
}
}
}
这种写法利用了内部类来获取SinglePattern的实例,类装载器即使一开始就装载了SinglePattern类,但是在没有调用到getSinglePattern()方法之前是不会显示的装载内部类SinglePatternHolder,也就不会实例化对象singlePatternHolder
测试代码
public class Test extends Thread{
public void run(){
SinglePattern.getSinglePattern();
}
public static void main(String[] args){
try {
Class c =Class.forName("SinglePattern");
System.out.println(c.getClassLoader());
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Thread thread1 = new Test();
Thread thread2 = new Test();
thread1.start();
thread2.start();
}
}
输出结果
sun.misc.Launcher$AppClassLoader@3326b249
我被加载了
Hi
获取实例成功:SinglePattern@65690726
获取实例成功:SinglePattern@65690726
可以看出只有在调用了getSinglePattern()方法之后,jvm才开始显示装载内部类SinglePatternHolder,并初始化静态域,这样即可以保证SinglePattern只存在一个变量,又可以保证在需要的时候才实例化对象singlePattern。