java高并發(fā)的ReentrantLock重入鎖
synchronized的局限性
synchronized是java內(nèi)置的關(guān)鍵字,它提供了一種獨占的加鎖方式。synchronized的獲取和釋放鎖由jvm實現(xiàn),用戶不需要顯示的釋放鎖,非常方便,然而synchronized也有一定的局限性,例如:
1.當線程嘗試獲取鎖的時候,如果獲取不到鎖會一直阻塞,這個阻塞的過程,用戶無法控制
2.如果獲取鎖的線程進入休眠或者阻塞,除非當前線程異常,否則其他線程嘗試獲取鎖必須一直等待
JDK1.5之后發(fā)布,加入了Doug Lea實現(xiàn)的java.util.concurrent包。包內(nèi)提供了Lock類,用來提供更多擴展的加鎖功能。Lock彌補了synchronized的局限,提供了更加細粒度的加鎖功能。
ReentrantLock
ReentrantLock是Lock的默認實現(xiàn),在聊ReentranLock之前,我們需要先弄清楚一些概念:
1.可重入鎖:可重入鎖是指同一個線程可以多次獲得同一把鎖;ReentrantLock和關(guān)鍵字Synchronized都是可重入鎖
2.可中斷鎖:可中斷鎖時只線程在獲取鎖的過程中,是否可以相應(yīng)線程中斷操作。synchronized是不可中斷的,ReentrantLock是可中斷的
3.公平鎖和非公平鎖:公平鎖是指多個線程嘗試獲取同一把鎖的時候,獲取鎖的順序按照線程到達的先后順序獲取,而不是隨機插隊的方式獲取。synchronized是非公平鎖,而ReentrantLock是兩種都可以實現(xiàn),不過默認是非公平鎖
ReentrantLock基本使用
我們使用3個線程來對一個共享變量++操作,先使用synchronized
實現(xiàn),然后使用ReentrantLock
實現(xiàn)。
synchronized方式:
package com.itsoku.chat06; /** * 微信公眾號:路人甲Java,專注于java技術(shù)分享(帶你玩轉(zhuǎn) 爬蟲、分布式事務(wù)、異步消息服務(wù)、任務(wù)調(diào)度、分庫分表、大數(shù)據(jù)等),喜歡請關(guān)注! */ public class Demo2 { private static int num = 0; private static synchronized void add() { num++; } public static class T extends Thread { @Override public void run() { for (int i = 0; i < 10000; i++) { Demo2.add(); } } } public static void main(String[] args) throws InterruptedException { T t1 = new T(); T t2 = new T(); T t3 = new T(); t1.start(); t2.start(); t3.start(); t1.join(); t2.join(); t3.join(); System.out.println(Demo2.num); } }
ReentrantLock方式:
package com.itsoku.chat06; import java.util.concurrent.locks.ReentrantLock; /** * 微信公眾號:路人甲Java,專注于java技術(shù)分享(帶你玩轉(zhuǎn) 爬蟲、分布式事務(wù)、異步消息服務(wù)、任務(wù)調(diào)度、分庫分表、大數(shù)據(jù)等),喜歡請關(guān)注! */ public class Demo3 { private static int num = 0; private static ReentrantLock lock = new ReentrantLock(); private static void add() { lock.lock(); try { num++; } finally { lock.unlock(); } } public static class T extends Thread { @Override public void run() { for (int i = 0; i < 10000; i++) { Demo3.add(); } } } public static void main(String[] args) throws InterruptedException { T t1 = new T(); T t2 = new T(); T t3 = new T(); t1.start(); t2.start(); t3.start(); t1.join(); t2.join(); t3.join(); System.out.println(Demo3.num); } }
ReentrantLock的使用過程:
1.創(chuàng)建鎖:ReentrantLock lock = new ReentrantLock();
2.獲取鎖:lock.lock()
3.釋放鎖:lock.unlock();
對比上面的代碼,與關(guān)鍵字synchronized相比,ReentrantLock鎖有明顯的操作過程,開發(fā)人員必須手動的指定何時加鎖,何時釋放鎖,正是因為這樣手動控制,ReentrantLock對邏輯控制的靈活度要遠遠勝于關(guān)鍵字synchronized,上面代碼需要注意lock.unlock()一定要放在finally中,否則,若程序出現(xiàn)了異常,鎖沒有釋放,那么其他線程就再也沒有機會獲取這個鎖了。
ReentrantLock是可重入鎖
來驗證一下ReentrantLock是可重入鎖,實例代碼:
package com.itsoku.chat06; import java.util.concurrent.locks.ReentrantLock; /** * 微信公眾號:路人甲Java,專注于java技術(shù)分享(帶你玩轉(zhuǎn) 爬蟲、分布式事務(wù)、異步消息服務(wù)、任務(wù)調(diào)度、分庫分表、大數(shù)據(jù)等),喜歡請關(guān)注! */ public class Demo4 { private static int num = 0; private static ReentrantLock lock = new ReentrantLock(); private static void add() { lock.lock(); lock.lock(); try { num++; } finally { lock.unlock(); lock.unlock(); } } public static class T extends Thread { @Override public void run() { for (int i = 0; i < 10000; i++) { Demo4.add(); } } } public static void main(String[] args) throws InterruptedException { T t1 = new T(); T t2 = new T(); T t3 = new T(); t1.start(); t2.start(); t3.start(); t1.join(); t2.join(); t3.join(); System.out.println(Demo4.num); } }
上面代碼中add()方法中,當一個線程進入的時候,會執(zhí)行2次獲取鎖的操作,運行程序可以正常結(jié)束,并輸出和期望值一樣的30000,假如ReentrantLock是不可重入的鎖,那么同一個線程第2次獲取鎖的時候由于前面的鎖還未釋放而導(dǎo)致死鎖,程序是無法正常結(jié)束的。ReentrantLock命名也挺好的Re entrant Lock,和其名字一樣,可重入鎖。
代碼中還有幾點需要注意:
1.lock()方法和unlock()方法需要成對出現(xiàn),鎖了幾次,也要釋放幾次,否則后面的線程無法獲取鎖了;可以將add中的unlock刪除一個事實,上面代碼運行將無法結(jié)束
2.unlock()方法放在finally中執(zhí)行,保證不管程序是否有異常,鎖必定會釋放
ReentrantLock實現(xiàn)公平鎖
在大多數(shù)情況下,鎖的申請都是非公平的,也就是說,線程1首先請求鎖A,接著線程2也請求了鎖A。那么當鎖A可用時,是線程1可獲得鎖還是線程2可獲得鎖呢?這是不一定的,系統(tǒng)只是會從這個鎖的等待隊列中隨機挑選一個,因此不能保證其公平性。這就好比買票不排隊,大家都圍在售票窗口前,售票員忙的焦頭爛額,也顧及不上誰先誰后,隨便找個人出票就完事了,最終導(dǎo)致的結(jié)果是,有些人可能一直買不到票。而公平鎖,則不是這樣,它會按照到達的先后順序獲得資源。公平鎖的一大特點是:它不會產(chǎn)生饑餓現(xiàn)象,只要你排隊,最終還是可以等到資源的;synchronized關(guān)鍵字默認是有jvm內(nèi)部實現(xiàn)控制的,是非公平鎖。而ReentrantLock運行開發(fā)者自己設(shè)置鎖的公平性。
看一下jdk中ReentrantLock的源碼,2個構(gòu)造方法:
public ReentrantLock() { sync = new NonfairSync(); } public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); }
默認構(gòu)造方法創(chuàng)建的是非公平鎖。
第2個構(gòu)造方法,有個fair參數(shù),當fair為true的時候創(chuàng)建的是公平鎖,公平鎖看起來很不錯,不過要實現(xiàn)公平鎖,系統(tǒng)內(nèi)部肯定需要維護一個有序隊列,因此公平鎖的實現(xiàn)成本比較高,性能相對于非公平鎖來說相對低一些。因此,在默認情況下,鎖是非公平的,如果沒有特別要求,則不建議使用公平鎖。
公平鎖和非公平鎖在程序調(diào)度上是很不一樣,來一個公平鎖示例看一下:
package com.itsoku.chat06; import java.util.concurrent.locks.ReentrantLock; /** * 微信公眾號:路人甲Java,專注于java技術(shù)分享(帶你玩轉(zhuǎn) 爬蟲、分布式事務(wù)、異步消息服務(wù)、任務(wù)調(diào)度、分庫分表、大數(shù)據(jù)等),喜歡請關(guān)注! */ public class Demo5 { private static int num = 0; private static ReentrantLock fairLock = new ReentrantLock(true); public static class T extends Thread { public T(String name) { super(name); } @Override public void run() { for (int i = 0; i < 5; i++) { fairLock.lock(); try { System.out.println(this.getName() + "獲得鎖!"); } finally { fairLock.unlock(); } } } } public static void main(String[] args) throws InterruptedException { T t1 = new T("t1"); T t2 = new T("t2"); T t3 = new T("t3"); t1.start(); t2.start(); t3.start(); t1.join(); t2.join(); t3.join(); } }
運行結(jié)果輸出:
t1獲得鎖!
t2獲得鎖!
t3獲得鎖!
t1獲得鎖!
t2獲得鎖!
t3獲得鎖!
t1獲得鎖!
t2獲得鎖!
t3獲得鎖!
t1獲得鎖!
t2獲得鎖!
t3獲得鎖!
t1獲得鎖!
t2獲得鎖!
t3獲得鎖!
看一下輸出的結(jié)果,鎖時按照先后順序獲得的。
修改一下上面代碼,改為非公平鎖試試,如下:
ReentrantLock fairLock = new ReentrantLock(false);
運行結(jié)果如下:
t1獲得鎖! t3獲得鎖! t3獲得鎖! t3獲得鎖! t3獲得鎖! t1獲得鎖! t1獲得鎖! t1獲得鎖! t1獲得鎖! t2獲得鎖! t2獲得鎖! t2獲得鎖! t2獲得鎖! t2獲得鎖! t3獲得鎖!
可以看到t3可能會連續(xù)獲得鎖,結(jié)果是比較隨機的,不公平的。
ReentrantLock獲取鎖的過程是可中斷的
對于synchronized關(guān)鍵字,如果一個線程在等待獲取鎖,最終只有2種結(jié)果:
1.要么獲取到鎖然后繼續(xù)后面的操作
2.要么一直等待,直到其他線程釋放鎖為止
而ReentrantLock提供了另外一種可能,就是在等的獲取鎖的過程中(發(fā)起獲取鎖請求到還未獲取到鎖這段時間內(nèi))是可以被中斷的,也就是說在等待鎖的過程中,程序可以根據(jù)需要取消獲取鎖的請求。有些使用這個操作是非常有必要的。比如:你和好朋友越好一起去打球,如果你等了半小時朋友還沒到,突然你接到一個電話,朋友由于突發(fā)狀況,不能來了,那么你一定達到回府。中斷操作正是提供了一套類似的機制,如果一個線程正在等待獲取鎖,那么它依然可以收到一個通知,被告知無需等待,可以停止工作了。
示例代碼:
package com.itsoku.chat06; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReentrantLock; /** * 微信公眾號:路人甲Java,專注于java技術(shù)分享(帶你玩轉(zhuǎn) 爬蟲、分布式事務(wù)、異步消息服務(wù)、任務(wù)調(diào)度、分庫分表、大數(shù)據(jù)等),喜歡請關(guān)注! */ public class Demo6 { private static ReentrantLock lock1 = new ReentrantLock(false); private static ReentrantLock lock2 = new ReentrantLock(false); public static class T extends Thread { int lock; public T(String name, int lock) { super(name); this.lock = lock; } @Override public void run() { try { if (this.lock == 1) { lock1.lockInterruptibly(); TimeUnit.SECONDS.sleep(1); lock2.lockInterruptibly(); } else { lock2.lockInterruptibly(); TimeUnit.SECONDS.sleep(1); lock1.lockInterruptibly(); } } catch (InterruptedException e) { System.out.println("中斷標志:" + this.isInterrupted()); e.printStackTrace(); } finally { if (lock1.isHeldByCurrentThread()) { lock1.unlock(); } if (lock2.isHeldByCurrentThread()) { lock2.unlock(); } } } } public static void main(String[] args) throws InterruptedException { T t1 = new T("t1", 1); T t2 = new T("t2", 2); t1.start(); t2.start(); } }
先運行一下上面代碼,發(fā)現(xiàn)程序無法結(jié)束,使用jstack查看線程堆棧信息,發(fā)現(xiàn)2個線程死鎖了。
Found one Java-level deadlock: ============================= "t2": waiting for ownable synchronizer 0x0000000717380c20, (a java.util.concurrent.locks.ReentrantLock$NonfairSync), which is held by "t1" "t1": waiting for ownable synchronizer 0x0000000717380c50, (a java.util.concurrent.locks.ReentrantLock$NonfairSync), which is held by "t2"
lock1被線程t1占用,lock2倍線程t2占用,線程t1在等待獲取lock2,線程t2在等待獲取lock1,都在相互等待獲取對方持有的鎖,最終產(chǎn)生了死鎖,如果是在synchronized關(guān)鍵字情況下發(fā)生了死鎖現(xiàn)象,程序是無法結(jié)束的。
我們隊上面代碼改造一下,線程t2一直無法獲取到lock1,那么等待5秒之后,我們中斷獲取鎖的操作。主要修改一下main方法,如下:
T t1 = new T("t1", 1); T t2 = new T("t2", 2); t1.start(); t2.start(); TimeUnit.SECONDS.sleep(5); t2.interrupt();
新增了2行代碼 TimeUnit.SECONDS.sleep(5);t2.interrupt();,程序可以結(jié)束了,運行結(jié)果:
java.lang.InterruptedException at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchronizer.java:898) at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1222) at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:335) at com.itsoku.chat06.Demo6$T.run(Demo6.java:31) 中斷標志:false
從上面信息中可以看出,代碼的31行觸發(fā)了異常,中斷標志輸出:false
t2在31行一直獲取不到lock1的鎖,主線程中等待了5秒之后,t2線程調(diào)用了 interrupt()方法,將線程的中斷標志置為true,此時31行會觸發(fā) InterruptedException異常,然后線程t2可以繼續(xù)向下執(zhí)行,釋放了lock2的鎖,然后線程t1可以正常獲取鎖,程序得以繼續(xù)進行。線程發(fā)送中斷信號觸發(fā)InterruptedException異常之后,中斷標志將被清空。
關(guān)于獲取鎖的過程中被中斷,注意幾點:
1.ReentrankLock中必須使用實例方法 lockInterruptibly()獲取鎖時,在線程調(diào)用interrupt()方法之后,才會引發(fā) InterruptedException異常
2.線程調(diào)用interrupt()之后,線程的中斷標志會被置為true
3.觸發(fā)InterruptedException異常之后,線程的中斷標志有會被清空,即置為false
4.所以當線程調(diào)用interrupt()引發(fā)InterruptedException異常,中斷標志的變化是:false->true->false
ReentrantLock鎖申請等待限時
申請鎖等待限時是什么意思?一般情況下,獲取鎖的時間我們是不知道的,synchronized關(guān)鍵字獲取鎖的過程中,只能等待其他線程把鎖釋放之后才能夠有機會獲取到所。所以獲取鎖的時間有長有短。如果獲取鎖的時間能夠設(shè)置超時時間,那就非常好了。
ReentrantLock剛好提供了這樣功能,給我們提供了獲取鎖限時等待的方法 tryLock(),可以選擇傳入時間參數(shù),表示等待指定的時間,無參則表示立即返回鎖申請的結(jié)果:true表示獲取鎖成功,false表示獲取鎖失敗。
tryLock無參方法
看一下源碼中tryLock方法:
public boolean tryLock()
返回boolean類型的值,此方法會立即返回,結(jié)果表示獲取鎖是否成功,示例:
package com.itsoku.chat06; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReentrantLock; /** * 微信公眾號:路人甲Java,專注于java技術(shù)分享(帶你玩轉(zhuǎn) 爬蟲、分布式事務(wù)、異步消息服務(wù)、任務(wù)調(diào)度、分庫分表、大數(shù)據(jù)等),喜歡請關(guān)注! */ public class Demo8 { private static ReentrantLock lock1 = new ReentrantLock(false); public static class T extends Thread { public T(String name) { super(name); } @Override public void run() { try { System.out.println(System.currentTimeMillis() + ":" + this.getName() + "開始獲取鎖!"); //獲取鎖超時時間設(shè)置為3秒,3秒內(nèi)是否能否獲取鎖都會返回 if (lock1.tryLock()) { System.out.println(System.currentTimeMillis() + ":" + this.getName() + "獲取到了鎖!"); //獲取到鎖之后,休眠5秒 TimeUnit.SECONDS.sleep(5); } else { System.out.println(System.currentTimeMillis() + ":" + this.getName() + "未能獲取到鎖!"); } } catch (InterruptedException e) { e.printStackTrace(); } finally { if (lock1.isHeldByCurrentThread()) { lock1.unlock(); } } } } public static void main(String[] args) throws InterruptedException { T t1 = new T("t1"); T t2 = new T("t2"); t1.start(); t2.start(); } }
代碼中獲取鎖成功之后,休眠5秒,會導(dǎo)致另外一個線程獲取鎖失敗,運行代碼,輸出:
1563356291081:t2開始獲取鎖!
1563356291081:t2獲取到了鎖!
1563356291081:t1開始獲取鎖!
1563356291081:t1未能獲取到鎖!
可以看到t2獲取成功,t1獲取失敗了,tryLock()是立即響應(yīng)的,中間不會有阻塞。
tryLock有參方法
可以明確設(shè)置獲取鎖的超時時間,該方法簽名:
public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException
該方法在指定的時間內(nèi)不管是否可以獲取鎖,都會返回結(jié)果,返回true,表示獲取鎖成功,返回false表示獲取失敗。此方法由2個參數(shù),第一個參數(shù)是時間類型,是一個枚舉,可以表示時、分、秒、毫秒等待,使用比較方便,第1個參數(shù)表示在時間類型上的時間長短。此方法在執(zhí)行的過程中,如果調(diào)用了線程的中斷interrupt()方法,會觸發(fā)InterruptedException異常。
示例:
package com.itsoku.chat06; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReentrantLock; /** * 微信公眾號:路人甲Java,專注于java技術(shù)分享(帶你玩轉(zhuǎn) 爬蟲、分布式事務(wù)、異步消息服務(wù)、任務(wù)調(diào)度、分庫分表、大數(shù)據(jù)等),喜歡請關(guān)注! */ public class Demo7 { private static ReentrantLock lock1 = new ReentrantLock(false); public static class T extends Thread { public T(String name) { super(name); } @Override public void run() { try { System.out.println(System.currentTimeMillis() + ":" + this.getName() + "開始獲取鎖!"); //獲取鎖超時時間設(shè)置為3秒,3秒內(nèi)是否能否獲取鎖都會返回 if (lock1.tryLock(3, TimeUnit.SECONDS)) { System.out.println(System.currentTimeMillis() + ":" + this.getName() + "獲取到了鎖!"); //獲取到鎖之后,休眠5秒 TimeUnit.SECONDS.sleep(5); } else { System.out.println(System.currentTimeMillis() + ":" + this.getName() + "未能獲取到鎖!"); } } catch (InterruptedException e) { e.printStackTrace(); } finally { if (lock1.isHeldByCurrentThread()) { lock1.unlock(); } } } } public static void main(String[] args) throws InterruptedException { T t1 = new T("t1"); T t2 = new T("t2"); t1.start(); t2.start(); } }
程序中調(diào)用了ReentrantLock的實例方法 tryLock(3,TimeUnit.SECONDS),表示獲取鎖的超時時間是3秒,3秒后不管是否能否獲取鎖,該方法都會有返回值,獲取到鎖之后,內(nèi)部休眠了5秒,會導(dǎo)致另外一個線程獲取鎖失敗。
運行程序,輸出:
1563355512901:t2開始獲取鎖!
1563355512901:t1開始獲取鎖!
1563355512902:t2獲取到了鎖!
1563355515904:t1未能獲取到鎖!
輸出結(jié)果中分析,t2獲取到鎖了,然后休眠了5秒,t1獲取鎖失敗,t1打印了2條信息,時間相差3秒左右。
關(guān)于tryLock()方法和tryLock(long timeout, TimeUnit unit)方法,說明一下:
1.都會返回boolean值,結(jié)果表示獲取鎖是否成功
2.tryLock()方法,不管是否獲取成功,都會立即返回;而有參的tryLock方法會嘗試在指定的時間內(nèi)去獲取鎖,中間會阻塞的現(xiàn)象,在指定的時間之后會不管是否能夠獲取鎖都會返回結(jié)果
3.tryLock()方法不會響應(yīng)線程的中斷方法;而有參的tryLock方法會響應(yīng)線程的中斷方法,而出發(fā) InterruptedException異常,這個從2個方法的聲明上可以可以看出來
ReentrantLock其他常用的方法
isHeldByCurrentThread:實例方法,判斷當前線程是否持有ReentrantLock的鎖,上面代碼中有使用過。
獲取鎖的4種方法對比
獲取鎖的方法 | 是否立即響應(yīng)(不會阻塞) | 是否響應(yīng)中斷 |
---|---|---|
lock() | × | × |
lockInterruptibly() | × | √ |
tryLock() | √ | × |
tryLock(long timeout, TimeUnit unit) | × | √ |
總結(jié)
1.ReentrantLock可以實現(xiàn)公平鎖和非公平鎖
2.ReentrantLock默認實現(xiàn)的是非公平鎖
3.ReentrantLock的獲取鎖和釋放鎖必須成對出現(xiàn),鎖了幾次,也要釋放幾次
4.釋放鎖的操作必須放在finally中執(zhí)行
5.lockInterruptibly()
實例方法可以相應(yīng)線程的中斷方法,調(diào)用線程的interrupt()方法時,lockInterruptibly()方法會觸發(fā) InterruptedException異常
6.關(guān)于 InterruptedException異常說一下,看到方法聲明上帶有 throwsInterruptedException,表示該方法可以相應(yīng)線程中斷,調(diào)用線程的interrupt()方法時,這些方法會觸發(fā) InterruptedException異常,觸發(fā)InterruptedException時,線程的中斷中斷狀態(tài)會被清除。所以如果程序由于調(diào)用 interrupt()方法而觸發(fā) InterruptedException異常,線程的標志由默認的false變?yōu)閠ure,然后又變?yōu)閒alse
7.實例方法tryLock()獲會嘗試獲取鎖,會立即返回,返回值表示是否獲取成功
8.實例方法tryLock(long timeout, TimeUnit unit)會在指定的時間內(nèi)嘗試獲取鎖,指定的時間內(nèi)是否能夠獲取鎖,都會返回,返回值表示是否獲取鎖成功,該方法會響應(yīng)線程的中斷
本篇文章就到這里了,希望能夠給你帶來幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!
相關(guān)文章
解決Mybatis返回update后影響的行數(shù)問題
這篇文章主要介紹了解決Mybatis返回update后影響的行數(shù)問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-11-11修改request的parameter的幾種方式總結(jié)
這篇文章主要介紹了修改request的parameter的幾種方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-08-08Maven介紹與配置+IDEA集成Maven+使用Maven命令小結(jié)
Maven是Apache軟件基金會的一個開源項目,是一個優(yōu)秀的項目構(gòu)建管理工具,它用來幫助開發(fā)者管理項目中的 jar,以及 jar 之間的依賴關(guān)系、完成項目的編譯、測試、打包和發(fā)布等工作,本文給大家介紹Maven介紹與配置+IDEA集成Maven+使用Maven命令,感興趣的朋友一起看看吧2024-01-01Java C++解決在排序數(shù)組中查找數(shù)字出現(xiàn)次數(shù)問題
本文終于介紹了分別通過Java和C++實現(xiàn)統(tǒng)計一個數(shù)字在排序數(shù)組中出現(xiàn)的次數(shù)。文中詳細介紹了實現(xiàn)思路,感興趣的小伙伴可以跟隨小編學(xué)習(xí)一下2021-12-12Java 模擬數(shù)據(jù)庫連接池的實現(xiàn)代碼
這篇文章主要介紹了Java 模擬數(shù)據(jù)庫連接池的實現(xiàn),本文通過實例代碼給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-02-02Jasypt的StandardPBEByteEncryptor使用源碼解析
這篇文章主要介紹了Jasypt的StandardPBEByteEncryptor使用源碼解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-09-09