单例模式
单例模式使用场景
什么是单例模式?保障一个类只能有一个对象(实例)的代码开发模式就叫单例模式
什么时候使用? 工具类!(一种做法,所有的方法都是static,还有一种单例模式让工具类只有一个实例) 某类工厂(SqlSessionFactory)
实现方式
1. 饿汉
/**
* 饿汉模式(迫切加载)
*/
public class Singleton01 {
//构造私有化
private Singleton01(){
}
//2 创建一个private对象
private static final Singleton01 INSTANCE = new Singleton01();//这个地方就体现饿汉,一来就创建对象给他
//3 提供一个static方法来获取你的这个单实例对象
public static Singleton01 newInstance(){
return INSTANCE;
}
}
测试代码
public class MyTest {
public static void main(String[] args) {
Singleton01 singleton01 = Singleton01.newInstance();
Singleton01 singleton02 = Singleton01.newInstance();
System.out.println(singleton01.equals(singleton02));
}
}
输出为true说明两个对象是相同的
2. 懒汉(懒汉太懒了,要用的时候才能创建。)
/**
* 懒汉模式(懒加载) 单线程
*/
public class Singleton02 {
private Singleton02() {
}
//2 创建一个private对象
private static Singleton02 INSTANCE;
//3 提供一个static方法来获取你的这个单实例对象 只适合在单线程中
public static Singleton02 newInstance() {
if(INSTANCE == null){
INSTANCE = new Singleton02()
}
return INSTANCE;
}
}
测试代码
public class MyTest {
public static void main(String[] args) {
Singleton02 singleton03 = Singleton02.newInstance();
Singleton02 singleton04 = Singleton04.newInstance();
System.out.println(singleton03.equals(singleton04));
}
}
输出为true说明两个对象是相同的
懒汉模式 只要不调用newInstance()方法 INSTANCE就一直为空 只用调用了才会创建
测试代码(如果多线程 就不是单例了)
public class MyTest {
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(() -> Singleton02.newInstance()).start();
}
}
}
运行结果
说明有不同的线程都调用到了构造方法
解决方法:加锁
1.方法上加锁
public static synchronized Singleton02 newInstance()
缺点:锁住整个方法 里面还有业务逻辑 效率降低很多
2.synchronized代码块
/**
* 懒汉模式(懒加载)
*/
public class Singleton02 {
private Singleton02() {
}
//2 创建一个private对象
private static Singleton02 INSTANCE;
//3 提供一个static方法来获取你的这个单实例对象
//方案1:方法锁,里面还有业务逻辑
//public static synchronized Singleton02 newInstance(){
public static Singleton02 newInstance() {
//方案2:锁代码块
//synchronized (Singleton02.class){ // 100个线程哪怕有一个已经创建了也要排队
if (INSTANCE == null) {
//多线程来了? T1 T2
synchronized (Singleton02.class) {
if (INSTANCE == null) {
INSTANCE = new Singleton02();
}
}
}
//}
//此处有10W行代码....
return INSTANCE;
}
//普通方法
public String getConnection() {
return "I am connection!";
}
}
只会有一个线程调用到构造方法
写到这里是不是觉得单例模式已经可以了?
但是这个代码也不是绝对安全的,不绝对是单例 利用反射去创建类的对象可以将单例进行破坏 写个测试代码
public class MyTest {
public static void main(String[] args) throws Exception{
//正常获取
Singleton02 instance = Singleton02.newInstance();
//通过反射获取
Class<? extends Singleton02> aClass = instance.getClass();
//注意:构造方法是私有的
Constructor<? extends Singleton02> declaredConstructor = aClass.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
Singleton02 instance02 = declaredConstructor.newInstance();
System.out.println(instance);
System.out.println(instance02);
}
}
生成了两个对象,说明已经通过反射将单例破坏掉了
修改代码
/**
* 懒汉模式(懒加载)
*/
public class Singleton02 {
private Singleton02() {
synchronized (Singleton02.class) {
if (INSTANCE != null){
throw new RuntimeException("防止反射破坏单例");
}
}
}
//2 创建一个private对象
private static Singleton02 INSTANCE;
//3 提供一个static方法来获取你的这个单实例对象
//方案1:方法锁,里面还有业务逻辑
//public static synchronized Singleton02 newInstance(){
public static Singleton02 newInstance() {
//方案2:锁代码块
//synchronized (Singleton02.class){ // 100个线程哪怕有一个已经创建了也要排队
if (INSTANCE == null) {
//多线程来了? T1 T2
synchronized (Singleton02.class) {
if (INSTANCE == null) {
INSTANCE = new Singleton02();
}
}
}
//}
//此处有10W行代码....
return INSTANCE;
}
//普通方法
public String getConnection() {
return "I am connection!";
}
}
那到这一步反射就不能搞破坏了吗?
答案是可以的 为什么?
因为第一次创建的时候使用的是正常的方式肯定会调用构造方法
将第一个打印放到反射创建之前就会打印出第一个的对象 第二次通过反射再拿一个对象就不行 如果我两次都使用反射来获取对象
public class MyTest {
public static void main(String[] args) throws Exception{
Class<? extends Singleton02> aClass = Singleton02.class;
//注意:构造方法是私有的
Constructor<? extends Singleton02> declaredConstructor = aClass.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
Singleton02 instance01 = declaredConstructor.newInstance();
Singleton02 instance02 = declaredConstructor.newInstance();
System.out.println(instance01);
System.out.println(instance02);
}
}
ok 单例又被破坏
还有没有其他方法?
不管是反射还是正常都会调用构造方法 那就先搞一个字段flag来进行校验
package org.jhy._01singleton;
/**
* 懒汉模式(懒加载)
*/
public class Singleton02 {
private static Boolean flag = false;
private Singleton02() {
// synchronized (Singleton02.class) {
// if (INSTANCE != null){
// throw new RuntimeException("防止反射破坏单例");
// }
// }
if (flag == false) {
flag = true;
} else {
throw new RuntimeException("防止反射破坏单例");
}
}
//2 创建一个private对象
private static Singleton02 INSTANCE;
//3 提供一个static方法来获取你的这个单实例对象
//方案1:方法锁,里面还有业务逻辑
//public static synchronized Singleton02 newInstance(){
public static Singleton02 newInstance() {
//方案2:锁代码块
//synchronized (Singleton02.class){ // 100个线程哪怕有一个已经创建了也要排队
if (INSTANCE == null) {
//多线程来了? T1 T2
synchronized (Singleton02.class) {
if (INSTANCE == null) {
INSTANCE = new Singleton02();
}
}
}
//}
//此处有10W行代码....
return INSTANCE;
}
//普通方法
public String getConnection() {
return "I am connection!";
}
}
public class MyTest {
public static void main(String[] args) throws Exception{
//通过反射获取
Class<? extends Singleton02> aClass = Singleton02.class;
//注意:构造方法是私有的
Constructor<? extends Singleton02> declaredConstructor = aClass.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
Singleton02 instance01 = declaredConstructor.newInstance();
//获取字段名
Field flag = aClass.getDeclaredField("flag");
flag.setAccessible(true);
//获取之后复位 认为又是第一次
flag.set(instance01,false);
Singleton02 instance02 = declaredConstructor.newInstance();
System.out.println(instance01);
System.out.println(instance02);
}
}
ok,单例又被破坏了
那么反射真的就无所不能了吗???
让我们来看看newInstance()这个方法的源码
不能通过反射创建枚举的对象,所以用枚举就能防止反射破坏单例
public enum Singleton03 {
INSTANCE;
public void testMethod(){
System.out.println("执行了单例类的方法");
}
}
// Test.java
class Test {
public static void main(String[] args) {
//演示如何使用枚举写法的单例类
Singleton03.INSTANCE.testMethod();
System.out.println(Singleton03.INSTANCE);
Singleton03 instance01 = Singleton03.INSTANCE;
Singleton03 instance02 = Singleton03.INSTANCE;
System.out.println(instance01.equals(instance02));
}
}
结果显然为true 而且枚举类里面就不能用反射的方法 枚举里面只有一个实例那就是单例