java多線程之線程安全的單例模式
概念:
java中單例模式是一種常見的設(shè)計(jì)模式,單例模式分三種:懶漢式單例、餓漢式單例、登記式單例三種。
單例模式有一下特點(diǎn):
1、單例類只能有一個(gè)實(shí)例。
2、單例類必須自己創(chuàng)建自己的唯一實(shí)例。
3、單例類必須給所有其他對象提供這一實(shí)例。
單例模式確保某個(gè)類只有一個(gè)實(shí)例,而且自行實(shí)例化并向整個(gè)系統(tǒng)提供這個(gè)實(shí)例。在計(jì)算機(jī)系統(tǒng)中,線程池、緩存、日志對象、對話框、打印機(jī)、顯卡的驅(qū)動(dòng)程序?qū)ο蟪1辉O(shè)計(jì)成單例。這些應(yīng)用都或多或少具有資源管理器的功能。每臺(tái)計(jì)算機(jī)可以有若干個(gè)打印機(jī),但只能有一個(gè)Printer Spooler,以避免兩個(gè)打印作業(yè)同時(shí)輸出到打印機(jī)中。每臺(tái)計(jì)算機(jī)可以有若干通信端口,系統(tǒng)應(yīng)當(dāng)集中管理這些通信端口,以避免一個(gè)通信端口同時(shí)被兩個(gè)請求同時(shí)調(diào)用??傊?,選擇單例模式就是為了避免不一致狀態(tài),避免政出多頭。
這里主要詳細(xì)介紹兩種:懶漢式和餓漢式
一、立即加載/餓漢式
在調(diào)用方法前,實(shí)例就已經(jīng)被創(chuàng)建,代碼:
package com.weishiyao.learn.day8.singleton.ep1;
public class MyObject {
// 立即加載方式==惡漢模式
private static MyObject myObject = new MyObject();
private MyObject() {
}
public static MyObject getInstance() {
// 此代碼版本為立即加載
// 此版本代碼的缺點(diǎn)是不能有其他實(shí)例變量
// 因?yàn)間etInstance()方法沒有同步
// 所以有可能出現(xiàn)非線程安全的問題
return myObject;
}
}
創(chuàng)建線程類
package com.weishiyao.learn.day8.singleton.ep1;
public class MyThread extends Thread {
@Override
public void run() {
System.out.println(MyObject.getInstance().hashCode());
}
}
創(chuàng)建運(yùn)行類
package com.weishiyao.learn.day8.singleton.ep1;
public class Run {
public static void main(String[] args) {
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
MyThread t3 = new MyThread();
t1.start();
t2.start();
t3.start();
}
}
運(yùn)行結(jié)果
167772895
167772895
167772895
hashCode是同一個(gè)值,說明對象也是同一個(gè),說明實(shí)現(xiàn)了立即加載型的單利模式
二、延遲加載/懶漢式
在調(diào)用方法以后實(shí)例才會(huì)被創(chuàng)建,實(shí)現(xiàn)方案可以是將實(shí)例化放到無參構(gòu)造函數(shù)當(dāng)中,這樣只有當(dāng)調(diào)用的時(shí)候才會(huì)創(chuàng)建對象的實(shí)例,代碼:
package com.weishiyao.learn.day8.singleton.ep2;
public class MyObject {
private static MyObject myObject;
private MyObject() {
}
public static MyObject getInstance() {
// 延遲加載
if (myObject != null) {
} else {
myObject = new MyObject();
}
return myObject;
}
}
創(chuàng)建線程類
package com.weishiyao.learn.day8.singleton.ep2;
public class MyThread extends Thread {
@Override
public void run() {
System.out.println(MyObject.getInstance().hashCode());
}
}
創(chuàng)建運(yùn)行類
package com.weishiyao.learn.day8.singleton.ep2;
public class Run {
public static void main(String[] args) {
MyThread t1 = new MyThread();
t1.start();
}
}
運(yùn)行結(jié)果
167772895
這樣雖然取出了一個(gè)對象的實(shí)例,但是如果在多線程的環(huán)境中,就會(huì)出現(xiàn)多個(gè)實(shí)例的情況,這樣就不是單例模式了
運(yùn)行測試類
package com.weishiyao.learn.day8.singleton.ep2;
public class Run {
public static void main(String[] args) {
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
MyThread t3 = new MyThread();
MyThread t4 = new MyThread();
MyThread t5 = new MyThread();
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
}
}
運(yùn)行結(jié)果
980258163
1224717057
1851889404
188820504
1672864109
既然出現(xiàn)問題,就要解決問題,在懶漢模式中的多線程的解決方案,代碼:
第一種方案,最常見的,加synchronized,而synchronized可以加到不同的位置
第一種,方法鎖
package com.weishiyao.learn.day8.singleton.ep3;
public class MyObject {
private static MyObject myObject;
private MyObject() {
}
synchronized public static MyObject getInstance() {
// 延遲加載
try {
if (myObject != null) {
} else {
// 模擬在創(chuàng)建對象之前做一些準(zhǔn)備性的工作
Thread.sleep(2000);
myObject = new MyObject();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return myObject;
}
}
這種synchronized的同步方案導(dǎo)致效率過于低下,整個(gè)方法都被鎖住
第二種synchronized使用方案
package com.weishiyao.learn.day8.singleton.ep3;
public class MyObject {
private static MyObject myObject;
private MyObject() {
}
public static MyObject getInstance() {
// 延遲加載
try {
synchronized (MyObject.class) {
if (myObject != null) {
} else {
// 模擬在創(chuàng)建對象之前做一些準(zhǔn)備性的工作
Thread.sleep(2000);
myObject = new MyObject();
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return myObject;
}
}
這種方法效率一樣很低,方法內(nèi)的所有代碼都被鎖住,只需要鎖住關(guān)鍵代碼就好,第三種synchronized使用方案
package com.weishiyao.learn.day8.singleton.ep3;
public class MyObject {
private static MyObject myObject;
private MyObject() {
}
public static MyObject getInstance() {
// 延遲加載
try {
if (myObject != null) {
} else {
// 模擬在創(chuàng)建對象之前做一些準(zhǔn)備性的工作
Thread.sleep(2000);
synchronized (MyObject.class) {
myObject = new MyObject();
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return myObject;
}
}
這么寫看似是最優(yōu)方案了,但是,運(yùn)行一下結(jié)果,發(fā)現(xiàn),其實(shí)它是非線程安全的
結(jié)果:
1224717057
971173439
1851889404
1224717057
1672864109
Why?
雖然鎖住了對象創(chuàng)建的語句,每次只能有一個(gè)線程完成創(chuàng)建,但是,當(dāng)?shù)谝粋€(gè)線程進(jìn)來創(chuàng)建完成Object對象以后,第二個(gè)線程進(jìn)來還是可以繼續(xù)創(chuàng)建的,因?yàn)槲覀兙o緊只鎖住了創(chuàng)建語句,這個(gè)問題解決方案
package com.weishiyao.learn.day8.singleton.ep3;
public class MyObject {
private static MyObject myObject;
private MyObject() {
}
public static MyObject getInstance() {
// 延遲加載
try {
if (myObject != null) {
} else {
// 模擬在創(chuàng)建對象之前做一些準(zhǔn)備性的工作
Thread.sleep(2000);
synchronized (MyObject.class) {
if (myObject == null) {
myObject = new MyObject();
}
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return myObject;
}
}
只需要在鎖里面再添加一個(gè)判斷,就可以保證單例了,這個(gè)是DCL雙檢查機(jī)制
結(jié)果如下:
1224717057
1224717057
1224717057
1224717057
1224717057
三、使用內(nèi)置靜態(tài)類實(shí)現(xiàn)單例
主要代碼
package com.weishiyao.learn.day8.singleton.ep4;
public class MyObject {
// 內(nèi)部類方式
private static class MyObjectHandler {
private static MyObject myObject = new MyObject();
}
public MyObject() {
}
public static MyObject getInstance() {
return MyObjectHandler.myObject;
}
}
線程類代碼
package com.weishiyao.learn.day8.singleton.ep4;
public class MyThread extends Thread {
@Override
public void run() {
System.out.println(MyObject.getInstance().hashCode());
}
}
運(yùn)行類
package com.weishiyao.learn.day8.singleton.ep4;
public class Run {
public static void main(String[] args) {
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
MyThread t3 = new MyThread();
MyThread t4 = new MyThread();
MyThread t5 = new MyThread();
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
}
}
結(jié)果
1851889404
1851889404
1851889404
1851889404
1851889404
通過內(nèi)部靜態(tài)類,得到了線程安全的單例模式
四、序列化和反序列化單例模式
內(nèi)置靜態(tài)類可以達(dá)到線程安全的問題,但如果遇到序列化對象時(shí),使用默認(rèn)方式得到的結(jié)果還是多例的
MyObject代碼
package com.weishiyao.learn.day8.singleton.ep5;
import java.io.Serializable;
public class MyObject implements Serializable {
/**
*
*/
private static final long serialVersionUID = 888L;
// 內(nèi)部類方式
private static class MyObjectHandler {
private static MyObject myObject = new MyObject();
}
public MyObject() {
}
public static MyObject getInstance() {
return MyObjectHandler.myObject;
}
// protected MyObject readResolve() {
// System.out.println("調(diào)用了readResolve方法!");
// return MyObjectHandler.myObject;
// }
}
業(yè)務(wù)類
package com.weishiyao.learn.day8.singleton.ep5;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class SaveAndRead {
public static void main(String[] args) {
try {
MyObject myObject = MyObject.getInstance();
FileOutputStream fosRef = new FileOutputStream(new File("myObjectFile.txt"));
ObjectOutputStream oosRef = new ObjectOutputStream(fosRef);
oosRef.writeObject(myObject);
oosRef.close();
fosRef.close();
System.out.println(myObject.hashCode());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
FileInputStream fisRef;
try {
fisRef = new FileInputStream(new File("myObjectFile.txt"));
ObjectInputStream iosRef = new ObjectInputStream(fisRef);
MyObject myObject = (MyObject) iosRef.readObject();
iosRef.close();
fisRef.close();
System.out.println(myObject.hashCode());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
結(jié)果
970928725
1099149023
兩個(gè)不同的hashCode,證明并不是同一個(gè)對象,解決方案,添加下面這段代碼
protected MyObject readResolve() {
System.out.println("調(diào)用了readResolve方法!");
return MyObjectHandler.myObject;
}
在反序列化的時(shí)候調(diào)用,可以得到同一個(gè)對象
System.out.println(myObject.readResolve().hashCode());
結(jié)果
1255301379
調(diào)用了readResolve方法!
1255301379
相同的hashCode,證明得到了同一個(gè)對象
五、使用static代碼塊實(shí)現(xiàn)單例
靜態(tài)代碼塊中的代碼在使用類的時(shí)候就已經(jīng)執(zhí)行了,所以可以應(yīng)用靜態(tài)代碼快這個(gè)特性來實(shí)現(xiàn)單利模式
MyObject類
package com.weishiyao.learn.day8.singleton.ep6;
public class MyObject {
private static MyObject instance = null;
private MyObject() {
super();
}
static {
instance = new MyObject();
}
public static MyObject getInstance() {
return instance;
}
}
線程類
package com.weishiyao.learn.day8.singleton.ep6;
public class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(MyObject.getInstance().hashCode());
}
}
}
運(yùn)行類
package com.weishiyao.learn.day8.singleton.ep6;
public class Run {
public static void main(String[] args) {
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
MyThread t3 = new MyThread();
MyThread t4 = new MyThread();
MyThread t5 = new MyThread();
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
}
}
運(yùn)行結(jié)果:
1678885403
1678885403
1678885403
1678885403
1678885403
1678885403
1678885403
1678885403
1678885403
1678885403
1678885403
1678885403
1678885403
1678885403
1678885403
1678885403
1678885403
1678885403
1678885403
1678885403
1678885403
1678885403
1678885403
1678885403
1678885403
通過靜態(tài)代碼塊只執(zhí)行一次的特性也成功的得到了線程安全的單例模式
六、使用enum枚舉數(shù)據(jù)類型實(shí)現(xiàn)單例模式
枚舉enum和靜態(tài)代碼塊的特性類似,在使用枚舉時(shí),構(gòu)造方法會(huì)被自動(dòng)調(diào)用,也可以用來實(shí)現(xiàn)單例模式
MyObject類
package com.weishiyao.learn.day8.singleton.ep7;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
public enum MyObject {
connectionFactory;
private Connection connection;
private MyObject() {
try {
System.out.println("調(diào)用了MyObject的構(gòu)造");
String url = "jdbc:mysql://172.16.221.19:3306/wechat_1?useUnicode=true&characterEncoding=UTF-8";
String name = "root";
String password = "111111";
String driverName = "com.mysql.jdbc.Driver";
Class.forName(driverName);
connection = DriverManager.getConnection(url, name, password);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
}
public Connection getConnection() {
return connection;
}
}
線程類
package com.weishiyao.learn.day8.singleton.ep7;
public class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(MyObject.connectionFactory.getConnection().hashCode());
}
}
}
運(yùn)行類
package com.weishiyao.learn.day8.singleton.ep7;
public class Run {
public static void main(String[] args) {
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
MyThread t3 = new MyThread();
MyThread t4 = new MyThread();
MyThread t5 = new MyThread();
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
}
}
運(yùn)行結(jié)果
調(diào)用了MyObject的構(gòu)造
56823666
56823666
56823666
56823666
56823666
56823666
56823666
56823666
56823666
56823666
56823666
56823666
56823666
56823666
56823666
56823666
56823666
56823666
56823666
56823666
56823666
56823666
56823666
56823666
56823666
上面這種寫法將枚舉類暴露了,違反了“職責(zé)單一原則”,可以使用一個(gè)類將枚舉包裹起來
package com.weishiyao.learn.day8.singleton.ep8;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
public class MyObject {
public enum MyEnumSingleton {
connectionFactory;
private Connection connection;
private MyEnumSingleton() {
try {
System.out.println("調(diào)用了MyObject的構(gòu)造");
String url = "jdbc:mysql://172.16.221.19:3306/wechat_1?useUnicode=true&characterEncoding=UTF-8";
String name = "root";
String password = "111111";
String driverName = "com.mysql.jdbc.Driver";
Class.forName(driverName);
connection = DriverManager.getConnection(url, name, password);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
}
public Connection getConnection() {
return connection;
}
}
public static Connection getConnection() {
return MyEnumSingleton.connectionFactory.getConnection();
}
}
更改線程代碼
package com.weishiyao.learn.day8.singleton.ep8;
public class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(MyObject.getConnection().hashCode());
}
}
}
結(jié)果
調(diào)用了MyObject的構(gòu)造
1948356121
1948356121
1948356121
1948356121
1948356121
1948356121
1948356121
1948356121
1948356121
1948356121
1948356121
1948356121
1948356121
1948356121
1948356121
1948356121
1948356121
1948356121
1948356121
1948356121
1948356121
1948356121
1948356121
1948356121
1948356121
以上總結(jié)了單利模式與多線程結(jié)合時(shí)遇到的各種情況和解決方案,以供以后使用時(shí)查閱。
相關(guān)文章
java操作mysql實(shí)現(xiàn)增刪改查的方法
這篇文章主要介紹了java操作mysql實(shí)現(xiàn)增刪改查的方法,結(jié)合實(shí)例形式分析了java操作mysql數(shù)據(jù)庫進(jìn)行增刪改查的具體實(shí)現(xiàn)技巧與相關(guān)注意事項(xiàng),需要的朋友可以參考下2017-05-05
Springboot中yml文件沒有葉子圖標(biāo)的解決
這篇文章主要介紹了Springboot中yml文件沒有葉子圖標(biāo)的解決方案,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-09-09
使用Spring?Boot?2.x構(gòu)建Web服務(wù)的詳細(xì)代碼
這篇文章主要介紹了使用Spring?Boot?2.x構(gòu)建Web服務(wù)的詳細(xì)代碼,主要基于JWT的身份認(rèn)證,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-03-03
IDEA中SpringBoot項(xiàng)目的yml多環(huán)境配置方式
這篇文章主要介紹了IDEA中SpringBoot項(xiàng)目的yml多環(huán)境配置,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2023-10-10
SpringCloud中NacosNamingService的作用詳解
這篇文章主要介紹了SpringCloud中NacosNamingService的作用詳解,NacosNamingService類完成服務(wù)實(shí)例注冊,撤銷與獲取服務(wù)實(shí)例操作,NacosNamingService初始化采用單例模式,使用反射生成,需要的朋友可以參考下2023-11-11
VSCode?配置?Spring?Boot?項(xiàng)目開發(fā)環(huán)境的全過程
兩三年前曾經(jīng)試過配置Java環(huán)境, 存在不少問題作罷. 最近搜了下相關(guān)的文章, 感覺VSCode對Java項(xiàng)目的支持比三年前完善了不少. 今天實(shí)際配置了一下環(huán)境, 把自己常用的功能過了一遍, 基本能跑通開發(fā)流程, 做個(gè)筆記,需要的朋友可以參考下2024-03-03

