目录
一、什么是单例模式
1. 单例模式优缺点
优点:
1、单例类只有一个实例
2、共享资源,全局使用
3、节省创建时间,提高性能
缺点:可能存在线程不安全的问题
二、单例模式应用场景
windows的任务管理器、网站的计数器、Web应用配置对象的读取、多线程的线程池
三、单例的七种写法
分别是「饿汉」、「懒汉(非线程安全)」、「懒汉(线程安全)」、「双重校验锁」、「静态内部类」、「枚举」和「容器类管理、静态块初始化」
1. 饿汉式
package com.demo.singleton.v1;
/**
* @Author: JYC
* @Title: 饿汉式
* @Description: TODO
* @Date: 2022/4/20 10:11
*/
public class SingletonV1 {
/**
* 饿汉式
* 优点:先天性线程安全,当类初始化的时候,就被创建该对象。
* 缺点:如果项目中使用过多的饿汉式会发生问题,项目在启动的时候会变得非常慢,存放在方法区占用内存比较大
* 如果用户不使用该对象的时候,也会被提前创建好
*/
private static SingletonV1 singLetonV1 = new SingletonV1();
// 1. 单例模式是否可以让程序员初始化
private SingletonV1() {
}
/**
* 返回该对象的实例
* @return
*/
public static SingletonV1 getSingLetonV1() {
return singLetonV1;
}
}
package com.demo.singleton.v1;
/**
* @Author: JYC
* @Title: SingletonV1Test
* @Description: TODO
* @Date: 2022/4/20 10:35
*/
public class SingletonV1Test {
public static void main(String[] args) {
SingletonV1 instance1 = SingletonV1.getSingLetonV1();
SingletonV1 instance2 = SingletonV1.getSingLetonV1();
System.out.println(instance1 == instance2); // 判断是否为同一个对象
}
}
1.1 优缺点
饿汉式
优点:先天性线程是安全的,当类初始化的时候就会创建该对象。
缺点:如果饿汉式使用过多,可能会影响项目启动的效率问题。
2. 懒汉式(线程不安全)
package com.demo.singleton.v2;
/**
* @Author: JYC
* @Title: 懒汉式(线程不安全)
* @Description: TODO
* @Date: 2022/4/20 10:50
*/
public class SingletonV2 {
// 懒汉式 当真正需要使用该对象的时候才会被初始化
private static SingletonV2 singletonV2;
// 1. 单例模式是否可以让程序员初始化
private SingletonV2() {
}
/**
* 线程安全问题
* 在多线程情况下,可能会被初始化多次
* @return
*/
public static SingletonV2 getSingletonV2() {
// 当第一次singletonV2 等于null情况才会被初始化
if (singletonV2 == null) {
try {
Thread.sleep(2000); //
} catch (Exception e) {
}
singletonV2 = new SingletonV2();
}
return singletonV2;
}
}
package com.demo.singleton.v2;
/**
* @Author: JYC
* @Title: 懒汉式,模拟多线程操作
* @Description: TODO
* @Date: 2022/4/20 10:54
*/
public class SingletonV2Test {
public static void main(String[] args) {
// SingletonV2 singleton1 = SingletonV2.getSingletonV2();
// SingletonV2 singleton2 = SingletonV2.getSingletonV2();
// System.out.println(singleton1 == singleton2); // 判断是否为同一个对象
// 如何去模拟高并发情况下,懒汉式线程安全问题
for (int i = 0; i < 100; i++) {
new Thread(new Runnable() {
@Override
public void run() {
SingletonV2 singleton1 = SingletonV2.getSingletonV2();
System.out.println(Thread.currentThread().getName() + "," + singleton1);
}
}).start();
}
}
}
3. 懒汉式(线程安全)
package com.demo.singleton.v2;
/**
* @Author: JYC
* @Title: 懒汉式(线程安全)
* @Description: TODO
* @Date: 2022/4/20 10:50
*/
public class SingletonV2 {
// 懒汉式 当真正需要使用该对象的时候才会被初始化
private static SingletonV2 singletonV2;
// 1. 单例模式是否可以让程序员初始化
private SingletonV2() {
}
/**
* 创建和获取实例时上锁,可以解决线程安全问题,但是执行效率非常低
* @return
*/
public synchronized static SingletonV2 getSingletonV2() {
try {
Thread.sleep(2000); //
} catch (Exception e) {
}
// 当第一次singletonV2 等于null情况才会被初始化
if (singletonV2 == null) {
singletonV2 = new SingletonV2();
}
return singletonV2;
}
}
package com.demo.singleton.v2;
/**
* @Author: JYC
* @Title: 懒汉式,模拟多线程操作
* @Description: TODO
* @Date: 2022/4/20 10:54
*/
public class SingletonV2Test {
public static void main(String[] args) {
// SingletonV2 singleton1 = SingletonV2.getSingletonV2();
// SingletonV2 singleton2 = SingletonV2.getSingletonV2();
// System.out.println(singleton1 == singleton2); // 判断是否为同一个对象
// 如何去模拟高并发情况下,懒汉式线程安全问题
for (int i = 0; i < 100; i++) {
new Thread(new Runnable() {
@Override
public void run() {
SingletonV2 singleton1 = SingletonV2.getSingletonV2();
System.out.println(Thread.currentThread().getName() + "," + singleton1);
}
}).start();
}
}
}
4. 双重检验锁(DCL)
package com.demo.singleton.v3;
/**
* @Author: JYC
* @Title: SingletonV3
* @Description: TODO
* @Date: 2022/4/20 11:33
*/
public class SingletonV3 {
private static SingletonV3 singletonV3;
// 双重检验锁,解决懒汉式读和写都加上锁的问题,缺点:第一次创建对象可能会比较慢
private SingletonV3() {
}
/**
* 读的不加锁,写的时候才会加锁
*/
public static SingletonV3 getSingletonV3() {
// 当多个线程同时在可能new对象的时候,才会加锁,保证线程安全问题
if (singletonV3 == null) {
try {
Thread.sleep(2000); //
} catch (Exception e) {
}
synchronized (SingletonV3.class) {
if (singletonV3 == null) { // 当前线程已经获取到锁了,再判断一下该对象是否已经初始化过,没有初始化过的创建
singletonV3 = new SingletonV3();
}
}
}
return singletonV3;
}
// 双重检验锁的目的时什么? 解决懒汉式获取对象效率问题。
}
package com.demo.singleton.v3;
/**
* @Author: JYC
* @Title: 模拟多线程操作
* @Description: TODO
* @Date: 2022/4/20 11:57
*/
public class SingletonV3Test {
public static void main(String[] args) {
// 如何去模拟高并发情况下,懒汉式线程安全问题
for (int i = 0; i < 100; i++) {
new Thread(new Runnable() {
@Override
public void run() {
SingletonV3 singleton1 = SingletonV3.getSingletonV3();
System.out.println(Thread.currentThread().getName() + "," + singleton1);
}
}).start();
}
}
}
5. 静态内部类形式
package com.demo.singleton.v4;
/**
* @Author: JYC
* @Title: SingletonV4
* @Description: TODO
* @Date: 2022/4/20 12:08
*/
public class SingletonV4 {
private SingletonV4() {
System.out.println("构造函数被初始化...");
}
public static SingletonV4 getInstance() {
return SingletonV4Utils.singletonV4;
}
// 在类里面嵌套的
private static class SingletonV4Utils {
private static final SingletonV4 singletonV4 = new SingletonV4();
}
// 静态内部类特征:继承懒汉式和饿汉式优点,同时解决双重检验锁第一次加载慢的问题,读和写都不需要同步,效率非常高...
public static void main(String[] args) {
System.out.println("项目启动成功...");
SingletonV4 instance1 = SingletonV4.getInstance();
SingletonV4 instance2 = SingletonV4.getInstance();
System.out.println(instance1 == instance2);
}
}
6. 枚举形式
package com.demo.singleton.v7;
/**
* @Author: JYC
* @Title: EnumSingleton
* @Description: TODO
* @Date: 2022/4/20 14:29
*/
public enum EnumSingleton {
INSTANCE;
// 枚举能够绝对有效的防止实例化多次,和防止反射和序列化破击
public void add(){
System.out.println("add方法...");
}
}
package com.demo.singleton.v7;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
/**
* @Author: JYC
* @Title: EnumSingletonTest
* @Description: TODO
* @Date: 2022/4/20 14:29
*/
public class EnumSingletonTest {
public static void main(String[] args) throws InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchMethodException {
EnumSingleton singleton1 = EnumSingleton.INSTANCE;
EnumSingleton singleton2 = EnumSingleton.INSTANCE;
System.out.println(singleton1 == singleton2);
singleton1.add();
// 通过反射破坏单例
Constructor<EnumSingleton> declaredConstructor = EnumSingleton.class.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
EnumSingleton singleton3 = declaredConstructor.newInstance();
System.out.println(singleton3 == singleton1);
}
}
7. 使用容器管理
8. 如何防止破坏单例
虽然单例通过私有构造函数,可以实现防止程序猿初始化对象,但是还可以通过反射和序列化技术破解单例。
8.1 使用反射技术破解单例
package com.demo.singleton.v5;
/**
* @Author: JYC
* @Title: SingletonV5
* @Description: TODO
* @Date: 2022/4/20 14:47
*/
public class SingletonV5 {
private static SingletonV5 singletonV5;
// 双重检验锁,解决懒汉式读和写都加上锁的问题,缺点:第一次创建对象可能会比较慢
// 如何解决读和写都不加锁,还能保证唯一性线程安全问题
private SingletonV5() {
System.out.println("SingletonV5被初始化...");
}
/**
* 读的不加锁,写的时候才会加锁
*/
public static SingletonV5 getSingletonV5() {
// 当多个线程同时在可能new对象的时候,才会加锁,保证线程安全问题
if (singletonV5 == null) {
try {
Thread.sleep(2000); //
} catch (Exception e) {
}
synchronized (SingletonV5.class) {
if (singletonV5 == null) { // 当前线程已经获取到锁了,再判断一下该对象是否已经初始化过,没有初始化过的创建
singletonV5 = new SingletonV5();
}
}
}
return singletonV5;
}
// 双重检验锁的目的时什么? 解决懒汉式获取对象效率问题。
}
package com.demo.singleton.v5;
import com.demo.singleton.v3.SingletonV3;
import java.lang.reflect.Constructor;
/**
* @Author: JYC
* @Title: SingletonV5Test
* @Description: TODO
* @Date: 2022/4/20 14:48
*/
public class SingletonV5Test {
public static void main(String[] args) throws Exception {
// 单例基本原则:保证在单个jvm中不重复创建
SingletonV5 singleton1 = SingletonV5.getSingletonV5();
// 如何去破解单例? 使用java的反射技术、序列化技术
Constructor<SingletonV5> declaredConstructor = SingletonV5.class.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
// 使用java的反射技术创建
SingletonV5 singleton2 = declaredConstructor.newInstance();
System.out.println(singleton1 == singleton2);
}
}
8.2 如何防止被反射破解
package com.demo.singleton.v5;
/**
* @Author: JYC
* @Title: SingletonV5
* @Description: TODO
* @Date: 2022/4/20 14:47
*/
public class SingletonV5 {
private static SingletonV5 singletonV5;
// 双重检验锁,解决懒汉式读和写都加上锁的问题,缺点:第一次创建对象可能会比较慢
// 如何解决读和写都不加锁,还能保证唯一性线程安全问题
private SingletonV5() throws Exception {
if (singletonV5 != null) {
throw new Exception("对象已经被初始化");
}
System.out.println("SingletonV5被初始化...");
}
/**
* 读的不加锁,写的时候才会加锁
*/
public static SingletonV5 getSingletonV5() throws Exception {
// 当多个线程同时在可能new对象的时候,才会加锁,保证线程安全问题
if (singletonV5 == null) {
try {
Thread.sleep(2000); //
} catch (Exception e) {
}
synchronized (SingletonV5.class) {
if (singletonV5 == null) { // 当前线程已经获取到锁了,再判断一下该对象是否已经初始化过,没有初始化过的创建
singletonV5 = new SingletonV5();
}
}
}
return singletonV5;
}
// 双重检验锁的目的时什么? 解决懒汉式获取对象效率问题。
}
package com.demo.singleton.v5;
import com.demo.singleton.v3.SingletonV3;
import java.lang.reflect.Constructor;
/**
* @Author: JYC
* @Title: SingletonV5Test
* @Description: TODO
* @Date: 2022/4/20 14:48
*/
public class SingletonV5Test {
public static void main(String[] args) throws Exception {
// 单例基本原则:保证在单个jvm中不重复创建
SingletonV5 singleton1 = SingletonV5.getSingletonV5();
// 如何去破解单例? 使用java的反射技术、序列化技术
Constructor<SingletonV5> declaredConstructor = SingletonV5.class.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
// 使用java的反射技术创建
SingletonV5 singleton2 = declaredConstructor.newInstance();
System.out.println(singleton1 == singleton2);
}
}
8.3 使用序列化技术破解单例
package com.demo.singleton.v6;
import java.io.*;
/**
* @Author: JYC
* @Title: SingletonV6
* @Description: TODO
* @Date: 2022/4/20 14:20
*/
public class SingletonV6 implements Serializable {
public static void main(String[] args) throws Exception {
// 从硬盘中读取到内存——反序列化
SingletonV6 singleton1 = SingletonV6.getSingLetonV6();
FileOutputStream fos = new FileOutputStream("D:\\ideaCode\\demo06\\src\\main\\resources\\Singleton.obj");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(singleton1);
oos.flush();
oos.close();
// 反序列化
FileInputStream fis = new FileInputStream("D:\\ideaCode\\demo06\\src\\main\\resources\\Singleton.obj");
ObjectInputStream ois = new ObjectInputStream(fis);
SingletonV6 singleton2 = (SingletonV6) ois.readObject();
System.out.println(singleton1 == singleton2);
}
/**
* 饿汉式
* 优点:先天性线程安全,当类初始化的时候,就被创建该对象。
* 缺点:如果项目中使用过多的饿汉式会发生问题,项目在启动的时候会变得非常慢,存放在方法区占用内存比较大
* 如果用户不使用该对象的时候,也会被提前创建好
*/
private static SingletonV6 singLetonV6 = new SingletonV6();
// 1. 单例模式是否可以让程序员初始化
private SingletonV6() {
}
/**
* 返回该对象的实例
*
* @return
*/
public static SingletonV6 getSingLetonV6() {
return singLetonV6;
}
}
8.4 如何防止被序列化技术破解
package com.demo.singleton.v6;
import java.io.*;
/**
* @Author: JYC
* @Title: SingletonV6
* @Description: TODO
* @Date: 2022/4/20 14:20
*/
public class SingletonV6 implements Serializable {
public static void main(String[] args) throws Exception {
// 从硬盘中读取到内存——反序列化
SingletonV6 singleton1 = SingletonV6.getSingLetonV6();
FileOutputStream fos = new FileOutputStream("D:\\ideaCode\\demo06\\src\\main\\resources\\Singleton.obj");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(singleton1);
oos.flush();
oos.close();
// 反序列化
FileInputStream fis = new FileInputStream("D:\\ideaCode\\demo06\\src\\main\\resources\\Singleton.obj");
ObjectInputStream ois = new ObjectInputStream(fis);
SingletonV6 singleton2 = (SingletonV6) ois.readObject();
System.out.println(singleton1 == singleton2);
}
/**
* 饿汉式
* 优点:先天性线程安全,当类初始化的时候,就被创建该对象。
* 缺点:如果项目中使用过多的饿汉式会发生问题,项目在启动的时候会变得非常慢,存放在方法区占用内存比较大
* 如果用户不使用该对象的时候,也会被提前创建好
*/
private static SingletonV6 singLetonV6 = new SingletonV6();
// 1. 单例模式是否可以让程序员初始化
private SingletonV6() {
}
/**
* 返回该对象的实例
*
* @return
*/
public static SingletonV6 getSingLetonV6() {
return singLetonV6;
}
// 返回序列化获取对象,保证为单例
public Object readResolve() {
return singLetonV6;
}
}
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/131276.html