我们也是遵循演进的一个方式,一点点体会他们的不同,以及优缺点,单例模式也是创建型模式,我们在这个包下创建
一个包,我们先学习一下懒汉式单例模式
package com.learn.design.pattern.creational.singleton;
/**
*
* @author Leon.Sun
*
*/
public class LazySingleton {
/**
* 首先我们声明一个静态的单例的一个对象
* 懒汉式可以理解说他比较懒
* 在初始化的时候呢
* 是没有创建的
* 而是做一个延迟加载
* 为了不让外部来进行new
* 这两个点都比较好理解
*
*
*/
private static LazySingleton lazySingleton = null;
private LazySingleton(){
if(lazySingleton != null){
throw new RuntimeException("单例构造器禁止反射调用");
}
}
/**
* 我们写一个获取LazySingleton的一个方法
* 他呢肯定是public的
* getInstance
* 这里面很简单
*
* 第一种方式我们在getInstance方法上
* 加上synchronized这个关键字
* 是这个方法变成同步方法
* 如果这个锁加载静态方法上
* 相当于锁的是LazySingleton这个类的clas文件
* 如果这个不是静态方法呢
* 相当于锁是在堆内存中生成的对象
* 这里要注意一下
* 也就是说在静态方法中
* 我们加了synchronized这个关键字
* 来锁这个方法的时候
* 相当于锁了这个类
* 那我们换一种写法
*
* 这个时候Thread0进入这个方法
* 我们再切入到Thread1上
* 可以看到断点并没有往下跳
* 同时这个线程的状态变成Monitor
* 那我们可以认为Thread1现在是阻塞状态
* 这个是一个监视锁
* 我们可以看到下边有一个提示
* 这个提示很清晰
* Thead1被线程0给阻塞了
* 下面有一个蓝色的按钮
* 如果点的话
* 就会继续线程0
* 所以Thread1进不了getInstance的方法
* 我们再切回Thread0
* 单步走返回了
* 然后准备输出了
* 我们再切回Thread1
* 我们可以看到Thread1是Running状态
* F6单步
* 这个时候我们看一下
* 因为在if(lazySingleton == null)的时候
* Thread0已经new完了
* LazySingleton并不等于空
* 他们两返回的是同一个对象
* 并且在LazySingleton里面
* 这个时候只生产了一个实例
* F8直接过
* 看一下console
* 这两个拿到的也是同一个对象
* 通过这种同步的方式
* 我们解决了懒汉式在多线程可能引起的问题
* 那我们也知道synchronized比较消耗资源
* 这里面有一个加锁和解锁的一个开销
* 而且synchronized修饰static方法的时候
* 锁的是这个class
* 这个锁的范围也是非常大的
* 对性能也会有一定的影响
* 那我们还有没有一种方式继续演进
* 在性能和安全性方面取得平衡
* 答案是有的
* 那我们现在继续演进我们的懒汉式
*
*
*
* @return
*/
public synchronized static LazySingleton getInstance(){
// public static LazySingleton getInstance(){
/**
* 刚刚写静态锁的这种写法和现在这种写法是一样的
* synchronized (LazySingleton.class)这里锁的也是LazySingleton
* 那么再复原成同步方法
* public synchronized static LazySingleton getInstance()
* 就是这个样子的
*
*
*/
// synchronized (LazySingleton.class) {
/**
* 做一个空判断
* 如果lazySingleton为null的话
* 给他赋值
* new一个LazySingleton
*
* 最开始的时候lazySingleton为null
* 因为判断lazySingleton == null结果为true
* 进入到lazySingleton = new LazySingleton();
* 进入之后还没有执行new
* 这个时候执行lazySingleton = new LazySingleton();
* 这个单例就有值了
*
* 我们单步来到if(lazySingleton == null)
* debug来调试多线程的节奏
* 来触发这种写法在多线程中的问题
* 那之前run的时候没有发生这种问题呢
* 那我们看到的是表象没有发生
* 但是实际有没有发生其实是不确定的
* 另外一种就是实际就没有发生
* 但是对于发生了但是我们没有看到
* 这种情况只要我们接着往下看
* 肯定就理解了
* 那另外一个他没有发生
* 这个呢也很好理解
* 他就是没有发生
* 我说的发生是多线程的问题
* 也就是这个单例模式是否只new出来一个对象
* 那如果我们没有用断点干预
* 直接run的话
* 和CPU分配的时间有关
* 是有一定概率的
* 我们现在程序比较简单
* 如果我们程序复杂一些
* 这种隐患还是有可能发生的
* 既然有隐患我们就要消除掉他
* 那现在我们继续来看一下他
* 把这个隐患找出来
* 一定要掌握多线程debug
* 讲TOMCAT集群的时候呢
* 操作了都进程debug
* 现在在设计模式的课程中
* 我们在写多线程debug
* 这个技能非常重要
* 一定要学会
*
* 因为Thread0并没有赋值上
* 所以他还是为空
* F6单步
* 他也进来了
* 那我再切回Thread0
*
* 我们切换到Thread0线程上
* 为null进来
*
* 单步Thread1也进来了
* 现在对于Thread1我直接让他返回
* 已经输出了
* 那我们再切回Thread0上
* 这个时候Thread1是431
* 但是lazySingleton = new LazySingleton()一旦完成
* 那这个对象就变了
* 已经变成了432
* 这个时候他就返回
* 然后输出
* 我们再看一下console
* 那这个时候我们就可以看到
* Thread1拿到的42D这个对象
* Thread0是拿到420这个对象
* 所以呢我们不能被表面所迷惑
* 例如我们直接run的时候
* 看到的对象是同一个
* 但是在这中间获取的可能不止一个对象
* 所以呢这个是有一定概率的
* 例如在lazySingleton里面
* 如果第一个线程执行特别快
* 先new上了
* 那第二个线程判断为null的时候
* 就会返回false
* 然后直接return
* 所以呢
* 具体这个返回值什么样子
* 都是有一定概率的
* 那这个隐患我们是一定要消除的
* 怎么消除呢
* 很简单
* 对于懒汉式这种方式呢
* 首先我们来到lazySingleton里边
*
*
*/
if(lazySingleton == null){
/**
* 第一个线程到这里的时候并且没有执行这一行
* 第二个线程达到if(lazySingleton == null)这里
* 那if(lazySingleton == null)这一行判断
* 因为lazySingleton = new LazySingleton();这里还没有new
* 那么if(lazySingleton == null)判断的结果是true
* 所以第二个线程也会进入到lazySingleton = new LazySingleton();
* 那这个对象就new了两次
* 同时会返回最后执行的lazySingleton
* 那我们怎么验证呢
* 我们写一个测试类
*
* 懒汉式注重的是延迟加载
*
* 这个时候lazySingleton还是null
* 因为lazySingleton赋值new LazySingleton()
* 这一行还没有执行完
* 所以他并没有被复制上
* 这个时候我们切到Thread1上
* Thread1单步
*
* 可以看到在lazySingleton = new LazySingleton()还没有执行的时候
* lazySingleton是有值的
* 但是我们马上就要执行它了
* F6单步
* 现在可以看到lazySingleton值变了
* 也就是说在我们写的懒汉式的单例模式中
* lazySingleton在多线程的模式中
* 生成了不止一个实例
* 那现在是两个线程
* 如果是多个线程呢
* 所以在第一次初始化的时候
* 有可能创建很多个的单例对象
* 如果这个单例类的对象特别消耗资源
* 那很有可能造成系统故障
* 这个呢是非常有风险的
* 那在我们这个例子中lazySingleton并没有特别消耗资源的地方
* 但是场景是一样的
* 那我们现在再切换到Thread0上
* lazySingleton这个对象已经被Thread1重新赋值了
* 那现在F8过
* 主动跳到主线程
* 我们看一下console
* 这个时候我们可以看到
* Thread0和Thread1拿到的还是同一个对象
*
* 现在Thread0在lazySingleton = new LazySingleton();这一行上
* 我们再切换到Thread1上
*
*
*/
lazySingleton = new LazySingleton();
}
// }
/**
* 把这个对象返回
* 把这个对象返回回去
* 这种方式是线程不安全的
* 我们看一看代码
* 在单线程的时候
* 这种模式这种写法
* 是OK的
* 但是一旦多线程来使用这个单例的话
* 假设我们现在两个线程
*
* 这个时候Thread0已经把lazySingleton赋值上了
* 这个时候我们在切到Thread1上
*
*
*/
return lazySingleton;
}
// public static void main(String[] args) throws Exception {
// Class objectClass = LazySingleton.class;
// Constructor c = objectClass.getDeclaredConstructor();
// c.setAccessible(true);
//
/**
* 通过LazySingleton这个类调用getInstance方法
* 因为它是private构造器
* 所以在外部是new不到他的
* 然后我们直接getInstance
*
* 这样简单一个单线程获取的单例呢就完成了
* 只有使用它的时候才初始化
* 如果不使用就初始化LazySingleton对象
* 那main本身是一个线程
* 现在我们在这个线程中再创建两个线程
* 去获取现在这种写法的单例的时候
* 会碰到什么问题呢
* 我们一起来看一下
* 首先我们写一下线程的类
*
*
*
*/
// LazySingleton o1 = LazySingleton.getInstance();
// System.out.println("Program end.....");
//
// Field flag = o1.getClass().getDeclaredField("flag");
// flag.setAccessible(true);
// flag.set(o1,true);
//
//
// LazySingleton o2 = (LazySingleton) c.newInstance();
//
// System.out.println(o1);
// System.out.println(o2);
// System.out.println(o1==o2);
// }
}
package com.learn.design.pattern.creational.singleton;
/**
*
* @author Leon.Sun
*
*/
public class LazySingleton {
/**
* 首先我们声明一个静态的单例的一个对象
* 懒汉式可以理解说他比较懒
* 在初始化的时候呢
* 是没有创建的
* 而是做一个延迟加载
* 为了不让外部来进行new
* 这两个点都比较好理解
*
*
*/
private static LazySingleton lazySingleton = null;
private LazySingleton(){
if(lazySingleton != null){
throw new RuntimeException("单例构造器禁止反射调用");
}
}
/**
* 我们写一个获取LazySingleton的一个方法
* 他呢肯定是public的
* getInstance
* 这里面很简单
*
* 第一种方式我们在getInstance方法上
* 加上synchronized这个关键字
* 是这个方法变成同步方法
* 如果这个锁加载静态方法上
* 相当于锁的是LazySingleton这个类的clas文件
* 如果这个不是静态方法呢
* 相当于锁是在堆内存中生成的对象
* 这里要注意一下
* 也就是说在静态方法中
* 我们加了synchronized这个关键字
* 来锁这个方法的时候
* 相当于锁了这个类
* 那我们换一种写法
*
* 这个时候Thread0进入这个方法
* 我们再切入到Thread1上
* 可以看到断点并没有往下跳
* 同时这个线程的状态变成Monitor
* 那我们可以认为Thread1现在是阻塞状态
* 这个是一个监视锁
* 我们可以看到下边有一个提示
* 这个提示很清晰
* Thead1被线程0给阻塞了
* 下面有一个蓝色的按钮
* 如果点的话
* 就会继续线程0
* 所以Thread1进不了getInstance的方法
* 我们再切回Thread0
* 单步走返回了
* 然后准备输出了
* 我们再切回Thread1
* 我们可以看到Thread1是Running状态
* F6单步
* 这个时候我们看一下
* 因为在if(lazySingleton == null)的时候
* Thread0已经new完了
* LazySingleton并不等于空
* 他们两返回的是同一个对象
* 并且在LazySingleton里面
* 这个时候只生产了一个实例
* F8直接过
* 看一下console
* 这两个拿到的也是同一个对象
* 通过这种同步的方式
* 我们解决了懒汉式在多线程可能引起的问题
* 那我们也知道synchronized比较消耗资源
* 这里面有一个加锁和解锁的一个开销
* 而且synchronized修饰static方法的时候
* 锁的是这个class
* 这个锁的范围也是非常大的
* 对性能也会有一定的影响
* 那我们还有没有一种方式继续演进
* 在性能和安全性方面取得平衡
* 答案是有的
* 那我们现在继续演进我们的懒汉式
*
*
*
* @return
*/
public synchronized static LazySingleton getInstance(){
// public static LazySingleton getInstance(){
/**
* 刚刚写静态锁的这种写法和现在这种写法是一样的
* synchronized (LazySingleton.class)这里锁的也是LazySingleton
* 那么再复原成同步方法
* public synchronized static LazySingleton getInstance()
* 就是这个样子的
*
*
*/
// synchronized (LazySingleton.class) {
/**
* 做一个空判断
* 如果lazySingleton为null的话
* 给他赋值
* new一个LazySingleton
*
* 最开始的时候lazySingleton为null
* 因为判断lazySingleton == null结果为true
* 进入到lazySingleton = new LazySingleton();
* 进入之后还没有执行new
* 这个时候执行lazySingleton = new LazySingleton();
* 这个单例就有值了
*
* 我们单步来到if(lazySingleton == null)
* debug来调试多线程的节奏
* 来触发这种写法在多线程中的问题
* 那之前run的时候没有发生这种问题呢
* 那我们看到的是表象没有发生
* 但是实际有没有发生其实是不确定的
* 另外一种就是实际就没有发生
* 但是对于发生了但是我们没有看到
* 这种情况只要我们接着往下看
* 肯定就理解了
* 那另外一个他没有发生
* 这个呢也很好理解
* 他就是没有发生
* 我说的发生是多线程的问题
* 也就是这个单例模式是否只new出来一个对象
* 那如果我们没有用断点干预
* 直接run的话
* 和CPU分配的时间有关
* 是有一定概率的
* 我们现在程序比较简单
* 如果我们程序复杂一些
* 这种隐患还是有可能发生的
* 既然有隐患我们就要消除掉他
* 那现在我们继续来看一下他
* 把这个隐患找出来
* 一定要掌握多线程debug
* 讲TOMCAT集群的时候呢
* 操作了都进程debug
* 现在在设计模式的课程中
* 我们在写多线程debug
* 这个技能非常重要
* 一定要学会
*
* 因为Thread0并没有赋值上
* 所以他还是为空
* F6单步
* 他也进来了
* 那我再切回Thread0
*
* 我们切换到Thread0线程上
* 为null进来
*
* 单步Thread1也进来了
* 现在对于Thread1我直接让他返回
* 已经输出了
* 那我们再切回Thread0上
* 这个时候Thread1是431
* 但是lazySingleton = new LazySingleton()一旦完成
* 那这个对象就变了
* 已经变成了432
* 这个时候他就返回
* 然后输出
* 我们再看一下console
* 那这个时候我们就可以看到
* Thread1拿到的42D这个对象
* Thread0是拿到420这个对象
* 所以呢我们不能被表面所迷惑
* 例如我们直接run的时候
* 看到的对象是同一个
* 但是在这中间获取的可能不止一个对象
* 所以呢这个是有一定概率的
* 例如在lazySingleton里面
* 如果第一个线程执行特别快
* 先new上了
* 那第二个线程判断为null的时候
* 就会返回false
* 然后直接return
* 所以呢
* 具体这个返回值什么样子
* 都是有一定概率的
* 那这个隐患我们是一定要消除的
* 怎么消除呢
* 很简单
* 对于懒汉式这种方式呢
* 首先我们来到lazySingleton里边
*
*
*/
if(lazySingleton == null){
/**
* 第一个线程到这里的时候并且没有执行这一行
* 第二个线程达到if(lazySingleton == null)这里
* 那if(lazySingleton == null)这一行判断
* 因为lazySingleton = new LazySingleton();这里还没有new
* 那么if(lazySingleton == null)判断的结果是true
* 所以第二个线程也会进入到lazySingleton = new LazySingleton();
* 那这个对象就new了两次
* 同时会返回最后执行的lazySingleton
* 那我们怎么验证呢
* 我们写一个测试类
*
* 懒汉式注重的是延迟加载
*
* 这个时候lazySingleton还是null
* 因为lazySingleton赋值new LazySingleton()
* 这一行还没有执行完
* 所以他并没有被复制上
* 这个时候我们切到Thread1上
* Thread1单步
*
* 可以看到在lazySingleton = new LazySingleton()还没有执行的时候
* lazySingleton是有值的
* 但是我们马上就要执行它了
* F6单步
* 现在可以看到lazySingleton值变了
* 也就是说在我们写的懒汉式的单例模式中
* lazySingleton在多线程的模式中
* 生成了不止一个实例
* 那现在是两个线程
* 如果是多个线程呢
* 所以在第一次初始化的时候
* 有可能创建很多个的单例对象
* 如果这个单例类的对象特别消耗资源
* 那很有可能造成系统故障
* 这个呢是非常有风险的
* 那在我们这个例子中lazySingleton并没有特别消耗资源的地方
* 但是场景是一样的
* 那我们现在再切换到Thread0上
* lazySingleton这个对象已经被Thread1重新赋值了
* 那现在F8过
* 主动跳到主线程
* 我们看一下console
* 这个时候我们可以看到
* Thread0和Thread1拿到的还是同一个对象
*
* 现在Thread0在lazySingleton = new LazySingleton();这一行上
* 我们再切换到Thread1上
*
*
*/
lazySingleton = new LazySingleton();
}
// }
/**
* 把这个对象返回
* 把这个对象返回回去
* 这种方式是线程不安全的
* 我们看一看代码
* 在单线程的时候
* 这种模式这种写法
* 是OK的
* 但是一旦多线程来使用这个单例的话
* 假设我们现在两个线程
*
* 这个时候Thread0已经把lazySingleton赋值上了
* 这个时候我们在切到Thread1上
*
*
*/
return lazySingleton;
}
// public static void main(String[] args) throws Exception {
// Class objectClass = LazySingleton.class;
// Constructor c = objectClass.getDeclaredConstructor();
// c.setAccessible(true);
//
/**
* 通过LazySingleton这个类调用getInstance方法
* 因为它是private构造器
* 所以在外部是new不到他的
* 然后我们直接getInstance
*
* 这样简单一个单线程获取的单例呢就完成了
* 只有使用它的时候才初始化
* 如果不使用就初始化LazySingleton对象
* 那main本身是一个线程
* 现在我们在这个线程中再创建两个线程
* 去获取现在这种写法的单例的时候
* 会碰到什么问题呢
* 我们一起来看一下
* 首先我们写一下线程的类
*
*
*
*/
// LazySingleton o1 = LazySingleton.getInstance();
// System.out.println("Program end.....");
//
// Field flag = o1.getClass().getDeclaredField("flag");
// flag.setAccessible(true);
// flag.set(o1,true);
//
//
// LazySingleton o2 = (LazySingleton) c.newInstance();
//
// System.out.println(o1);
// System.out.println(o2);
// System.out.println(o1==o2);
// }
}
package com.learn.design.pattern.creational.singleton;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
/**
* 本身Main是一个主线程
* 从上至下而执行
* 那执行到t1.start();的时候呢
* 就开启了两个线程
* 一会我们在执行的时候其实是三个线程在执行
* 主线程还有t1和t2
* 那断点呢我们也打上了
* 现在运行debug
*
* 但是刚刚通过debug我们已经知道了
* 他们返回同一个对象
* 是因为最后的线程重新赋值了
* 并且在重新赋值之后
* 两个线程才进行return的
* 所以我们在console里面看到的是同一个对象
* 把我们再debug操作一下
* 让他们返回不同的对象
*
*
* @author Leon.Sun
*
*/
public class Test {
public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
// LazySingleton lazySingleton = LazySingleton.getInstance();
// System.out.println("main thread"+ThreadLocalInstance.getInstance());
// System.out.println("main thread"+ThreadLocalInstance.getInstance());
// System.out.println("main thread"+ThreadLocalInstance.getInstance());
// System.out.println("main thread"+ThreadLocalInstance.getInstance());
// System.out.println("main thread"+ThreadLocalInstance.getInstance());
// System.out.println("main thread"+ThreadLocalInstance.getInstance());
/**
* new一个t1线程
*
*/
Thread t1 = new Thread(new T());
/**
* 再new一个t2线程
*
*/
Thread t2 = new Thread(new T());
/**
* 我们可以看到Thread1和Thread0拿到的是同一个对象
* 那这个呢是run的情况下
* 如果我们debug进行干预的话
*
*
*/
t1.start();
t2.start();
/**
* 现在这个线程是主线程的
* 我们关心的是Main Thread0 Thread1
* 那现在这个三个线程的状态都是Running
* 那我们现在通过Frame切换到Thread0上
* 可以看到Thread0调用getInstance方法了
*
*
*/
System.out.println("program end");
// HungrySingleton instance = HungrySingleton.getInstance();
// EnumInstance instance = EnumInstance.getInstance();
// instance.setData(new Object());
//
// ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("singleton_file"));
// oos.writeObject(instance);
//
// File file = new File("singleton_file");
// ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
//
HungrySingleton newInstance = (HungrySingleton) ois.readObject();
// EnumInstance newInstance = (EnumInstance) ois.readObject();
//
// System.out.println(instance.getData());
// System.out.println(newInstance.getData());
// System.out.println(instance.getData() == newInstance.getData());
// Class objectClass = HungrySingleton.class;
// Class objectClass = StaticInnerClassSingleton.class;
// Class objectClass = LazySingleton.class;
// Class objectClass = EnumInstance.class;
// Constructor constructor = objectClass.getDeclaredConstructor(String.class,int.class);
//
// constructor.setAccessible(true);
// EnumInstance instance = (EnumInstance) constructor.newInstance("Geely",666);
//
// LazySingleton newInstance = (LazySingleton) constructor.newInstance();
// LazySingleton instance = LazySingleton.getInstance();
// StaticInnerClassSingleton instance = StaticInnerClassSingleton.getInstance();
// StaticInnerClassSingleton newInstance = (StaticInnerClassSingleton) constructor.newInstance();
// HungrySingleton newInstance = (HungrySingleton) constructor.newInstance();
// HungrySingleton instance = HungrySingleton.getInstance();
// System.out.println(instance);
// System.out.println(newInstance);
// System.out.println(instance == newInstance);
// EnumInstance instance = EnumInstance.getInstance();
// instance.printTest();
}
}