java單例模式和線程安全問題怎麼解決
單例模式、多實例模式、和執行緒安全性
#單例模式
單例模式是指確保一個類別僅有一個唯一的實例,並且提供了一個全域的存取點。
分類: 懶漢式、餓漢式
為什麼需要單例模式?
再某些特殊的情況下,存在一個類別僅能用來產生一個唯一物件的必要性。例如:印表機室有許多印表機,但是它的列印管理系統只有一個列印任務控制對象,該對像管理列印排隊並分配列印任務給各個印表機。單例模式正是為了解決這樣的需求而產生的。
實作想法:
為了防止客戶端利用建構器建立多個對象,將建構方法宣告為 private 類型。但這樣會使得這個類別不可用,所以必須提供一個可以獲得實例的靜態方法,通常稱為 getInstance 方法, 此方法傳回一個實例。這個方法必須是靜態的,因為靜態方法是根據類別名稱呼叫的,否則也是無法使用的。
類別圖:懶漢式
#類別圖:餓漢式
#先來看一個簡單的範例:
測試單例類別:Dog’
//懒汉式 public class Dog { private static Dog dog; private String name; private int age; //私有的构造器 private Dog() {} public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } //静态工厂方法 public static Dog getInstance() { if (dog == null) { dog = new Dog(); } return dog; } @Override public String toString() { return "Dog [name=" + name + ", age=" + age + "]"; } }
測試單例類:Cat
//饿汉式 public class Cat { private static Cat cat = new Cat(); private String name; private int age; //私有构造器 private Cat() {} public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } //静态工厂方法 public static Cat getInstance() { return cat; } @Override public String toString() { return "Cat [name=" + name + ", age=" + age + "]"; } }
測試類別
import java.util.HashSet; import java.util.Set; public class Client { public static void main(String[] args) { //单线程模式测试 Dog dog1 = Dog.getInstance(); Dog dog2 = Dog.getInstance(); System.out.println("dog1 == dog2: "+(dog1 == dog2)); Cat cat1 = Cat.getInstance(); Cat cat2 = Cat.getInstance(); System.out.println("cat1 == cat2: "+(cat1 == cat2)); } }
運行結果
創建區別
懶漢式是在第一次呼叫靜態方法getInstance() 時建立單例對象。 餓漢式是在類別載入時建立單例對象,即在宣告靜態單例對象時實例化單例類別。
執行緒安全性
懶漢式是執行緒不安全的,而餓漢式是執行緒安全的(下面會測試)。資源佔用
懶漢式是等到使用時才會創建,而餓漢式是在類別載入時創建。所以懶漢式沒有餓漢式快,但是餓漢式比較佔用資源,如果一直不使用,會很佔據資源。 多執行緒模式下的安全性多執行緒類別#
import java.util.HashSet; import java.util.Set; public class DogThread extends Thread{ private Dog dog; private Set<Dog> set; public DogThread() { set = new HashSet<>(); } //这个方法是为了测试添加的。 public int getCount() { return set.size(); } @Override public void run() { dog = Dog.getInstance(); set.add(dog); } }
多執行緒測試類別
import java.util.HashSet; import java.util.Set; public class Client { public static void main(String[] args) { //单线程模式测试 Dog dog1 = Dog.getInstance(); Dog dog2 = Dog.getInstance(); System.out.println("dog1 == dog2: "+(dog1 == dog2)); Cat cat1 = Cat.getInstance(); Cat cat2 = Cat.getInstance(); System.out.println("cat1 == cat2: "+(cat1 == cat2)); //多线程模式测试 DogThread dogThread = new DogThread(); Thread thread = null; for (int i = 0; i < 10; i++) { thread = new Thread(dogThread); thread.start(); } try { Thread.sleep(2000); //主线程等待子线程完成! } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("dog's number: "+dogThread.getCount()); } }
運行結果
注意:多執行緒的結果是很難預測的,這裡涉及執行緒的競爭,可能多次運行結果是一樣的(多次一樣不代表是絕對正確),但是只要多次測試,就能看到不一樣的結果。
說明
這裡我使用一點集合的技巧,利用Set集合的特性,把每次產生的dog 物件存入Set集合中,最後只要呼叫集合的size() 方法就行了。可以看出來產生了兩個 dog 對象,這就是產生了錯誤,這就是屬於程式錯誤了。也要明白多線程下不一定會出錯,所以產生的 dog 物件小於線程數。由於 餓漢式單例 是線程安全的,這裡就不測試了,有興趣的可以測試一下。
解決懶漢式單例執行緒安全的方法:同步注意:同步有很多種方法,也可以使用Lock 來處理,同步是一種方法,不是特別指synchronzied 這個關鍵字,有興趣的人可以多探究一下。
且同步的方法通常比較慢,效能方面也要權衡。
//静态同步工厂方法 public synchronized static Dog getInstance() { if (dog == null) { dog = new Dog(); } return dog; }
多重實例模式類別
//固定数目实例模式 public class MultiInstance { //实例数量,这里为四个 private final static int INSTANCE_COUNT = 4; private static int COUNT = 0; private static MultiInstance[] instance = new MultiInstance[4]; private MultiInstance() {}; public static MultiInstance getInstance() { //注意数组的下标只能为 COUNT - 1 if (MultiInstance.COUNT <= MultiInstance.INSTANCE_COUNT - 1) { instance[MultiInstance.COUNT] = new MultiInstance(); MultiInstance.COUNT++; } //返回实例前,执行了 COUNT++ 操作,所以 应该返回上一个实例 return MultiInstance.instance[MultiInstance.COUNT-1]; } }
測試類別
import java.util.HashSet; import java.util.Set; public class Test { public static void main(String[] args) { System.out.println("------------------------"); testMultiInstance(); } //测试多实例模式(单例的扩展,固定数目实例) public static void testMultiInstance() { Set<MultiInstance> instanceSet = new HashSet<>(); MultiInstance instance = null; for (int i = 0; i < 10; i++) { instance = MultiInstance.getInstance(); instanceSet.add(instance); } System.out.println("8个实例中,不同的实例有:"+instanceSet.size()); } }
執行結果注意:如果在多執行緒環境下使用,也是要考慮執行緒安全的。有興趣的可以自己實現一下。
單例模式一定是安全的嗎?
不一定,有很多方法可以破壞單例模式!
这里举例看一看(我只能举我知道的哈!其他的感兴趣,可以去探究一下!)
使用反射:这种办法是非常有用的,通过反射即使是私有的属性和方法也可以访问了,因此反射破坏了类的封装性,所以使用反射还是要多多小心。但是反射也有许多其他的用途,这是一项非常有趣的技术(我也只是会一点点)。
使用反射破坏单例模式测试类
这里使用的还是前面的 Dog 实体类。注意我这里的**包名:**com。
所有的类都是在 com包 下面的。
import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; public class Client { public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { Class<?> clazz = Class.forName("com.Dog"); Constructor<?> con = clazz.getDeclaredConstructor(); //设置可访问权限 con.setAccessible(true); Dog dog1 = (Dog) con.newInstance(); Dog dog2 = (Dog) con.newInstance(); System.out.println(dog1 == dog2); } }
说明:反射的功能是很强大的,从这里既可以看出来,正是有了反射,才使得Java 语言具有了更多的特色,这也是Java的强大之处。
使用对象序列化破坏单例模式
测试实体类:Dog(增加一个对象序列化接口实现)
import java.io.Serializable; //懒汉式 public class Dog implements Serializable{ private static final long serialVersionUID = 1L; private static Dog dog; private String name; private int age; //私有的构造器 private Dog() {} public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } //静态工厂方法 public synchronized static Dog getInstance() { if (dog == null) { dog = new Dog(); } return dog; } @Override public String toString() { return "Dog [name=" + name + ", age=" + age + "]"; } }
对象序列化测试类
import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; public class Client { public static void main(String[] args) throws IOException, ClassNotFoundException { Dog dog1 = Dog.getInstance(); dog1.setName("小黑"); dog1.setAge(2); System.out.println(dog1.toString()); ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos); oos.writeObject(dog1); ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bis); Dog dog2 = (Dog) ois.readObject(); System.out.println(dog2.toString()); System.out.println("dog1 == dog2: "+(dog1 == dog2)); } }
运行结果
说明
这里可以看出来通过对象序列化(这里也可以说是对象的深拷贝或深克隆),
同样也可以实现类的实例的不唯一性。这同样也算是破坏了类的封装性。对象序列化和反序列化的过程中,对象的唯一性变了。
这里具体的原因很复杂,我最近看了点深拷贝的知识,所以只是知其然不知其之所以然。(所以学习是需要不断进行的!加油诸位。)
这里我贴一下别的经验吧:(感兴趣的可以实现一下!)
为什么序列化可以破坏单例了?
答:序列化会通过反射调用无参数的构造方法创建一个新的对象。
这个东西目前超出了我的能力范围了,但也是去查看源码得出来的,就是序列化(serializable)和反序列化(externalizable)接口的详细情况了。但是有一点,它也是通过反射来做的的,所以可以看出**反射(reflect)**是一种非常强大和危险的技术了。
以上是java單例模式和線程安全問題怎麼解決的詳細內容。更多資訊請關注PHP中文網其他相關文章!

熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

Video Face Swap
使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

熱工具

記事本++7.3.1
好用且免費的程式碼編輯器

SublimeText3漢化版
中文版,非常好用

禪工作室 13.0.1
強大的PHP整合開發環境

Dreamweaver CS6
視覺化網頁開發工具

SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)

Java 8引入了Stream API,提供了一種強大且表達力豐富的處理數據集合的方式。然而,使用Stream時,一個常見問題是:如何從forEach操作中中斷或返回? 傳統循環允許提前中斷或返回,但Stream的forEach方法並不直接支持這種方式。本文將解釋原因,並探討在Stream處理系統中實現提前終止的替代方法。 延伸閱讀: Java Stream API改進 理解Stream forEach forEach方法是一個終端操作,它對Stream中的每個元素執行一個操作。它的設計意圖是處

PHP是一種廣泛應用於服務器端的腳本語言,特別適合web開發。 1.PHP可以嵌入HTML,處理HTTP請求和響應,支持多種數據庫。 2.PHP用於生成動態網頁內容,處理表單數據,訪問數據庫等,具有強大的社區支持和開源資源。 3.PHP是解釋型語言,執行過程包括詞法分析、語法分析、編譯和執行。 4.PHP可以與MySQL結合用於用戶註冊系統等高級應用。 5.調試PHP時,可使用error_reporting()和var_dump()等函數。 6.優化PHP代碼可通過緩存機制、優化數據庫查詢和使用內置函數。 7

PHP和Python各有優勢,選擇應基於項目需求。 1.PHP適合web開發,語法簡單,執行效率高。 2.Python適用於數據科學和機器學習,語法簡潔,庫豐富。

PHP適合web開發,特別是在快速開發和處理動態內容方面表現出色,但不擅長數據科學和企業級應用。與Python相比,PHP在web開發中更具優勢,但在數據科學領域不如Python;與Java相比,PHP在企業級應用中表現較差,但在web開發中更靈活;與JavaScript相比,PHP在後端開發中更簡潔,但在前端開發中不如JavaScript。

PHP和Python各有優勢,適合不同場景。 1.PHP適用於web開發,提供內置web服務器和豐富函數庫。 2.Python適合數據科學和機器學習,語法簡潔且有強大標準庫。選擇時應根據項目需求決定。

PHPhassignificantlyimpactedwebdevelopmentandextendsbeyondit.1)ItpowersmajorplatformslikeWordPressandexcelsindatabaseinteractions.2)PHP'sadaptabilityallowsittoscaleforlargeapplicationsusingframeworkslikeLaravel.3)Beyondweb,PHPisusedincommand-linescrip

PHP成為許多網站首選技術棧的原因包括其易用性、強大社區支持和廣泛應用。 1)易於學習和使用,適合初學者。 2)擁有龐大的開發者社區,資源豐富。 3)廣泛應用於WordPress、Drupal等平台。 4)與Web服務器緊密集成,簡化開發部署。

PHP適用於Web開發和內容管理系統,Python適合數據科學、機器學習和自動化腳本。 1.PHP在構建快速、可擴展的網站和應用程序方面表現出色,常用於WordPress等CMS。 2.Python在數據科學和機器學習領域表現卓越,擁有豐富的庫如NumPy和TensorFlow。
