单例模式,是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例的特殊类。通过单例模式可以保证系统中,应用该模式的类一个类只有一个实例。即一个类只有一个对象实例
(1)将构造方法私有化,使其不能在类的外部通过new关键字实例化该类对象。
(2)在该类内部产生一个唯一的实例化对象,并且将其封装为private static类型。
(3)定义一个静态方法返回这个唯一对象。
延迟加载,当只有使用的时候才开始真正的实例化
/**
* 单例设计模式懒汉式
*/
public class SingleTonLazy {
//定义一个实例化对象
private static SingleTonLazy singleTonLazy = null;
//构造方法私有化
private SingleTonLazy(){}
//静态工厂方法
private static SingleTonLazy getInstance(){
if (singleTonLazy == null){
singleTonLazy = new SingleTonLazy();
}
return singleTonLazy;
}
}
单例设计模式本身是线程安全的,因为从程序被创建就提供了一个静态类,除非关闭程序
但是在多线程模式下就会出现线程安全问题
public class SingleTonSync {
private static SingleTonSync singleTonSync = null;
//构造方法私有
private SingleTonSync(){}
public static SingleTonSync getSingleTonSync(){
if (singleTonSync == null){
//加锁操作
synchronized (SingleTonSync.class){
if (singleTonSync == null){
singleTonSync = new SingleTonSync();
}
}
}
return singleTonSync;
}
}
当singleTonSync对象是空时进行加锁操作,来保证线程的安全
在编译器,cpu进行编译时有可能对指令进行重排序,导致尚未初始化的示例
什么意思呢?
创建对象的正常顺序:1分配空间,2初始化,3引用赋值
被重排序以后:1分配空间,3引用赋值,2初始化
假入有两个线程 T1,T2
T1首次创建对象被重排序以后,T2有可能在对象引用赋值之后,初始化之前访问,此时singleTonSync不为空 T2线程就直接返回singleTonSync对象,但是由于没初始化就会发生空指针等异常 那么如何来处理呢?
可以通过volatile关键字修饰,对于volatile修饰的字段,可以防止指令重排序
//加入volatile 对于volatile修饰的字段,可以防止指令重排序
private volatile static SingleTonSync singleTonSync = null;
//构造方法私有
private SingleTonSync(){}
public static SingleTonSync getSingleTonSync(){
if (singleTonSync == null){
//加锁操作
synchronized (SingleTonSync.class){
if (singleTonSync == null){
singleTonSync = new SingleTonSync();
}
}
}
return singleTonSync;
}
class SingleTon{
private static SingleTon singleTon = new SingleTon();
private SingleTon(){}
public static SingleTon getSingleTon(){
return singleTon;
}
}
通过jvm的类加载机制来保证单例
class SingleTonStaticTest{
//静态内部类
private static class SingleTon{
private static SingleTonStaticTest singleTonStaticTest = new SingleTonStaticTest();
}
private SingleTonStaticTest(){}
public static SingleTonStaticTest getInstance(){
return SingleTon.singleTonStaticTest;
}
}
本质上是利用类的加载机制来保证线程的安全
只有在实际使用时才会触发类的初始化,所以也是懒加载的一种
通过反射来创建类会破坏单例
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
//通过反射来创建单例对象
Constructor<SingleTonStaticTest> sin = SingleTonStaticTest.class.getDeclaredConstructor();
sin.setAccessible(true);
SingleTonStaticTest singleTonStaticTest = sin.newInstance();
SingleTonStaticTest instance = SingleTonStaticTest.getInstance();
System.out.println(singleTonStaticTest == instance);
}
这里的两个对象就不是单例对象
那么怎么来解决呢?
懒汉模式是不能解决的,懒汉模式的单例对象应当避免使用反射的方式创建
饿汉模式和静态内部类可以通过异常处理解决
class SingleTonStaticTest{
//静态内部类
private static class SingleTon{
private static SingleTonStaticTest singleTonStaticTest = new SingleTonStaticTest();
}
private SingleTonStaticTest(){
//解决反射创建单例对象的问题
if (SingleTon.singleTonStaticTest!=null){
throw new RuntimeException("单例模式不允许创建多个对象");
}
}
public static SingleTonStaticTest getInstance(){
return SingleTon.singleTonStaticTest;
}
}
那么是不是所有的类都能通过反射创建呢?
查看newInstance的源码发现
if ((clazz.getModifiers() & Modifier.ENUM) != 0)
throw new IllegalArgumentException("Cannot reflectively create enum objects");
当类型为枚举时是会抛出一个异常的
/**
* 枚举类型单例
*/
public enum SingleTonEnmu {
INSTANCE;
}
话不多说看代码
public class SingleTonStatic {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, IOException, ClassNotFoundException {
//获取到单例对象
SingleTonStaticTest instance = SingleTonStaticTest.getInstance();
//序列化对象
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("test"));
oos.writeObject(instance);
oos.close();
//反序列化对象
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("test"));
SingleTonStaticTest singleTonStaticTest = ((SingleTonStaticTest) ois.readObject());
//输出false
System.out.println(instance == singleTonStaticTest);
}
}
class SingleTonStaticTest implements Serializable {
//静态内部类
private static class SingleTon{
private static SingleTonStaticTest singleTonStaticTest = new SingleTonStaticTest();
}
private SingleTonStaticTest(){
//解决反射创建单例对象的问题
if (SingleTon.singleTonStaticTest!=null){
throw new RuntimeException("单例模式不允许创建多个对象");
}
}
public static SingleTonStaticTest getInstance(){
return SingleTon.singleTonStaticTest;
}
}
反序列化创建对象字节流中获取数据不会通过类的构造函数
那么如何解决?
查看Serializable接口的源码
* Classes that need to designate a replacement when an instance of it
* is read from the stream should implement this special method with the
* exact signature.
*
* <PRE>
* ANY-ACCESS-MODIFIER Object readResolve() throws ObjectStreamException;
* </PRE><p>
提供一个readResolve方法
public class SingleTonStatic {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, IOException, ClassNotFoundException {
//获取到单例对象
SingleTonStaticTest instance = SingleTonStaticTest.getInstance();
//序列化对象
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("test"));
oos.writeObject(instance);
oos.close();
//反序列化对象
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("test"));
SingleTonStaticTest singleTonStaticTest = ((SingleTonStaticTest) ois.readObject());
//输出true
System.out.println(instance == singleTonStaticTest);
}
}
}
class SingleTonStaticTest implements Serializable {
//序列化版本号
static final long serialVersionUID = 42L;
//静态内部类
private static class SingleTon{
private static SingleTonStaticTest singleTonStaticTest = new SingleTonStaticTest();
}
private SingleTonStaticTest(){
//解决反射创建单例对象的问题
if (SingleTon.singleTonStaticTest!=null){
throw new RuntimeException("单例模式不允许创建多个对象");
}
}
public static SingleTonStaticTest getInstance(){
return SingleTon.singleTonStaticTest;
}
//返回自己的类
Object readResolve() throws ObjectStreamException{
return SingleTon.singleTonStaticTest;
}
}
刚刚提到了单例对象可以通过反序列化被破坏,那么枚举类型会怎样呢?
查看反序列枚举类型的实现
查看readObject方法源码
//拿到对应的枚举类型
String name = readString(false);
Enum<?> result = null;
Class<?> cl = desc.forClass();
if (cl != null) {
try {
@SuppressWarnings("unchecked")
//获取对应的实例
Enum<?> en = Enum.valueOf((Class)cl, name);
result = en;
} catch (IllegalArgumentException ex) {
throw (IOException) new InvalidObjectException(
"enum constant " + name + " does not exist in " +
cl).initCause(ex);
}
if (!unshared) {
handles.setObject(enumHandle, result);
}
}
通过源码我们可以看到valueof方法是一个静态方法
public static <T extends Enum<T>> T valueOf(Class<T> enumType,
String name) {
T result = enumType.enumConstantDirectory().get(name);
if (result != null)
return result;
if (name == null)
throw new NullPointerException("Name is null");
throw new IllegalArgumentException(
"No enum constant " + enumType.getCanonicalName() + "." + name);
}
综上枚举类型的单例对象对反序列有着天然的优势,换句话讲 通过反序列化的方式不会破坏枚举类型的单例对象
java.lang.Runtime
public class Runtime {
private static Runtime currentRuntime = new Runtime();
/**
* Returns the runtime object associated with the current Java application.
* Most of the methods of class <code>Runtime</code> are instance
* methods and must be invoked with respect to the current runtime object.
*
* @return the <code>Runtime</code> object associated with the current
* Java application.
*/
public static Runtime getRuntime() {
return currentRuntime;
}
/** Don't let anyone else instantiate this class */
private Runtime() {}
}
饿汉模式在jdk中的实现
// 构造函数
function Person(){
this.name = '张三'
}
// 单例核心代码
let instance = null
function singleTon(){
if(!instance) instance = new Person()
return instance
}
//使用闭包对单例核心代码进行改造
const singleTon = ( function(){
// 构造函数
function Person(){
this.name = '张三'
}
let instance = null
return function singleTon(){
if(!instance) instance = new Person()
return instance
}
})()