文章目录
- 3.单例模式
- 3.2.2.1 案例一
- 3.2.2.2 案例二 线程安全问题演示
- 3.2.2.3 解决线程问题-优化1[方法上加锁]
- 3.2.2.3 解决线程问题-优化2[方法内对象双重加锁]
- 3.2.2.4 静态内部类实例化[懒汉式]
- 单例模式总结
3.单例模式
3.1.饿汉式单例模式
1.在单例类第一次加载的时候就创建实例;不管用不用,先加载创建.
静态方法实例化
package com.gaoxinfu.demo.pattern.singleton.hungry;
public class HungrySingleton {
//先静态、后动态
//先属性、后方法
//先上后下
private static final HungrySingleton hungrySingleton=new HungrySingleton();
private HungrySingleton() {
// TODO Auto-generated constructor stub
}
public static HungrySingleton getInstance() {
return hungrySingleton;
}
}
}
静态块实例化
package com.gaoxinfu.demo.pattern.singleton.hungry;
public class HungryStaticSingleton {
private static final HungryStaticSingleton hungrySingleton;
//静态块
static{
hungrySingleton=new HungryStaticSingleton();
}
private HungryStaticSingleton() {
// TODO Auto-generated constructor stub
}
public static HungryStaticSingleton getInstance() {
return hungrySingleton;
}
}
3.1.2.优点
//优点:没有加任何的锁、执行效率比较高,
//在用户体验上来说,比懒汉式更好
//绝对线程安全,在线程还没出现以前就是实例化了,不可能存在访问安全问题
3.1.2.缺点
,有点浪费内存空间资源
所以只能小范围使用
为了改造上面问题,出现懒汉式单例
3.2.懒汉式单例模式
3.2.1.概述
1.被外部类第一次调用时,才会初始化话创建;
3.2.2.案例说明
3.2.2.1 案例一
package com.gaoxinfu.demo.pattern.singleton.lazy;
/**
*
* @Description:TODO
* @Author:gaoxinfu
* @Time:2019年3月10日 上午9:08:26
*/
//懒汉式单例
//在外部需要使用的时候才进行实例化
public class LazySimpleSingleton {
private LazySimpleSingleton(){}
//静态块,公共内存区域
//不能用final 声明,因为使用final,getInstance 方法 无法赋值
private static LazySimpleSingleton lazy = null;
public static LazySimpleSingleton getInstance(){
if(lazy == null){
lazy = new LazySimpleSingleton();
}
return lazy;
}
}
测试
package com.gaoxinfu.demo.pattern.singleton.lazy;
public class LazySimpleSingletonTest {
public static void main(String[] args) {
LazySimpleSingleton lazySimpleSingleton=LazySimpleSingleton.getInstance();
System.out.println(lazySimpleSingleton);
}
}
但是上面存在线程安全的问题,详细见案例二测试演示
3.2.2.2 案例二 线程安全问题演示
复制上面的 3.2.2.1
package com.gaoxinfu.demo.pattern.singleton.lazy;
/**
*
* @Description:TODO
* @Author:gaoxinfu
* @Time:2019年3月10日 上午9:08:26
*/
//懒汉式单例
//在外部需要使用的时候才进行实例化
public class LazySimpleSingleton {
private LazySimpleSingleton(){}
//静态块,公共内存区域
//不能用final 声明,因为使用final,getInstance 方法 无法赋值
private static LazySimpleSingleton lazy = null;
public static LazySimpleSingleton getInstance(){
if(lazy == null){
lazy = new LazySimpleSingleton();
}
return lazy;
}
}
package com.gaoxinfu.demo.pattern.singleton.lazy;
/**
*
* @Description:TODO
* @Author:gaoxinfu
* @Time:2019年3月10日 上午9:19:39
*/
public class ExectorThread implements Runnable{
@Override
public void run() {
LazySimpleSingleton singleton = LazySimpleSingleton.getInstance();
System.out.println(Thread.currentThread().getName() + ":" + singleton);
}
}
测试
package com.gaoxinfu.demo.pattern.singleton.lazy;
public class LazySimpleSingletonThreadTest {
public static void main(String[] args) {
Thread t1 = new Thread(new ExectorThread());
Thread t2 = new Thread(new ExectorThread());
t1.start();
t2.start();
System.out.println("End");
}
}
演示如下
解释
1.LazySimpleSingleton类中getInstance方法中加一个断点;
2.我们启动了两个断点.Thread0 和Thread1 线程,可以针对断点进行调试,如上,
选中,Thread0 ,然后进行一步一步的调试;然后调试Thread1
3.完成之后,我们会发现两个线程实例化的对象不一致;
很明显,上面的线程的问题,实例化出多个对象,不符合我们单例模式;
解决上面的问题,见 3.2.2.3
3.2.2.3 解决线程问题-优化1[方法上加锁]
package com.gaoxinfu.demo.pattern.singleton.lazy;
/**
*
* @Description:TODO
* @Author:gaoxinfu
* @Time:2019年3月10日 上午9:08:26
*/
//懒汉式单例
//在外部需要使用的时候才进行实例化
public class LazySimpleSingleton {
private LazySimpleSingleton(){}
//静态块,公共内存区域
//不能用final 声明,因为使用final,getInstance 方法 无法赋值
private static LazySimpleSingleton lazy = null;
//在实例化方法上添加synchronized 锁,保证只能一个线程进来
//但是这样的容易导致这个类被锁,所以,进一步优化,见3.2.2.4
public synchronized static LazySimpleSingleton getInstance(){
if(lazy == null){
lazy = new LazySimpleSingleton();
}
return lazy;
}
}
3.2.2.3 解决线程问题-优化2[方法内对象双重加锁]
package com.gaoxinfu.demo.pattern.singleton.lazy;
public class LazyDoubleCheckSingleton {
private volatile static LazyDoubleCheckSingleton lazy = null;
private LazyDoubleCheckSingleton(){}
public static LazyDoubleCheckSingleton getInstance(){
//一次验证
if(lazy == null){
//对象加锁实例化
synchronized (LazyDoubleCheckSingleton.class){
if(lazy == null){
lazy = new LazyDoubleCheckSingleton();
//1.分配内存给这个对象
//2.初始化对象
//3.设置lazy指向刚分配的内存地址
//4.初次访问对象
}
}
}
return lazy;
}
}
第二步和第三方有可能会调换 这个就是由于指令排序的问题,volatile问题
这个可以暂时忽略 后面分布式会讲解
双重加锁
3.2.2.4 静态内部类实例化[懒汉式]
package com.gaoxinfu.demo.pattern.singleton.lazy;
public class LazyInnerClassSingleton {
private LazyInnerClassSingleton(){
}
//每一个关键字都不是多余的
//static 是为了使单例的空间共享
//final 保证这个方法不会被重写,重载
public static final LazyInnerClassSingleton getInstance(){
//在返回结果以前,一定会先加载内部类
return LazyHolder.LAZY;
}
//内部类 默认不加载
private static class LazyHolder{
private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton();
}
}
备注
加载类的时候,优先加载内部类,加载内部类的时候,优先加载内部类的逻辑,整好实例化了final static 的对象
这个是性能最优的懒汉式写法
正常的调用LazyInnerClassSingleton.getInstance 是没有什么问题
但是如果通过反射,构造方法调用 还是存在着反射问题,如下:
package com.gupaoedu.vip.pattern.singleton.test;
import java.lang.reflect.Constructor;
import com.gupaoedu.vip.pattern.singleton.lazy.LazyInnerClassSingleton;
public class LazyInnerClassSingletonTest {
public static void main(String[] args) {
try{
//很无聊的情况下,进行破坏
Class<?> clazz = LazyInnerClassSingleton.class;
//通过反射拿到私有的构造方法
Constructor c = clazz.getDeclaredConstructor();
c.setAccessible(true); //强制访问,对所有属性设置访问权限 当类中的成员变量为private时 必须设置此项
//暴力初始化
Object o1 = c.newInstance();
//调用了两次构造方法,相当于new了两次
//犯了原则性问题,
Object o2 = c.newInstance();
System.out.println(" o1 = " +o1);
System.out.println(" o2 = " +o2);
System.out.println(o1 == o2);
// Object o2 = c.newInstance();
}catch (Exception e){
e.printStackTrace();
}
}
}
结果如下,初始化了两个实例
为了防止这种通过反射去实例化对象的问题,我们改一下
package com.gaoxinfu.demo.pattern.singleton.lazy;
public class LazyInnerClassSingleton {
private LazyInnerClassSingleton(){
if(LazyHolder.LAZY != null){
throw new RuntimeException("不允许创建多个实例");
}
}
//每一个关键字都不是多余的
//static 是为了使单例的空间共享
//final 保证这个方法不会被重写,重载
public static final LazyInnerClassSingleton getInstance(){
//在返回结果以前,一定会先加载内部类/
return LazyHolder.LAZY;
}
//默认不加载
private static class LazyHolder{
private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton();
}
}
再次测试,则会报不允许实例化两次
3.3.注册式单例模式
概念
1.将每个实例缓存到统一的容器中,使用标识符获取实例;
3.3.1.枚举式单例模式
案例
package com.gaoxinfu.demo.pattern.singleton.register;
//常量中去使用,常量不就是用来大家都能够共用吗?
//通常在通用API中使用
public enum EnumSingleton {
INSTANCE;
private Object data;
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public static EnumSingleton getInstance(){
return INSTANCE;
}
}
利用jad反编译EnumSingleton.class文件后如下: EnumSingleton.jad
// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.kpdus.com/jad.html
// Decompiler options: packimports(3)
// Source File Name: EnumSingleton.java
package com.gaoxinfu.demo.singleton.register;
public final class EnumSingleton extends Enum
{
public static EnumSingleton[] values()
{
return (EnumSingleton[])$VALUES.clone();
}
public static EnumSingleton valueOf(String name)
{
return (EnumSingleton)Enum.valueOf(com/gaoxinfu/demo/singleton/register/EnumSingleton, name);
}
private EnumSingleton(String s, int i)
{
super(s, i);
}
public Object getData()
{
return data;
}
public void setData(Object data)
{
this.data = data;
}
public static EnumSingleton getInstance()
{
return INSTANCE;
}
public static final EnumSingleton INSTANCE;
private Object data;
private static final EnumSingleton $VALUES[];
static
{
INSTANCE = new EnumSingleton("INSTANCE", 0);
$VALUES = (new EnumSingleton[] {
INSTANCE
});
}
}
package com.gaoxinfu.demo.pattern.singleton.register;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class EnumSingletonTest {
public static void main(String[] args) {
try {
EnumSingleton instance1 = null;
EnumSingleton instance2 = EnumSingleton.getInstance();
instance2.setData(new Object());
FileOutputStream fos = new FileOutputStream("EnumSingleton.obj");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(instance2);
oos.flush();
oos.close();
FileInputStream fis = new FileInputStream("EnumSingleton.obj");
ObjectInputStream ois = new ObjectInputStream(fis);
instance1 = (EnumSingleton) ois.readObject();
ois.close();
System.out.println(instance1.getData());
System.out.println(instance2.getData());
System.out.println(instance1.getData() == instance2.getData());
}catch (Exception e){
e.printStackTrace();
}
}
}
我们通过看一下源码来佐证一下枚举式单例,如何调用
Enum<?> en = Enum.valueOf((Class)cl, name);
//通过(Class)cl, name参数确定一个枚举值
// 这行我们可以看出,只能一个,所以单例的
我们再来验证一下错误
package com.gaoxinfu.demo.pattern.singleton.register;
import java.lang.reflect.Constructor;
public class EnumSingletonTest {
public static void main(String[] args) {
try {
Class clazz = EnumSingleton.class;
Constructor c = clazz.getDeclaredConstructor(String.class,int.class);
c.setAccessible(true);
EnumSingleton enumSingleton = (EnumSingleton)c.newInstance("Tom",666);
}catch (Exception e){
e.printStackTrace();
}
}
}
我们看一下源码
3.3.2.容器式单例模式
package com.gaoxinfu.demo.pattern.singleton.register;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
//Spring中的做法,就是用这种注册式单例
public class ContainerSingleton {
private ContainerSingleton(){}
private static Map<String,Object> ioc = new ConcurrentHashMap<String,Object>();
public static Object getInstance(String className){
synchronized (ioc) {
if (!ioc.containsKey(className)) {
Object obj = null;
try {
obj = Class.forName(className).newInstance();
ioc.put(className, obj);
} catch (Exception e) {
e.printStackTrace();
}
return obj;
} else {
return ioc.get(className);
}
}
}
}
3.4.ThreadLocal单例模式
案例
package com.gaoxinfu.demo.pattern.singleton.threadlocal;
public class ThreadLocalSingleton {
private static final ThreadLocal<ThreadLocalSingleton> threadLocalInstance =
new ThreadLocal<ThreadLocalSingleton>(){
@Override
protected ThreadLocalSingleton initialValue() {
return new ThreadLocalSingleton();
}
};
private ThreadLocalSingleton(){}
public static ThreadLocalSingleton getInstance(){
return threadLocalInstance.get();
}
}
测试
package com.gaoxinfu.demo.pattern.singleton.threadlocal;
import com.gaoxinfu.demo.pattern.singleton.threadlocal.ThreadLocalSingleton ;
public class ExectorThread implements Runnable{
@Override
public void run() {
ThreadLocalSingleton singleton = ThreadLocalSingleton.getInstance();
System.out.println(Thread.currentThread().getName() + ":" + singleton);
}
}
package com.gaoxinfu.demo.pattern.singleton.threadlocal;
import com.gaoxinfu.demo.pattern.singleton.threadlocal.ThreadLocalSingleton ;
/**
* Created by Tom.
*/
public class ThreadLocalSingletonTest {
public static void main(String[] args) {
System.out.println(ThreadLocalSingleton.getInstance());
System.out.println(ThreadLocalSingleton.getInstance());
System.out.println(ThreadLocalSingleton.getInstance());
System.out.println(ThreadLocalSingleton.getInstance());
System.out.println(ThreadLocalSingleton.getInstance());
Thread t1 = new Thread(new ExectorThread());
Thread t2 = new Thread(new ExectorThread());
t1.start();
t2.start();
System.out.println("End");
}
}
不同的子进程,实例化不一样,典型的单例模式
关于ThreadLocal的介绍见
https://blog.csdn.net/u014636209/article/details/88571917
1.在内存中只有一个实例,减少了内存的开销;
2.可以避免对资源的多重应用
3.设置全局访问点,严格控制了访问
3.5.序列化单例模式
3.5.1.序列化概念
(可以是磁盘、网络IO)
内存中状态给永久保存下来了
反序列化
讲已经持久化的字节码内容,转换为IO流
通过IO流的读取,进而将读取的内容转换为Java对象
在转换过程中会重新创建对象new
3.5.2.案例1
3.5.2.1. 序列化对象
import java.io.Serializable;
//反序列化时导致单例破坏
public class SeriableSingleton implements Serializable {
public final static SeriableSingleton INSTANCE = new SeriableSingleton();
private SeriableSingleton(){
}
public static SeriableSingleton getInstance(){
return INSTANCE;
}
}
3.5.2.2. 测试1
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class SeriableSingletonTest {
public static void main(String[] args) {
SeriableSingleton s1 = null;
SeriableSingleton s2 = SeriableSingleton.getInstance();
FileOutputStream fos = null;
try {
//输出一个对象属性
fos = new FileOutputStream("SeriableSingleton.obj");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(s2);
oos.flush();
oos.close();
//读取刚才写入的对象
FileInputStream fis = new FileInputStream("SeriableSingleton.obj");
ObjectInputStream ois = new ObjectInputStream(fis);
s1 = (SeriableSingleton)ois.readObject();
ois.close();
System.out.println(s1);
System.out.println(s2);
System.out.println(s1 == s2);
} catch (Exception e) {
e.printStackTrace();
}
}
}
3.5.2.3 输出结果
3.5.2.4 结果分析[不一致原因]
关键问题在于 s1 = (SeriableSingleton)ois.readObject();
详见下面动态图解释
描述
readObject()方法
readObject0方法
readOrdinaryObject 读取二进制文件
boolean isInstantiable() {
requireInitialized();
return (cons != null);
}
//其中的变量cons声明 是否有构造方法,显然我们SeriableSingleton有构造方法
因此
= desc.isInstantiable() ? desc.newInstance() : null;
因此会实例化出来新的对象;
boolean hasReadResolveMethod() {
requireInitialized();
return (readResolveMethod != null);
}
因为刚才我们测试的时候,并未自定义readResolve方法,所以会两个对象不一致;
通过反射初始化readResolveMethod添加如下,重新即可
备注说明
1.重新发序列化方法,只是覆盖返序列化出来得对象,还是创建了2次,发生在JVM层,相对来说比较安全;;
2.之前反序列化出来得对象会被GC回收
单例模式总结
1.优点
2.缺点
1.没有接口,扩展困难
2.如果扩展单例对象,只有修改代码,没有其他途径;
3.6 源码介绍
3.6.1 Runtime
//典型的饿汉式单例
//饿汉单例的话会影响JVM的启动速度。
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() {}
...