文章目錄
餓漢單例模式
簡單餓漢單例模式
public class HungrySingleton {
private static final HungrySingleton hungrySington = new HungrySingleton();
private HungrySingleton() {
}
public static HungrySingleton getInstance(){
return hungrySington;
}
}
靜態代碼塊實現餓漢模式
public class HungryStaticSingleton {
private static final HungryStaticSingleton hungrySington;
static {
hungrySington = new HungryStaticSingleton();
}
private HungryStaticSingleton() {
}
public static HungryStaticSingleton getInstance() {
return hungrySington;
}
}
懶漢單例模式
簡單懶漢單例模式(線程不安全)
public class LazySimplySington {
private static LazySimplySington lazy;
private LazySimplySington() {
}
//jdk1.8之後對synchronized性能優化不少
//不可避免還是存在一定的性能問題
public synchronized static LazySimplySington getInstance() {
if (lazy == null) {
lazy = new LazySimplySington();
}
return lazy;
}
}
雙重檢查實現單例模式(線程安全)
注意指令重排序問題,需要單獨加voliate關鍵字
public class LazyDoubleCheckSington {
private volatile static LazyDoubleCheckSington lazy = null;
private LazyDoubleCheckSington() {
}
//jdk1.8之後對synchronized性能優化不少
//不可避免還是存在一定的性能問題
public static LazyDoubleCheckSington getInstance() {
if (lazy == null) {
synchronized (LazyDoubleCheckSington.class) {
if (lazy == null) {
lazy = new LazyDoubleCheckSington();
//指令重排序的問題:也就是 第二步和第三步會顛倒,
// 解決方式 變量上加voliate,讓線程可見
//CPU執行時候會轉換成JVM指令執行
//1.分配內存給這個對象
//2.初始化對象
//3.將初始化好的對象和內存地址建立關聯,賦值
//4.用戶初次始化
}
}
}
return lazy;
}
}
內部類實現單例模式(線程安全)
public class LazyInnerClassSington {
//雖然構造方法有了,但是逃不過反射的法眼
private LazyInnerClassSington() {
}
//懶漢式單例
//LazyHolder裏面的邏輯需要等到外部方法調用時才執行
//巧妙利用了內部類的特性
//JVM底層執行邏輯,完美的避免了線程安全問題
public static final LazyInnerClassSington getInstance(){
return LazyHolder.lazy;
}
private static class LazyHolder{
private static final LazyInnerClassSington lazy = new LazyInnerClassSington();
}
}
暴力破解單例
反射暴力破解
public class LazyInnerClassSingtonTest {
public static void main(String[] args) {
try {
//反射,破壞了單例
Class<?> clazz = LazyInnerClassSington.class;
Constructor<?> c = clazz.getDeclaredConstructor(null);
c.setAccessible(true);
Object o = c.newInstance();
Object o2 = c.newInstance();
System.out.println(o == o2);
}catch (Exception e){
e.printStackTrace();
}
}
}
解決方式:
在構造函數內部添加判斷
//雖然構造方法有了,但是逃不過反射的法眼
private LazyInnerClassSington() {
if (LazyHolder.lazy != null){
throw new RuntimeException("不允許構建多個實例");
}
}
測試:
完整代碼:
public class LazyInnerClassSington {
//雖然構造方法有了,但是逃不過反射的法眼
private LazyInnerClassSington() {
if (LazyHolder.lazy != null){
throw new RuntimeException("不允許構建多個實例");
}
}
//懶漢式單例
//LazyHolder裏面的邏輯需要等到外部方法調用時才執行
//巧妙利用了內部類的特性
//JVM底層執行邏輯,完美的避免了線程安全問題
public static final LazyInnerClassSington getInstance(){
return LazyHolder.lazy;
}
private static class LazyHolder{
private static final LazyInnerClassSington lazy = new LazyInnerClassSington();
}
}
序列化和反序列化暴力破解
來個餓漢單例
//反序列化時導致單例破壞
public class SeriableSingleton implements Serializable {
//序列化就是說把內存中的狀態通過轉換成字節碼的形式
//從而轉換一個IO流,寫入到其他地方(可以是磁盤、網絡IO)
//內存中狀態給永久保存下來了
//反序列化
//講已經持久化的字節碼內容,轉換爲IO流
//通過IO流的讀取,進而將讀取的內容轉換爲Java對象
//在轉換過程中會重新創建對象new
public final static SeriableSingleton INSTANCE = new SeriableSingleton();
private SeriableSingleton(){}
public static SeriableSingleton getInstance(){
return INSTANCE;
}
破解
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();
}
}
}
解決方式resolve方法
加個方法:
//序列化解決單例問題的方式
//重寫readResolve方法,只不過是覆蓋了反序列化出來的對象
//還是創建了兩次,發生在JVM層面,相對來說比較安全
//之前反序列化出來的對象會被GC回收
private Object readResolve(){
return INSTANCE;
}
完整代碼:
//反序列化時導致單例破壞
public class SeriableSingleton implements Serializable {
//序列化就是說把內存中的狀態通過轉換成字節碼的形式
//從而轉換一個IO流,寫入到其他地方(可以是磁盤、網絡IO)
//內存中狀態給永久保存下來了
//反序列化
//將已經持久化的字節碼內容,轉換爲IO流
//通過IO流的讀取,進而將讀取的內容轉換爲Java對象
//在轉換過程中會重新創建對象new
public final static SeriableSingleton INSTANCE = new SeriableSingleton();
private SeriableSingleton(){}
public static SeriableSingleton getInstance(){
return INSTANCE;
}
//序列化解決單例問題的方式
//重寫readResolve方法,只不過是覆蓋了反序列化出來的對象
//還是創建了兩次,發生在JVM層面,相對來說比較安全
//之前反序列化出來的對象會被GC回收
private Object readResolve(){
return INSTANCE;
}
}
註冊類單例
枚舉實現單例(推薦)
可以同時實現防止反射和序列化反序列化暴力破解
public enum EnumSingleton {
INSTANCE;
private String name;
public String getData() {
return name;
}
public void setData(String data) {
this.name = data;
}
public static EnumSingleton getInstance(){
return INSTANCE;
}
}
使用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.example.sington.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/example/sington/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
});
}
}
容器實現單例
public class ContainerSingleton {
private ContainerSingleton() {
}
private static Map<String, Object> ioc = new ConcurrentHashMap<>();
public static Object getBean(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 ioc.get(className);
}
}
}
public class Pojo {
}
public class ContainerSingletonTest {
public static void main(String[] args) {
try {
long start = System.currentTimeMillis();
ConcurrentExecutor.execute(new ConcurrentExecutor.RunHandler() {
public void handler() {
Object obj = ContainerSingleton.getBean("com.example.Pojo");
System.out.println(System.currentTimeMillis() + ": " + obj);
}
}, 10,6);
long end = System.currentTimeMillis();
System.out.println("總耗時:" + (end - start) + " ms.");
}catch (Exception e){
e.printStackTrace();
}
}
}
線程間實現單例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();
}
}
public class ExectorThread implements Runnable {
@Override
public void run() {
// LazySimplySington instance = LazySimplySington.getInstance();
// LazyDoubleCheckSington instance = LazyDoubleCheckSington.getInstance();
ThreadLocalSingleton instance = ThreadLocalSingleton.getInstance();
System.out.println(Thread.currentThread().getName()+" ---"+instance);
ThreadLocalSingleton instance2 = ThreadLocalSingleton.getInstance();
System.out.println(Thread.currentThread().getName()+" ---"+instance2);
ThreadLocalSingleton instance3 = ThreadLocalSingleton.getInstance();
System.out.println(Thread.currentThread().getName()+" ---"+instance3);
ThreadLocalSingleton instance4 = ThreadLocalSingleton.getInstance();
System.out.println(Thread.currentThread().getName()+" ---"+instance4);
ThreadLocalSingleton instance5 = ThreadLocalSingleton.getInstance();
System.out.println(Thread.currentThread().getName()+" ---"+instance5);
System.out.println(Thread.currentThread().getName()+" ---"+instance);
}
}
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");
}
}
可以看到線程間是單例的。
單例模式的本質
本質: 控制實例數目。(研磨設計模式)
。
筆記和代碼地址
代碼:
https://github.com/hufanglei/pattern-learn/tree/master/src/main/java/com/example/sington
筆記https://blog.csdn.net/baidu_21349635/article/details/106067581
微信訂閱號:搜索: 怒放de每一天
個人微信公衆號:
搜索: 怒放de每一天
不定時推送相關文章,期待和大家一起成長!!
完