单例模式:保证一个类仅有一个实例,并提供一个访问它的全局访问点
安全的单例模式:
/*
* @(#)Singleton.java 2014-8-1
*
* Copyright 2014 XXXX, Inc. All rights reserved.
*/
package com.fiberhome.singleton;
/**
* 单例对象
*
* @author liyan
* @version 2014-8-1
* @since 1.0
*/
public class Singleton
{
/**
* 构造器似有化保证外部不能进行new操作
*/
private Singleton(){}
/**
* 静态内部类保证线程安全
*
* @author liyan
* @version 2014-8-1
* @since 1.0
*/
private static class SingleHolder
{
final static Singleton INSTANCE = new Singleton();
}
/**
* 获取实例对象的方法
* @return
*/
public static Singleton getInstance()
{
return SingleHolder.INSTANCE;
}
/**
* 普通的类方法
*/
public void doSth(){}
}
不安全的单例设计:
/*
* @(#)SingletonUnSafe.java 2014-8-1
*
* Copyright 2014 XXXX, Inc. All rights reserved.
*/
package com.fiberhome.singleton;
/**
* 非线程安全的单例设计
*
* @author liyan
* @version 2014-8-1
* @since 1.0
*/
public class SingletonUnSafe
{
private static SingletonUnSafe sing;
/**
* 似有化构造器
*/
private SingletonUnSafe()
{
}
/**
* 获取实例方法
* @return
*/
public static SingletonUnSafe getInstance()
{
/*
* 首次初始化当前对象的一种假设情景
* 此时的代码块执行过程中的快照
* 两个实例化线程:Thrad1 and Thread2
*/
//线程切换Thrad2线程执行此处执行判断分支为True即将进入
if (sing == null)
{
//Thrad1 刚进入代码块,还没有执行new指令
sing = new SingletonUnSafe();
}
return sing;
}
}
测试用例
/*
* @(#)SingletonTest.java 2014-8-1
*
* Copyright 2014 XXXX, Inc. All rights reserved.
*/
package com.fiberhome.singleton;
import org.junit.Assert;
import org.junit.Test;
/**
* 测试单例
*
* @author liyan
* @version 2014-8-1
* @since 1.0
* @see
*/
public class SingletonTest
{
@Test
public void singletonTestCase()
{
Assert.assertEquals(Singleton.getInstance(), Singleton.getInstance());
}
}
为什么静态内部类可以保证初始化单例对象的时候是线程安全的?
静态内部类SingleHolder在获取其静态域的时候发送getstatic指令,虚拟机接收到这个指令立即对SingleHolder初始化,在调用SingleHolder构造器初始化之前首先完成其静态域的收集和初始化,静态域初始化完成以后是以单例的形式存在静态块区域的,随后调用构造器,构造器的初始化由jvm保证线程安全。所以静态内部类的这种初始化时线程安全的。
关于类加载的时机
类加载的“顺序”(非线性):加载->验证->准备->解析->初始化->使用-卸载
在初始化阶段,虚拟机规范规定了只有4中情况必须立即对类进行初始化:
- 遇到new ,getstatic,putstatic,invokestatic四条字节码指令。如果类没有进行初始化需要对其进行初 始化操作。
- new : 使用new关键字实例化对象的时候
- getstatic /putstatic : 读取或者设置一个类的静态字段
- invokestatic :调用一个类的静态方法
- 调用reflect包的方法对类进行反射调用的时候,如果没有初始化,需要对其进行初始化
- 初始化一个类的时候,返现其父类还没有初始化,则需要初始化其父类
- 当虚拟机启动的时候,用户需要指定一个要执行的主类,虚拟机会先初始化这个主类
------------------------------------------补充概念------------------------------------
类加载器
“通过一个类的全限定名来获取描述此类的二进制字节流”这个动作的加载木块叫做“类加载器”
类加载器的分类:
- 启动类加载器(Bootstrap ClassLoader):加载存在<JAVA_HOME>\lib 或者-Xbootclasspath目录中的,并且是虚拟机可识别的类库到虚拟机内存。Java程序无法直接引用启动类加载器
- 扩展类加载器:加载<JAVA_HOME>\lib\ext目录中的,或者被java.ext.dirs系统变量指定的路径中的所有类库
- 应用程序类加载器:加载用户类路径(ClassPath)上指定的类库。
2014年8月1日 西安
hanily@msn.com