分類
發燒車訊

JAVA設計模式 2【創建型】原型模式的理解與使用、理解淺克隆和深克隆

在本節中,我們將學習和使用原型模式;這一節學習的原型模式也是創建型 模式的其中之一。再次複習一下:創建型 模式就是描述如何去更好的創建一個對象。

我們都知道,在JAVA 語言中。使用new 關鍵字創建一個新對象。將新的對象放到堆內存 裏面。當然,這個內存肯定是有大小限制的,況且,JAVA 不同於C語言等。 有內存管理機制,就是我們常說的垃圾回收器GC,才可以保證內存不被溢出。

說這些其實就是為了表示:為啥要用單例模式,能節省內存的時候,能用一個對象解決重複的事情,絕對不會創建多個。

概述

原型模式描述的如何快速創建重複的對象,並且減少new 關鍵字的使用。

  • 抽象原型類
  • 具體原型類
  • 訪問類

容我來一個一個解釋:

抽象原型類 也就是我們具體要實現的某個類,這個類在JAVA 裏面是有具體的接口的,其實是一個空接口,Cloneable

 * @author  unascribed
 * @see     java.lang.CloneNotSupportedException
 * @see     java.lang.Object#clone()
 * @since   JDK1.0
 */
public interface Cloneable {
}

我們會發現,這個類沒有任何的方法,怎麼來實現它,不要慌。先接着走。

具體原型類 也就是我們具體要克隆 的對象。比如我們重複的要創建100個學生Student 對象,那麼具體的學生對象就是具體原型類

public class Student implements Cloneable {

    private int id;

    private String name;

    private int sex;
}

訪問類 我就不必多說了

淺克隆和深克隆

原型模式其實也分淺克隆和深克隆。如何理解這兩個概念呢?

淺克隆

protected native Object clone() throws CloneNotSupportedException;

淺克隆,只需要具體原型類 實現Cloneable 接口,並且重寫父類Object類的clone() 方法,即可實現對象的淺克隆。

Student student1 = new Student(1, "李四");
Student student2 = student1.clone();

System.out.println(student1);
System.out.println(student2);

System.out.println(student1 == student2);
---------------------
學號:1,姓名:李四
學號:1,姓名:李四
false
  • 通過執行clone() 方法即可創建一個相同的,具有同樣屬性的對象。
  • 並且是新的對象,內存地址有所不同。

我們來看看,對於引用類型的變量,淺克隆是否可以進行克隆;

Teacher teacher = new Teacher(1, "張老師");

Student student1 = new Student(1, "李四", teacher);
Student student2 = student1.clone();

System.out.println(student1);
System.out.println(student2);

System.out.println(student1 == student2);
------------
學號:1,姓名:李四,老師=Teacher@1b6d3586
學號:1,姓名:李四,老師=Teacher@1b6d3586
false

我們發現,引用類型並沒有被克隆,也就是說:

特點

  • 淺克隆對於基本類型,可以進行完全的克隆,並且克隆的對象是一個新的對象
  • 但是對象裏面的引用,是無法被克隆的。

深克隆(序列化)

何謂序列化?

我們創建的都是保存在內存裏面的,只要被虛擬機GC進行回收,那麼這個對象的任何屬性都是消失,我們能不能找一個方法,將內存中這種對象的屬性以及對象的狀態通過某種東西保存下來,比如保存到數據庫,下次從數據庫將這個對象還原到內存裏面。 這就是序列化。

  • 序列化 內存對象->序列字符
  • 反序列化 序列字符->內存對象

請參考: https://baike.baidu.com/item/序列化/2890184

JAVA 序列化

 * @see java.io.Externalizable
 * @since   JDK1.1
 */
public interface Serializable {
}

JAVA 提供了一個空接口,其實這個接口和上面的Cloneable 一樣,都是一個空接口,其實這個空接口就是作為一種標識 你的對象實現了這個接口,JAVA 認為你的這個就可以被序列化 ,就是這麼簡單。

Teacher teacher = new Teacher(1, "張老師");

ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
ObjectOutputStream stream = new ObjectOutputStream(outputStream);

stream.writeObject(teacher);
System.out.println(Arrays.toString(outputStream.toByteArray()));
----------
[-84, -19, 0, 5, 115, 114, 0, 7, 84, 101, 97,。。。。。。

通過將對象序列化、其實也就是將內存中的對象轉化為二進制 字節數組

反序列化

Teacher teacher = new Teacher(1, "張老師");
System.out.println(teacher);

ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
ObjectOutputStream stream = new ObjectOutputStream(outputStream);

stream.writeObject(teacher);
System.out.println(Arrays.toString(outputStream.toByteArray()));

ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(outputStream.toByteArray());
ObjectInputStream inputStream = new ObjectInputStream(byteArrayInputStream);

Teacher teacher1 = (Teacher) inputStream.readObject();
System.out.println(teacher1);
---------------
id=1,name=張老師
[-84, -19, 0, 5, 115, xxxxx,-127, -27, -72, -120]
id=1,name=張老師

通過序列化和反序列化,即可對象的深克隆

小結

這一節,在講述 原型模式的同時,將原有實現原型模式的clone() 淺克隆,延伸到深克隆這一概念。其實JAVA 的原型模式,實現起來較為簡單。但還是要按需要實現,Object 類提供的 clone 淺克隆 是沒辦法克隆對象的引用類型的。需要克隆引用類型,還是需要序列化 深克隆

參考

http://c.biancheng.net/view/1343.html
https://www.liaoxuefeng.com/wiki/1252599548343744/1298366845681698

代碼示例

https://gitee.com/mrc1999/Dev-Examples

歡迎關注

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

新北清潔公司,居家、辦公、裝潢細清專業服務

※別再煩惱如何寫文案,掌握八大原則!

※教你寫出一流的銷售文案?

※超省錢租車方案

FB行銷專家,教你從零開始的技巧

分類
發燒車訊

Java多線程之內存模型

目錄

  • 多線程需要解決的問題
    • 線程之間的通信
    • 線程之間的同步
  • Java內存模型
    • 內存間的交互操作
    • 指令屏障
    • happens-before規則
  • 指令重排序
    • 從源程序到字節指令的重排序
    • as-if-serial語義
    • 程序順序規則
  • 順序一致性模型
    • 順序一致性模型特性
    • 順序一致性模型特性
    • 當程序未正確同步會發生什麼
  • 參考資料

多線程需要解決的問題

在多線程編程中,線程之間如何通信和同步是一個必須解決的問題:

線程之間的通信:

線程之間有兩種通信的方式:消息傳遞和共享內存

  • 共享內存:線程之間共享程序的公共狀態,通過讀——寫修改公共狀態進行隱式通信。如上面代碼中的numLock可以被理解為公共狀態
  • 消息傳遞:線程之間沒有公共狀態,必須通過發送消息來進行显示通信
    在java中,線程是通過共享內存來完成線程之間的通信

線程之間的同步:

同步指程序中永固空值不同線程間的操作發生的相對順序的機制

  • 共享內存:同步是显示進行的,程序員需要指定某個方法或者某段代碼需要在線程之間互斥執行。如上面代碼中的Lock加鎖和解鎖之間的代碼塊,或者被synchronized包圍的代碼塊
  • 消息傳遞:同步是隱式執行的,因為消息的發送必然發生在消息的接收之前,例如使用Objetc#notify(),喚醒的線程接收信號一定在發送喚醒信號的發送之後。

Java內存模型

在java中,所有的實例域,靜態域、數組都被存儲在堆空間當中,堆內存在線程之間共享。

所有的局部變量,方法定義參數和異常處理器參數不會被線程共享,在每個線程棧中獨享,他們不會存在可見性和線程安全問題。

從Java線程模型(JMM)的角度來看,線程之間的共享變量存儲在主內存當中,每個線程擁有一個私有的本地內存(工作內存)本地內存存儲了該線程讀——寫共享的變量的副本。
JMM只是一個抽象的概念,在現實中並不存在,其中所有的存儲區域都在堆內存當中。JMM的模型圖如下圖所示:

而java線程對於共享變量的操作都是對於本地內存(工作內存)中的副本的操作,並沒有對共享內存中原始的共享變量進行操作;

以線程1和線程2為例,假設線程1修改了共享變量,那麼他們之間需要通信就需要兩個步驟:

  1. 線程1本地內存中修改過的共享變量的副本同步到共享內存中去
  2. 線程2從共享內存中讀取被線程1更新過的共享變量
    這樣才能完成線程1的修改對線程2的可見。

內存間的交互操作

為了完成這一線程之間的通信,JMM為內存間的交互操作定義了8個原子操作,如下錶:

操作 作用域 說明
lock(鎖定) 共享內存中的變量 把一個變量標識為一條線程獨佔的狀態
unlock(解鎖) 共享內存中的變量 把一個處於鎖定的變量釋放出來,釋放后其他線程可以進行訪問
read(讀取) 共享內存中的變量 把一個變量的值從共享內存傳輸到線程的工作內存。供隨後的load操作使用
load(載入) 工作內存 把read操作從共享內存中得到的變量值放入工作內存的變量副本當中
use(使用) 工作內存 把工作內存中的一個變量值傳遞給執行引擎
assign(賦值) 工作內存 把一個從執行引擎接受到的值賦值給工作內存的變量
store(存儲) 作用於工作內存 把一個工作內存中的變量傳遞給共享內存,供後續的write使用
write(寫入) 共享內存中的變量 把store操作從工作內存中得到的變量的值放入主內存

JMM規定JVM四線時必須保證上述8個原子操作是不可再分割的,同時必須滿足以下的規則:

  1. 不允許readloadstorewrite操作之一單獨出現,即不允許只從共享內存讀取但工作內存不接受,或者工作捏村發起回寫但是共享內存不接收
  2. 不允許一個線程捨棄assign操作,即當一個線程修改了變量后必須寫回工作內存和共享內存
  3. 不允許一個線程將未修改的變量值寫回共享內存
  4. 變量只能從共享內存中誕生,不允許線程直接使用未初始化的變量
  5. 一個變量同一時刻只能由一個線程對其執行lock操作,但是一個變量可以被同一個線程重複執行多次lock,但是需要相同次數的unlock
  6. 如果對一個變量執行lock操作,那麼會清空工作內存中此變量的值,在執行引擎使用這個變量之前需要重新執行load和assign
  7. 不允許unlock一個沒有被鎖定的變量,也不允許unlock一個其他線程lock的變量
  8. 對一個變量unlock之前必須把此變量同步回主存當中。

longdouble的特殊操作
在一些32位的處理器上,如果要求對64位的longdouble的寫具有原子性,會有較大的開銷,為了照固這種情況,
java語言規範鼓勵但不要求虛擬機對64位的longdouble型變量的寫操作具有原子性,當JVM在這種處理器上運行時,
可能會把64位的long和double拆分成兩次32位的寫

指令屏障

為了保證內存的可見性,JMM的編譯器會禁止特定類型的編譯器重新排序;對於處理器的重新排序,
JMM會要求編譯器在生成指令序列時插入特定類型的的內存屏障指令,通過內存屏障指令巾紙特定類型的處理器重新排序

JMM規定了四種內存屏障,具體如下:

屏障類型 指令示例 說明
LoadLoad Barriers Load1;LoadLoad;Load2 確保Load1的數據先於Load2以及所有後續裝在指令的裝載
StoreStore Barries Store1;StoreStore;Store2 確保Store1數據對於其他處理器可見(刷新到內存)先於Store2及後續存儲指令的存儲
LoadStore Barriers Load1;LoadStore;Store2 確保Load1的裝載先於Store2及後續所有的存儲指令
StoreLoad Barrier Store1;StoreLoad;Load2 確保Store1的存儲指令先於Load1以及後續所所有的加載指令

StoreLoad是一個“萬能”的內存屏障,他同時具有其他三個內存屏障的效果,現代的處理器大都支持該屏障(其他的內存屏障不一定支持),
但是執行這個內存屏障的開銷很昂貴,因為需要將處理器緩衝區所有的數據刷回內存中。

happens-before規則

在JSR-133種內存模型種引入了happens-before規則來闡述操作之間的內存可見性。在JVM種如果一個操作的結果過需要對另一個操作可見,
那麼兩個操作之間必然要存在happens-bsfore關係:

  • 程序順序規則:一個線程中的個每個操作,happens-before於該線程的後續所有操作
  • 監視器鎖規則:對於一個鎖的解鎖,happens-before於隨後對於這個鎖的加鎖
  • volatitle變量規則:對於一個volatile的寫,happens-before於認意後續對這個volatile域的讀
  • 線程啟動原則:對線程的start()操作先行發生於線程內的任何操作
  • 線程終止原則:線程中的所有操作先行發生於檢測到線程終止,可以通過Thread.join()、Thread.isAlive()的返回值檢測線程是否已經終止
  • 線程終端原則:對線程的interrupt()的調用先行發生於線程的代碼中檢測到中斷事件的發生,可以通過Thread.interrupted()方法檢測是否發生中斷
  • 對象終結原則:一個對象的初始化完成(構造方法執行結束)先行發生於它的finalize()方法的開始。
  • 傳遞性:如果A happens-before B B happends-beforeC,那麼A happends-before C

指令重排序

從源程序到字節指令的重排序

眾所周知,JVM執行的是字節碼,Java源代碼需要先編譯成字節碼程序才能在Java虛擬機中運行,但是考慮下面的程序;

int a = 1;
int b = 1;

在這段代碼中,ab沒有任何的相互依賴關係,因此完全可以先對b初始化賦值,再對a變量初始化賦值;

事實上,為了提高性能,編譯器和處理器通常會對指令做重新排序。重排序分為3種:

  1. 編譯器優化的重排序。編譯器在不改變單線程的程序語義的前提下,可以安排字語句的執行順序。編譯器的對象是語句,不是字節碼,
    但是反應的結果就是編譯后的字節碼和寫的語句順序不一致。
  2. 執行級并行的重排序。現代處理器採用了并行技術,來將多條指令重疊執行。如果不存在數據依賴性,處理器可以改變語句對應機器指令的執行順序。
  3. 內存系統的重排序,由於處理器使用了緩存和讀/寫緩衝區,這使得加載和存儲操作看上去可能是在亂序執行。

數據依賴性:如果兩個操作訪問同一個變量,且兩個操作有一個是寫操作,則這兩個操作存在數據依賴性,改變這兩個操作的執行順序,就會改變執行結果。

儘管指令重排序會提高代碼的執行效率,但是卻為多線程編程帶來了問題,多線程操作共享變量需要一定程度上遵循代碼的編寫順序,
也需要將修改的共享數據存儲到共享內存中,不按照代碼順序執行可能會導致多線程程序出現內存可見性的問題,那又如何實現呢?

as-if-serial語義

as-if-serial語義:不論程序怎樣進行重排序,(單線程)程序的執行結果不能被改變。編譯器、runtime和處理器都必須支持as-if-serial語義。

程序順序規則

假設存在以下happens-before程序規則:

    1) A happens-before B
    2) B happens-before C
    3) A happens-before C

儘管這裏存在A happens-before B這一關係,但是JMM並不要求A一定要在B之前執行,僅僅要求A的執行結果對B可見。
即JMM僅要求前一個操作的結果對於后一個操作可見,並且前一個操作按照順序排在後一個操作之前。
但是若前一個操作放在後一個操作之後執行並不影響執行結果,則JMM認為這並不違法,JMM允許這種重排序。

順序一致性模型

在一個線程中寫一個變量,在另一個線程中同時讀取這個變量,讀和寫沒有通過排序來同步來排序,就會引發數據競爭。

數據競爭的核心原因是程序未正確同步。如果一個多線程程序是正確同步的,這個程序將是一個沒有數據競爭的程序。

順序一致性模型只是一個參考模型。

順序一致性模型特性

  • 一個線程中所有的操作必須按照程序的順序來執行。
  • 不管線程是否同步,所有的線程都只能看到一個單一的執行順序。

在順序一致性模型中每個曹祖都必須原子執行且立刻對所有線程可見。

當程序未正確同步會發生什麼

當線程未正確同步時,JMM只提供最小的安全性,當讀取到一個值時,這個值要麼是之前寫入的值,要麼是默認值。
JMM保證線程的操作不會無中生有。為了保證這一特點,JMM在分配對象時,首先會對內存空間清0,然後才在上面分配對象。

未同步的程序在JMM種執行時,整體上是無序的,執行結果也無法預知。位同步程序子兩個模型中執行特點有如下幾個差異:

  • 順序一致性模型保證單線程內的操作會按照程序的順序執行,而JMM不保證單線程內的操作會按照程序的順序執行
  • 順序一致性模型保證所有線程只能看到一致的操作執行順序,而JMM不保證所有線程能看到一致的操作執行順序
  • JMM不保證對64位的longdouble型變量具有寫操作的原子性,而順序一致性模型保證對所有的內存的讀/寫操作都具有原子性

參考資料

java併發編程的藝術-方騰飛,魏鵬,程曉明著

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

新北清潔公司,居家、辦公、裝潢細清專業服務

※別再煩惱如何寫文案,掌握八大原則!

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※超省錢租車方案

※教你寫出一流的銷售文案?

網頁設計最專業,超強功能平台可客製化

【其他文章推薦】

新北清潔公司,居家、辦公、裝潢細清專業服務

※別再煩惱如何寫文案,掌握八大原則!

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※超省錢租車方案

※教你寫出一流的銷售文案?

網頁設計最專業,超強功能平台可客製化

分類
發燒車訊

【Mongodb】 可複製集搭建

可複製集 replica set

概念圖

可複製集需要至少3個以上的mongodb節點,其中有一個主節點promary,其餘的為副本節點secondary

可複製集有三個角色:

  • 主要成員(Primary):主要接收所有寫操作。就是主節點。
  • 副本成員(Secondary):從主節點通過複製操作以維護相同的數據集,即備份數據,不可寫操作,但可以讀操作(但需要配置)。是默認的一種從節點類型。
  • 仲裁者(Arbiter):不保留任何數據的副本,只具有投票選舉作用。當然也可以將仲裁服務器維護為副本集的一部分,即副本成員同時也可以是仲裁者。也是一種從節點類型。

關於仲裁者:
如果主節點+副本節點是偶數推薦添加仲裁者,如果主節點+ 副本節點是奇數可以不添加仲裁者。仲裁者將永遠是仲裁者,而主要人員可能會退出並成為次要人員,而次要人員可能成為選舉期間的主要人員。

為什麼要用可複製集?它有什麼重要性?

  1. 避免數據丟失,保障數據安全,提高系統安全性;
    (最少3節點,最大50節點)
  2. 自動化災備機制,主節點宕機后通過選舉產生新主機;提高系統健壯性;
    (7個選舉節點上限)
  3. 讀寫分離,負載均衡,提高系統性能;

搭建

準備三個mongodb節點

正準備三個mongodb節點,我們先搭建一個主節點,2個副本節點的模式
修改配置mongo.conf

  • 第一個
systemLog:
  #MongoDB發送所有日誌輸出的目標指定為文件 
  destination: file
  #mongod或mongos應向其發送所有診斷日誌記錄信息的日誌文件的路徑 
  path: "/home/amber/mongodb/mongodb-001/log/mongod.log" 
  #當mongos或mongod實例重新啟動時,mongos或mongod會將新條目附加到現有日誌文件的末尾。 
  logAppend: true
storage: 
  #mongod實例存儲其數據的目錄。storage.dbPath設置僅適用於mongod。 
  dbPath: "/home/amber/mongodb/mongodb-001/data/db" 
  journal:
    #啟用或禁用持久性日誌以確保數據文件保持有效和可恢復。 
    enabled: true
processManagement:
  #啟用在後台運行mongos或mongod進程的守護進程模式。 
  fork: true 
  #指定用於保存mongos或mongod進程的進程ID的文件位置,其中mongos或mongod將寫入其PID 
  pidFilePath: "/home/amber/mongodb/mongodb-001/log/mongod.pid" 
net:
  #服務實例綁定所有IP,有副作用,副本集初始化的時候,節點名字會自動設置為本地域名,而不是ip 
  #bindIpAll: true 
  #服務實例綁定的IP 
  bindIp: 0.0.0.0
  #bindIp 
  #綁定的端口 
  port: 27017
replication: 
  #副本集的名稱 
  replSetName: myrs
  • 第二個第三個配置
    把上述文件中的mongodb-001換成mongodb-002``mongodb-003
    端口分別換成27018 27019

然後分別在mongodb-00X的根目錄下執行啟動命令

./bin/mongod -f ./conf/mongod.conf

檢查進程

ps -ef|grep mongod

設置主節點

進入27017的那個mongod的客戶端,並且執行

rs.initiate({
      _id: "myrs", //  需要和replSetName的名稱一致
      version: 1,
      members: [{ _id: 0, host : "192.168.xx.xx:27017" }]});

或者

rs.initiate({}) 

執行結果

提示:
1)“ok”的值為1,說明創建成功。
2)命令行提示符發生變化,變成了一個從節點角色,此時默認不能讀寫。稍等片刻,回車,變成主節  點。

配置副本節點

再27017的mongod客戶端,也就是主節點上執行192.168.xx.xx:27018是副本節點的ip和端口

rs.add("192.168.xx.xx:27018")
rs.add("192.168.xx.xx:27019")

使用

rs.status()

就可以看到members會有三個節點了

測試

再主節點插入一條數據

use article;
db.comment.insert({name: "amber"})

再從節點查看,結果

這是因為需要再從節點再次進行

rs.slaveok() // 確認當前節點是副本節點
db.comment.find(); // 查看當前數據

可以看到已經有數據了,這樣一主二從的可複製成功了

如果關閉主節點,在從節點執行rs.status();
可以看到原來的主節點的health變成了0

27018變成了新的主節點

主節點的選舉原則

MongoDB在副本集中,主節點選舉的觸發條件:
1) 主節點故障
2) 主節點網絡不可達(默認心跳信息為10秒)
3) 人工干預(rs.stepDown(600))

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※教你寫出一流的銷售文案?

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※回頭車貨運收費標準

※別再煩惱如何寫文案,掌握八大原則!

※超省錢租車方案

※產品缺大量曝光嗎?你需要的是一流包裝設計!

分類
發燒車訊

【Spring註解驅動開發】使用InitializingBean和DisposableBean來管理bean的生命周期,你真的了解嗎?

寫在前面

在《【Spring註解驅動開發】如何使用@Bean註解指定初始化和銷毀的方法?看這一篇就夠了!!》一文中,我們講述了如何使用@Bean註解來指定bean初始化和銷毀的方法。具體的用法就是在@Bean註解中使用init-method屬性和destroy-method屬性來指定初始化方法和銷毀方法。除此之外,Spring中是否還提供了其他的方式來對bean實例進行初始化和銷毀呢?

項目工程源碼已經提交到GitHub:https://github.com/sunshinelyz/spring-annotation

InitializingBean接口

1.InitializingBean接口概述

Spring中提供了一個InitializingBean接口,InitializingBean接口為bean提供了屬性初始化后的處理方法,它只包括afterPropertiesSet方法,凡是繼承該接口的類,在bean的屬性初始化后都會執行該方法。InitializingBean接口的源碼如下所示。

package org.springframework.beans.factory;
public interface InitializingBean {
	void afterPropertiesSet() throws Exception;
}

根據InitializingBean接口中提供的afterPropertiesSet()方法的名字可以推斷出:afterPropertiesSet()方法是在屬性賦好值之後調用的。那到底是不是這樣呢?我們來分析下afterPropertiesSet()方法的調用時機。

2.何時調用InitializingBean接口?

我們定位到Spring中的org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory類下的invokeInitMethods()方法中,來查看Spring加載bean的方法。

題外話:不要問我為什麼會是這個invokeInitMethods()方法,如果你和我一樣對Spring的源碼非常熟悉的話,你也會知道是這個invokeInitMethods()方法,哈哈哈哈!所以,小夥伴們不要只顧着使用Spring,還是要多看看Spring的源碼啊!Spring框架中使用了大量優秀的設計模型,其代碼的編寫規範和嚴謹程度也是業界開源框架中數一數二的,非常值得閱讀。

我們來到AbstractAutowireCapableBeanFactory類下的invokeInitMethods()方法,如下所示。

protected void invokeInitMethods(String beanName, final Object bean, @Nullable RootBeanDefinition mbd)
    throws Throwable {
	//判斷該bean是否實現了實現了InitializingBean接口,如果實現了InitializingBean接口,則調用bean的afterPropertiesSet方法
    boolean isInitializingBean = (bean instanceof InitializingBean);
    if (isInitializingBean && (mbd == null || !mbd.isExternallyManagedInitMethod("afterPropertiesSet"))) {
        if (logger.isTraceEnabled()) {
            logger.trace("Invoking afterPropertiesSet() on bean with name '" + beanName + "'");
        }
        if (System.getSecurityManager() != null) {
            try {
                AccessController.doPrivileged((PrivilegedExceptionAction<Object>) () -> {
                    //調用afterPropertiesSet()方法
                    ((InitializingBean) bean).afterPropertiesSet();
                    return null;
                }, getAccessControlContext());
            }
            catch (PrivilegedActionException pae) {
                throw pae.getException();
            }
        }
        else {
            //調用afterPropertiesSet()方法
            ((InitializingBean) bean).afterPropertiesSet();
        }
    }

    if (mbd != null && bean.getClass() != NullBean.class) {
        String initMethodName = mbd.getInitMethodName();
        if (StringUtils.hasLength(initMethodName) &&
            !(isInitializingBean && "afterPropertiesSet".equals(initMethodName)) &&
            !mbd.isExternallyManagedInitMethod(initMethodName)) {
            //通過反射的方式調用init-method
            invokeCustomInitMethod(beanName, bean, mbd);
        }
    }
}

分析上述代碼后,我們可以初步得出如下信息:

  • Spring為bean提供了兩種初始化bean的方式,實現InitializingBean接口,實現afterPropertiesSet方法,或者在配置文件和@Bean註解中通過init-method指定,兩種方式可以同時使用。
  • 實現InitializingBean接口是直接調用afterPropertiesSet()方法,比通過反射調用init-method指定的方法效率相對來說要高點。但是init-method方式消除了對Spring的依賴。
  • 如果調用afterPropertiesSet方法時出錯,則不調用init-method指定的方法。

也就是說Spring為bean提供了兩種初始化的方式,第一種實現InitializingBean接口,實現afterPropertiesSet方法,第二種配置文件或@Bean註解中通過init-method指定,兩種方式可以同時使用,同時使用先調用afterPropertiesSet方法,后執行init-method指定的方法。

DisposableBean接口

1.DisposableBean接口概述

實現org.springframework.beans.factory.DisposableBean接口的bean在銷毀前,Spring將會調用DisposableBean接口的destroy()方法。我們先來看下DisposableBean接口的源碼,如下所示。

package org.springframework.beans.factory;
public interface DisposableBean {
	void destroy() throws Exception;
}

可以看到,在DisposableBean接口中只定義了一個destroy()方法。

在Bean生命周期結束前調用destory()方法做一些收尾工作,亦可以使用destory-method。前者與Spring耦合高,使用類型強轉.方法名(),效率高。後者耦合低,使用反射,效率相對低

2.DisposableBean接口注意事項

多例bean的生命周期不歸Spring容器來管理,這裏的DisposableBean中的方法是由Spring容器來調用的,所以如果一個多例實現了DisposableBean是沒有啥意義的,因為相應的方法根本不會被調用,當然在XML配置文件中指定了destroy方法,也是沒有意義的。所以,在多實例bean情況下,Spring不會自動調用bean的銷毀方法。

單實例bean案例

創建一個Animal的類實現InitializingBean和DisposableBean接口,代碼如下:

package io.mykit.spring.plugins.register.bean;

import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;
/**
 * @author binghe
 * @version 1.0.0
 * @description 測試InitializingBean接口和DisposableBean接口
 */
public class Animal implements InitializingBean, DisposableBean {
    public Animal(){
        System.out.println("執行了Animal類的無參數構造方法");
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("執行了Animal類的初始化方法。。。。。");

    }
    @Override
    public void destroy() throws Exception {
        System.out.println("執行了Animal類的銷毀方法。。。。。");

    }
}

接下來,我們新建一個AnimalConfig類,並將Animal通過@Bean註解的方式註冊到Spring容器中,如下所示。

package io.mykit.spring.plugins.register.config;

import io.mykit.spring.plugins.register.bean.Animal;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
/**
 * @author binghe
 * @version 1.0.0
 * @description AnimalConfig
 */
@Configuration
@ComponentScan("io.mykit.spring.plugins.register.bean")
public class AnimalConfig {
    @Bean
    public Animal animal(){
        return new Animal();
    }
}

接下來,我們在BeanLifeCircleTest類中新增testBeanLifeCircle02()方法來進行測試,如下所示。

@Test
public void testBeanLifeCircle02(){
    //創建IOC容器
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AnimalConfig.class);
    System.out.println("IOC容器創建完成...");
    //關閉IOC容器
    context.close();
}

運行BeanLifeCircleTest類中的testBeanLifeCircle02()方法,輸出的結果信息如下所示。

執行了Animal類的無參數構造方法
執行了Animal類的初始化方法。。。。。
IOC容器創建完成...
執行了Animal類的銷毀方法。。。。。

從輸出的結果信息可以看出:單實例bean下,IOC容器創建完成后,會自動調用bean的初始化方法;而在容器銷毀前,會自動調用bean的銷毀方法。

多實例bean案例

多實例bean的案例代碼基本與單實例bean的案例代碼相同,只不過在AnimalConfig類中,我們在animal()方法上添加了@Scope(“prototype”)註解,如下所示。

package io.mykit.spring.plugins.register.config;
import io.mykit.spring.plugins.register.bean.Animal;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
/**
 * @author binghe
 * @version 1.0.0
 * @description AnimalConfig
 */
@Configuration
@ComponentScan("io.mykit.spring.plugins.register.bean")
public class AnimalConfig {
    @Bean
    @Scope("prototype")
    public Animal animal(){
        return new Animal();
    }
}

接下來,我們在BeanLifeCircleTest類中新增testBeanLifeCircle03()方法來進行測試,如下所示。

@Test
public void testBeanLifeCircle03(){
    //創建IOC容器
    AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(AnimalConfig.class);
    System.out.println("IOC容器創建完成...");
    System.out.println("-------");
    //調用時創建對象
    Object bean = ctx.getBean("animal");
    System.out.println("-------");
    //調用時創建對象
    Object bean1 = ctx.getBean("animal");
    System.out.println("-------");
    //關閉IOC容器
    ctx.close();
}

運行BeanLifeCircleTest類中的testBeanLifeCircle03()方法,輸出的結果信息如下所示。

IOC容器創建完成...
-------
執行了Animal類的無參數構造方法
執行了Animal類的初始化方法。。。。。
-------
執行了Animal類的無參數構造方法
執行了Animal類的初始化方法。。。。。
-------

從輸出的結果信息中可以看出:在多實例bean情況下,Spring不會自動調用bean的銷毀方法。

好了,咱們今天就聊到這兒吧!別忘了給個在看和轉發,讓更多的人看到,一起學習一起進步!!

項目工程源碼已經提交到GitHub:https://github.com/sunshinelyz/spring-annotation

寫在最後

如果覺得文章對你有點幫助,請微信搜索並關注「 冰河技術 」微信公眾號,跟冰河學習Spring註解驅動開發。公眾號回復“spring註解”關鍵字,領取Spring註解驅動開發核心知識圖,讓Spring註解驅動開發不再迷茫。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※超省錢租車方案

※別再煩惱如何寫文案,掌握八大原則!

※回頭車貨運收費標準

※教你寫出一流的銷售文案?

FB行銷專家,教你從零開始的技巧

分類
發燒車訊

區塊鏈系列教程之:比特幣中的網絡和區塊鏈

目錄

  • 簡介
  • 比特幣的網絡
    • 網絡發現與同步
  • SPV節點
    • 區塊鏈頭
    • Merkle Tree
  • 比特幣中的區塊鏈
    • 區塊標識符
  • 創世區塊
  • 總結

簡介

比特幣的底層就是區塊鏈技術,區塊鏈也是因為比特幣而廣為人知的。和其他的區塊鏈技術相比,比特幣的區塊鏈有什麼特徵呢?作為去區塊鏈的鼻祖,又有什麼與眾不同的特性呢?快來跟我們一起看看吧。

比特幣的網絡

比特幣使用的是P2P(peer-to-peer)網絡,此P2P非彼P2P,這裡是點對點的網絡架構,而不是人對人的借錢模式。

P2P是指位於同一網絡中的每台計算機都彼此對等,各個節點共同提供網絡服務,不存在任何“特殊”節點。每個網絡節點以“扁平(flat)”的拓撲結構相互連通。在P2P網絡中不存在任何服務端(server)、中央化的服務、以及層級結構。

傳統的網絡結構是client-server的模式,所有的client都是和server交互獲取信息, 只要server掛掉了,client也就沒有用了。

而在P2P網絡中,沒有server的概念,每個節點可以作為一個server。對比起來P2P網絡在穩定性方面要比C-S架構的系統要穩定得多。

網絡發現與同步

既然是P2P網絡,那麼問題來了,這個P2P網絡是怎麼建立起來的呢?節點之間是怎麼發現的呢?

有做過P2P下載的同學應該都聽說過種子的概念,這個種子裏面保存了其他活躍的節點的地址。通過下載種子就可以連接對應的節點。

而每個節點又保存了最近連接或者活躍的節點,這樣就形成了龐大的P2P網絡。

同樣的,比特幣的P2P網絡也是這樣的。

新節點是如何發現網絡中的對等節點的呢?雖然比特幣網絡中沒有特殊節點,但是客戶端會維持一個列表,那裡列出了那些長期穩定運行的節點。這樣的節點被稱為“種子節點(seed nodes)”

節點必須持續進行兩項工作:在失去已有連接時發現新節點,並在其他節點啟動時為其提供幫助。

SPV節點

我們之前介紹了,在比特幣的世界里既沒有賬戶,也沒有餘額,只有分散到區塊鏈里的UTXO(Unspent Transaction Outputs)。

那麼如果想要驗證交易的話,需要從歷史的交易中查找所有的和該交易有關的交易,從而進行完整,全面的驗證。

這樣做的問題就是,如果下載所有的歷史記錄,那麼需要上百G的硬盤空間,這對於手機或者其他輕量級的客戶端是無法想象的。

於是SPV出現了。SPV的全稱是Simplified payment verification,叫做簡單認證支付。

SPV保存的不是整個區塊鏈,而是區塊鏈的頭部,因為每個區塊鏈頭只有80字節,所以即使把所有的區塊頭都下載保存起來也不會很大。

區塊鏈頭

區塊頭由三組區塊元數據組成。首先是一組引用父區塊哈希值的數據,這組元數據用於將該區塊與區塊鏈中前一區塊相連接。

第二組元數據,即難度、時間戳和nonce,與挖礦競爭相關。

第三組元數據是merkle樹根(一種用來有效地總結區塊中所有交易的數據結構)。

Nonce、難度目標和時間戳會用於挖礦過程,Merkle根用來索引和組織該區塊所有的交易信息。

上圖是一個區塊鏈頭組成的鏈。

Merkle Tree

Merkle Tree,是一種樹(數據結構中所說的樹),網上大都稱為Merkle Hash Tree,這是因為 它所構造的Merkle Tree的所有節點都是Hash值。Merkle Tree具有以下特點:

  1. 它是一種樹,可以是二叉樹,也可以多叉樹,無論是幾叉樹,它都具有樹結構的所有特點;

  2. Merkle樹的恭弘=叶 恭弘子節點上的value,是由你指定的,這主要看你的設計了,如Merkle Hash Tree會將數據的Hash值作為恭弘=叶 恭弘子節點的值;

  3. 非恭弘=叶 恭弘子節點的value是根據它下面所有的恭弘=叶 恭弘子節點值,然後按照一定的算法計算而得出的。如Merkle Hash Tree的非恭弘=叶 恭弘子節點value的計算方法是將該節點的所有子節點進行組合,然後對組合結果進行hash計算所得出的hash value。

有了Merkle Tree,我們只需要知道和要驗證的交易相關的其他Merkle Tree中的信息,就可以計算出整個Merkle Tree的值,這樣就可以直接使用頭部信息進行驗證了。這就是SPV的原理。

比特幣中的區塊鏈

區塊鏈是由包含交易信息的區塊從後向前有序鏈接起來的數據結構。它可以被存儲為flat file(一種包含沒有相對關係記錄的文件),或是存儲在一個簡單數據庫中。

比特幣核心客戶端使用Google的LevelDB數據庫存儲區塊鏈元數據。

它由一個包含元數據的區塊頭和緊跟其後的構成區塊主體的一長串交易組成。區塊頭是80字節,而平均每個交易至少是250字節,而且平均每個區塊至少包含超過500個交易。

區塊標識符

那怎麼表示一個區塊呢?我們使用區塊標誌符。

區塊主標識符是它的加密哈希值,一個通過SHA256算法對區塊頭進行二次哈希計算而得到的数字指紋。產生的32字節哈希值被稱為區塊哈希值,但是更準確的名稱是:區塊頭哈希值,因為只有區塊頭被用於計算。

第二種識別區塊的方式是通過該區塊在區塊鏈中的位置,即“區塊高度(block height)”。第一個區塊,其區塊高度為0
和區塊哈希值不同的是,區塊高度並不是唯一的標識符。雖然一個單一的區塊總是會有一個明確的、固定的區塊高度,但反過來卻並不成立,一個區塊高度並不總是識別一個單一的區塊。兩個或兩個以上的區塊可能有相同的區塊高度,在區塊鏈里爭奪同一位置。

創世區塊

區塊鏈里的第一個區塊創建於2009年,被稱為創世區塊。它是區塊鏈裏面所有區塊的共同祖先,這意味着你從任一區塊,循鏈向後回溯,最終都將到達創世區塊。

因為創世區塊被編入到比特幣客戶端軟件里,所以每一個節點都始於至少包含一個區塊的區塊鏈,這能確保創世區塊不會被改變。每一個節點都“知道”創世區塊的哈希值、結構、被創建的時間和裏面的一個交易。因此,每個節點都把該區塊作為區塊鏈的首區塊,從而構建了一個安全的、可信的區塊鏈的根。

創世區塊的哈希值為:
0000000000 19d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f

創世區塊包含一個隱藏的信息。在其Coinbase交易的輸入中包含這樣一句話“The Times 03/Jan/2009 Chancellor on brink of second bailout forbanks.”這句話是泰晤士報當天的頭版文章標題,引用這句話,既是對該區塊產生時間的說明,也可視為半開玩笑地提醒人們一個獨立的貨幣制度的重要性,同時告訴人們隨着比特幣的發展,一場前所未有的世界性貨幣革命將要發生。該消息是由比特幣的創立者中本聰嵌入創世區塊中。

coinbase的值是:04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73

解碼方法如下:

在python shell下:

“04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73”.decode(‘hex’)

輸出:

‘\x04\xff\xff\x00\x1d\x01\x04EThe Times 03/Jan/2009 Chancellor on brink of second bailout for banks’

總結

本文介紹了比特幣的網絡和比特幣中的區塊鏈的相關概念,希望大家能夠喜歡。

本文作者:flydean程序那些事

本文鏈接:http://www.flydean.com/bitcoin-blockchain-network/

本文來源:flydean的博客

歡迎關注我的公眾號:程序那些事,更多精彩等着您!

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

※教你寫出一流的銷售文案?

※超省錢租車方案

分類
發燒車訊

「半雞半鴨」:考古發現最古老禽類化石

摘錄自2020年3月28日大紀元報導

考古學家發現一隻禽類生物的頭骨和腿骨化石,經檢驗發現它生活在距今約6,680萬~6,670萬年前,是至今全球發現的最古老的鳥類化石。

這份近期發表在《自然》(Nature)期刊上的研究公布了這一發現。研究者之一劍橋大學的古生物學家菲爾德(Daniel Field)說:「這是我們至今發現的最早存在的鳥類的證據。」

恐龍大約在距今6,600萬年前滅絕,因此這隻生物生活在僅比那個時間點早一點的時期。在此之前,科學家找到的最早的鳥類化石大約生活在距今6,650萬年前。新發現的化石比這個更早一些。

研究人員估計這隻鳥的體重為400克左右,只有現在的水鳥——鳧的一半大。「我們認為它的臉看起來有點像現代的雞,但是頭骨的後面看起來又更像現在的鴨子。」菲爾德說。一起發現的還有它的腿骨化石,看起來它有著兩條細長的腿,說明它是生活在岸邊的一種禽類。

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

新北清潔公司,居家、辦公、裝潢細清專業服務

※別再煩惱如何寫文案,掌握八大原則!

※教你寫出一流的銷售文案?

※超省錢租車方案

FB行銷專家,教你從零開始的技巧

分類
發燒車訊

幫居民生計找出路 坦尚尼亞社區林業 改善盜伐有成

環境資訊中心綜合外電;姜唯 編譯;林大利 審校

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

新北清潔公司,居家、辦公、裝潢細清專業服務

※別再煩惱如何寫文案,掌握八大原則!

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※超省錢租車方案

※教你寫出一流的銷售文案?

網頁設計最專業,超強功能平台可客製化

分類
發燒車訊

地球暖化冰河融解 北極新發現5座島嶼

摘錄自2019年10月23日民視新聞報導

俄羅斯研究人員23日表示,他們在北極偏遠地區發現了5座新島,目前還沒有命名研究人員是在法蘭士約瑟夫地群島,發現這5座新島嶼。島嶼原本為冰河覆蓋,因為冰河融化的關係,才被人發現。從無人機空拍畫面可以看到島嶼上有北極熊和海象等動物居住。

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※教你寫出一流的銷售文案?

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※回頭車貨運收費標準

※別再煩惱如何寫文案,掌握八大原則!

※超省錢租車方案

※產品缺大量曝光嗎?你需要的是一流包裝設計!

分類
發燒車訊

一種失去飛翔能力的鳥 使科學家重拾對環境的希望

環境資訊中心綜合外電;姜唯 編譯;林大利 審校

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※超省錢租車方案

※別再煩惱如何寫文案,掌握八大原則!

※回頭車貨運收費標準

※教你寫出一流的銷售文案?

FB行銷專家,教你從零開始的技巧

分類
發燒車訊

捨電動車 韓國現代氫動力貨卡將上市 目標零碳排征服瑞士高山

環境資訊中心綜合外電;姜唯 編譯;林大利 審校

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

※教你寫出一流的銷售文案?

※超省錢租車方案