23种设计模式之单例模式
Posted 暴躁的程序猿啊
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了23种设计模式之单例模式相关的知识,希望对你有一定的参考价值。
单例模式
这里小编学习单例时参考了狂神的设计模式视频 链接在这里 设计模式
这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
单例模式实现之一 饿汉模式
/**
* 饿汉式单例
* @create: 2021/6/18
* @author: Tony Stark
*/
public class Hungry {
/**
* 私有构造器可以保证只有一个对象
*/
private Hungry(){
}
private final static Hungry HUNGRY=new Hungry();
public static Hungry getInstance(){
return HUNGRY;
}
}
饿汉模式的弊端就是加载的时候就创建 浪费资源 所以才有了懒汉式
单例模式实现之二 懒汉模式
懒汉模式 用的时候才加载
/**
* 懒汉式单例
* @create: 2021/6/18
* @author: Tony Stark
*/
public class Lazy {
/**
* 构造器私有
*/
private Lazy(){
System.out.println(Thread.currentThread().getName()+"启动!");
}
private static Lazy lazy;
public static Lazy getInstance(){
if (lazyMan==null){
lazyMan = new LazyMan();
}
return lazy;
}
//单线程下但是是没问题的
}
但是懒汉模式多线程下就失效了
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(()->{
Lazy.getInstance();
}).start();
}
}
多线程下会出现问题 单例模式失效
所以我们要加锁 保证单例 DCL懒汉式 双重检查锁
/**
* 懒汉式单例
* @create: 2021/6/18
* @author: Tony Stark
*/
public class Lazy {
/**
* 构造器私有
*/
private Lazy(){
System.out.println(Thread.currentThread().getName()+"启动!");
}
private static Lazy lazy;
/**
* 双重检测锁模式的懒汉单例模式 简称DCL懒汉式
* @return
*/
public static Lazy getInstance(){
//解决多线程问题 加锁 可能加锁之前被两个线程拿到 所以我们要进行两次检测
if (lazy==null){
synchronized(Lazy.class){
if (lazy==null){
lazy = new Lazy();
}
}
}
return lazy;
}
//单线程下但是是没问题的
/**
*多线程并发
*/
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(()->{
Lazy.getInstance();
}).start();
}
}
}
测试
虽然这样就可以保证是单例的了 但是还有一个问题
lazyMan = new LazyMan();
不是原子性操作
new 的时候会有三个步骤 :
1.分配内存空间执行构造方法
2.初始化对象
3.把这个对象指向这个空间
虽然看起来 new 是一步操作 实际上它会进行以上三步操作 就会发生一个指令重排
原来可能是 123 的顺序执行 重排后可能会变为 231
即是当一个线程执行1之后执行了3步骤 先把对象指向了开辟的空间 但还未初始化对象
这时另一条线程 进行判断时 对象不为空 直接返回对象 但对象还未初始化 所以会出错
为了防止指令重排 我们要保证lazyMan的操作
加入volatile
private volatile static LazyMan lazyMan;
/**
* 懒汉式单例
* @create: 2021/6/18
* @author: Tony Stark
*/
public class Lazy {
/**
* 构造器私有
*/
private Lazy(){
System.out.println(Thread.currentThread().getName()+"启动!");
}
private volatile static Lazy lazy;
/**
* 双重检测锁模式的懒汉单例模式 简称DCL懒汉式
* @return
*/
public static Lazy getInstance(){
//解决多线程问题 加锁 可能加锁之前被两个线程拿到 所以我们要进行两次检测
if (lazy==null){
synchronized(Lazy.class){
if (lazy==null){
lazy = new Lazy();
}
}
}
return lazy;
}
//单线程下但是是没问题的
/**
*多线程并发
*/
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(()->{
Lazy.getInstance();
}).start();
}
}
}
单例模式实现之三 内部类实现模式
/**
* 静态内部类
* @create: 2021/6/18
* @author: Tony Stark
*/
public class External {
private External (){
}
public static External getInstance(){
return InnerClass.EXTERNAL;
}
public static class InnerClass{
private static final External EXTERNAL=new External ();
}
public static void main(String[] args) {
External instance = External .getInstance();
External instance1 = External .getInstance();
System.out.println(instance==instance1);
}
}
安全问题
但以上方式都是不安全的
Java中的反射可以破解 获取到对象
我们在上述代码中加入反射获取对象
import java.lang.reflect.Constructor;
/**
* @create: 2021/6/19
* @author: Tony Stark
*/
public class Lazy {
private Lazy(){
}
private volatile static Lazy LAZY;
public static Lazy getInstance(){
if (LAZY==null){
synchronized(Lazy.class){
if (LAZY==null){
LAZY=new Lazy();
}
}
}
return LAZY;
};
public static void main(String[] args) throws Exception{
//获取实例
Lazy lazy = Lazy.getInstance();
//反射获取实例
Class<Lazy> lazyClass = Lazy.class;
Constructor<Lazy> constructor = lazyClass.getDeclaredConstructor(null);
//跳过安全检查
constructor.setAccessible(true);
//获取实例对象
Lazy lazy1 = constructor.newInstance();
//判断反射获取实例和正常获取的实例是否是同一个对象
System.out.println(lazy==lazy1);
}
}
测试结果
单例被反射破坏了
解决方法
反射通过我们的无参构造器获取对象 我们可以再无参构造器中加入判断
判断已经有了对象之后就不可以再创建对象了
private Lazy(){
synchronized(Lazy.class){
if (LAZY!=null){
throw new RuntimeException("不要用反射破坏单例");
}
}
}
测试
但是此时又有了新问题
因为刚刚我们第一个对象是用getInstance new 出来的 所以有了一个对象 就无法反射了
如果我们都用反射创建呢 ?
public static void main(String[] args) throws Exception{
//获取实例
// Lazy lazy = Lazy.getInstance();
//反射获取实例
Class<Lazy> lazyClass = Lazy.class;
Constructor<Lazy> constructor = lazyClass.getDeclaredConstructor(null);
//跳过安全检查
constructor.setAccessible(true);
//获取实例对象
Lazy lazy1 = constructor.newInstance();
Lazy lazy2 = constructor.newInstance();
//判断反射获取实例和正常获取的实例是否是同一个对象
System.out.println(lazy2==lazy1);
}
结果是反射又被破坏了
此时我们需要加入一个信号位 来判断
public class Lazy {
//加入一个信号位
private static boolean feifei=false;
private Lazy(){
synchronized(Lazy.class){
if(feifei==false){
//更改信号位
feifei=true;
}else{
throw new RuntimeException("不要用反射破坏单例");
}
}
}
private volatile static Lazy LAZY;
public static Lazy getInstance(){
if (LAZY==null){
synchronized(Lazy.class){
if (LAZY==null){
LAZY=new Lazy();
}
}
}
return LAZY;
};
public static void main(String[] args) throws Exception{
//获取实例
// Lazy lazy = Lazy.getInstance();
//反射获取实例
Class<Lazy> lazyClass = Lazy.class;
Constructor<Lazy> constructor = lazyClass.getDeclaredConstructor(null);
//跳过安全检查
constructor.setAccessible(true);
//获取实例对象
Lazy lazy1 = constructor.newInstance();
Lazy lazy2 = constructor.newInstance();
//判断反射获取实例和正常获取的实例是否是同一个对象
System.out.println(lazy2==lazy1);
}
}
效果
成功解决的 反射问题
思考
这种方式还有什么弊端?
以上是关于23种设计模式之单例模式的主要内容,如果未能解决你的问题,请参考以下文章