摘要:如果釋放的次數(shù)多了,會(huì)得到一個(gè)異常反之則會(huì)導(dǎo)致當(dāng)前線程一直持有該鎖,導(dǎo)致其他線程無(wú)法進(jìn)入臨界區(qū)。,若沒(méi)有參數(shù),當(dāng)前線程會(huì)嘗試獲得鎖,如果申請(qǐng)鎖成功,則返回,否則立即返回。閉鎖閉鎖是一種同步工具類,可以延遲線程的進(jìn)度直到其到達(dá)終止?fàn)顟B(tài)。
為了更好地支持并發(fā)程序,“鎖”是較為常用的同步方法之一。在高并發(fā)環(huán)境下,激勵(lì)的鎖競(jìng)爭(zhēng)會(huì)導(dǎo)致程序的性能下降。
所以我們將在這里討論一些有關(guān)于鎖使用和問(wèn)題以及一些注意事項(xiàng)。
重入鎖可以完全替代Synchronized關(guān)鍵字,但其必須顯式的調(diào)用unlock。建議視為Synchronized的高級(jí)版,比起Synchronized關(guān)鍵字,其可定時(shí)、可輪詢并含有可中斷的鎖獲取操作,公平隊(duì)列以及非塊結(jié)構(gòu)的鎖。
/** * 重入鎖演示 * */ public class ReeterLock implements Runnable{ public static ReentrantLock lock = new ReentrantLock(); public static int i = 0; @Override public void run() { for (int j=0;j<10000;j++){ //手動(dòng)上鎖,可以上N把,這里是為了演示 lock.lock(); lock.lock(); lock.lock(); try { i ++; } finally { //無(wú)論如何必須釋放鎖,上幾把 釋放幾把 lock.unlock(); lock.unlock(); lock.unlock(); } } } public static void main(String[] a) throws InterruptedException { ReeterLock rl = new ReeterLock(); Thread t1 = new Thread(rl); Thread t2 = new Thread(rl); t1.start(); t2.start(); t1.join(); t2.join(); System.out.print(i); } }
那么我們可以明顯的看到重入鎖保護(hù)著臨界區(qū)資源i,確保多線程對(duì)i操作的安全。在demo中我們也是加了3次鎖并釋放了3次鎖。
需要注意的是,如果同一線程多次獲得鎖,那么在釋放鎖的時(shí)候,也必須釋放相同次數(shù)。如果釋放的次數(shù)多了,會(huì)得到一個(gè)java.lang.IllegalMonitorStateException異常;反之則會(huì)導(dǎo)致當(dāng)前線程一直持有該鎖,導(dǎo)致其他線程無(wú)法進(jìn)入臨界區(qū)。
中斷響應(yīng):ReentrantLock.lockInterruptibly()對(duì)于synchronized來(lái)說(shuō),如果一個(gè)線程在等待鎖,那么結(jié)果只有兩種情況,要么它獲得這把鎖繼續(xù)執(zhí)行,要么它就保持等待。而使用重入鎖,則提供另外一種可能,那就是線程可以被中斷。也就是在等待鎖,程序可以根據(jù)需要取消對(duì)鎖的請(qǐng)求。有些時(shí)候,這么做是非常有必要的。
lockInterruptibly()方法是一個(gè)可以對(duì)中斷進(jìn)行響應(yīng)的鎖申請(qǐng)動(dòng)作,即在等待鎖的過(guò)程中,中斷響應(yīng)。
public class IntLock implements Runnable{ public static ReentrantLock lock1 = new ReentrantLock(); public static ReentrantLock lock2 = new ReentrantLock(); int lock; /** * 控制加鎖順序,制造死鎖 * @param lock */ public IntLock(int lock) { this.lock = lock; } @Override public void run() { try { /** * 1號(hào)線程,先占用 1號(hào)鎖,再申請(qǐng) 2號(hào)鎖 * 2號(hào)線程,先占用 2號(hào)鎖,再申請(qǐng) 1號(hào)鎖 * 這樣就很容易造成兩個(gè)線程相互等待. */ if (lock == 1){ //加入優(yōu)先響應(yīng)中斷的鎖 lock1.lockInterruptibly(); System.out.println(Thread.currentThread().getName() + " 進(jìn)入..."); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } /** * 這時(shí)候,1號(hào)線程 想要持有 2號(hào)鎖 ,但是2號(hào)線程已經(jīng)先占用了2號(hào)鎖,所以1 號(hào)線程等待. * 2號(hào)線程也一樣,占用著2號(hào)鎖 不釋放,還想申請(qǐng)1號(hào)鎖,而1號(hào)鎖 被1號(hào)線程占用且不釋放. */ lock2.lockInterruptibly(); System.out.println(Thread.currentThread().getName() + " 完成..."); }else { lock2.lockInterruptibly(); System.out.println(Thread.currentThread().getName() + " 進(jìn)入..."); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } lock1.lockInterruptibly(); System.out.println(Thread.currentThread().getName() + " 完成..."); } } catch (InterruptedException e) { System.out.println(Thread.currentThread().getName() + " 被中斷,報(bào)異常..."); e.printStackTrace(); } finally { if (lock1.isHeldByCurrentThread()) { System.out.println(Thread.currentThread().getName() + " 釋放..."); lock1.unlock(); } if (lock2.isHeldByCurrentThread()) { System.out.println(Thread.currentThread().getName() + " 釋放..."); lock2.unlock(); } System.out.println(Thread.currentThread().getName() + " 線程退出..."); } } public static void main(String[] a) throws InterruptedException { IntLock re1 = new IntLock(1); IntLock re2 = new IntLock(2); Thread t1 = new Thread(re1," 1 號(hào)線程 "); Thread t2 = new Thread(re2," 2 號(hào)線程 "); t1.start(); t2.start(); //主線程sleep 2秒,讓兩個(gè)線程相互競(jìng)爭(zhēng)資源.造成死鎖 Thread.sleep(2000); //中斷2號(hào)線程 t2.interrupt(); /* 執(zhí)行結(jié)果: 1 號(hào)線程 進(jìn)入... 2 號(hào)線程 進(jìn)入... 2 號(hào)線程 被中斷,報(bào)異常... // 執(zhí)行 t2.interrupt(); java.lang.InterruptedException 2 號(hào)線程 釋放... at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchronizer.java:898) 2 號(hào)線程 線程退出... at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1222) 1 號(hào)線程 完成... // 只有1號(hào)線程能執(zhí)行完成 at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:335) 1 號(hào)線程 釋放... at com.iboray.javacore.Thread.T3.IntLock.run(IntLock.java:55) 1 號(hào)線程 釋放... at java.lang.Thread.run(Thread.java:745) 1 號(hào)線程 線程退出... */ } }鎖申請(qǐng)等待限時(shí):ReentrantLock.tryLock
除了等待外部通之外,避免死鎖還有另外一種方法,就是限時(shí)等待,給定一個(gè)等待時(shí)間讓線程自動(dòng)放棄。
tryLock(時(shí)長(zhǎng),計(jì)時(shí)單位),若超過(guò)設(shè)定時(shí)長(zhǎng)還沒(méi)得到鎖就返回false,若成功獲得鎖就返回true。
tryLock(),若沒(méi)有參數(shù),當(dāng)前線程會(huì)嘗試獲得鎖,如果申請(qǐng)鎖成功,則返回true,否則立即返回false。這種模式不會(huì)引起線程等待,因此不會(huì)產(chǎn)生死鎖。
public class TimeLock implements Runnable{ public static ReentrantLock lock = new ReentrantLock(); @Override public void run() { System.out.println(Thread.currentThread().getName() + " 申請(qǐng)資源..."); try { //申請(qǐng)3秒,如果獲取不到,返回false,退出. if (lock.tryLock(5, TimeUnit.SECONDS)) { System.out.println(Thread.currentThread().getName() + " 獲得資源,開始執(zhí)行..."); //持有鎖6秒 Thread.sleep(6000); System.out.println(Thread.currentThread().getName() + " 執(zhí)行完成..."); }else { System.out.println(Thread.currentThread().getName() + " 申請(qǐng)鎖失敗..."); } } catch (InterruptedException e) { System.out.println(Thread.currentThread().getName() + " 中斷..."); e.printStackTrace(); }finally { if (lock.isHeldByCurrentThread()) { System.out.println(Thread.currentThread().getName() + " 釋放鎖..."); lock.unlock(); } } } public static void main(String[] a) throws InterruptedException { TimeLock re = new TimeLock(); Thread t1 = new Thread(re," 1 號(hào)線程 "); Thread t2 = new Thread(re," 2 號(hào)線程 "); t1.start(); t2.start(); /* 執(zhí)行結(jié)果: 1 號(hào)線程 申請(qǐng)資源... 2 號(hào)線程 申請(qǐng)資源... 1 號(hào)線程 獲得資源,開始執(zhí)行... 2 號(hào)線程 釋放鎖... //等待了5秒后,依然申請(qǐng)不到鎖,就返回false 1 號(hào)線程 執(zhí)行完成... 1 號(hào)線程 釋放鎖... */ } }
由于占用鎖的線程會(huì)持有鎖長(zhǎng)達(dá)6秒,故另一個(gè)線程無(wú)法在5秒的等待時(shí)間內(nèi)獲取鎖,因此,請(qǐng)求鎖會(huì)失敗。
公平鎖:ReentrantLock(true)在大多數(shù)情況下,鎖的申請(qǐng)都是非公平的。也就是說(shuō),線程1首先請(qǐng)求了鎖A,接著線程2也請(qǐng)求了鎖A。那么當(dāng)鎖A可用時(shí),是線程1還是線程2可以獲得鎖呢?顯然這是不一定的。系統(tǒng)只會(huì)從這個(gè)鎖的等待隊(duì)列中隨機(jī)挑選一個(gè),因此不能保證其公平性。
公平鎖會(huì)按照實(shí)際的先后順序,保證先到先得,它不會(huì)產(chǎn)生饑餓,只要排隊(duì),最終都可以等到資源。在創(chuàng)建重入鎖時(shí),通過(guò)有參構(gòu)造函數(shù),傳入boolean類型的參數(shù),true表示是公平鎖。實(shí)現(xiàn)公平所必然要維護(hù)一個(gè)有序隊(duì)列,所以公平鎖的實(shí)現(xiàn)成本高,性能相對(duì)也非常低,默認(rèn)情況下,鎖是非公平的。
public class ReentrantLockExample3 implements Runnable{ //創(chuàng)建公平鎖 public static ReentrantLock lock = new ReentrantLock(true); static int i = 0; @Override public void run() { for (int j = 0;j<5;j++){ lock.lock(); try { i++; System.out.println(Thread.currentThread().getName() + " 獲得鎖 " + i); } finally { lock.unlock(); } } } public static void main(String[] a) throws InterruptedException { ReentrantLockExample3 re = new ReentrantLockExample3(); Thread t1 = new Thread(re," 1 號(hào)線程 "); Thread t2 = new Thread(re," 2 號(hào)線程 "); Thread t3 = new Thread(re," 3 號(hào)線程 "); Thread t4 = new Thread(re," 4 號(hào)線程 "); t1.start(); t2.start(); t3.start(); t4.start(); /* 執(zhí)行結(jié)果: 1 號(hào)線程 獲得鎖 1 2 號(hào)線程 獲得鎖 2 3 號(hào)線程 獲得鎖 3 4 號(hào)線程 獲得鎖 4 1 號(hào)線程 獲得鎖 5 2 號(hào)線程 獲得鎖 6 3 號(hào)線程 獲得鎖 7 4 號(hào)線程 獲得鎖 8 ..... 4 號(hào)線程 獲得鎖 16 1 號(hào)線程 獲得鎖 17 2 號(hào)線程 獲得鎖 18 3 號(hào)線程 獲得鎖 19 4 號(hào)線程 獲得鎖 20 */ } }ReentrantLock的以上幾個(gè)重要的方法
lock() 獲取鎖,如果鎖被占用,則等待
lockInterruptibly() 獲取鎖,但優(yōu)先響應(yīng)中斷
tryLock() 嘗試獲取鎖,如果成功返回true,否則返回false。該方法不等待,立即返回。
tryLock(long time,TimeUnit unit) 在給定時(shí)間內(nèi)獲取鎖。
unlock() 釋放鎖。
就重入鎖實(shí)現(xiàn)來(lái)看,它主要集中在Java 層面。在重入鎖實(shí)現(xiàn)中,主要包含三個(gè)要素:
原子狀態(tài)。原子狀態(tài)使用CAS操作來(lái)存儲(chǔ)當(dāng)前鎖的狀態(tài),判斷鎖是否已經(jīng)被別的線程持有。
等待隊(duì)列。所有沒(méi)有請(qǐng)求成功的線程都進(jìn)入等待隊(duì)列進(jìn)行等待。當(dāng)有線程釋放鎖后,系統(tǒng)就從當(dāng)前等待隊(duì)列中喚醒一個(gè)線程繼續(xù)工作。
阻塞原語(yǔ)park()和unpack(),用來(lái)掛起和恢復(fù)線程。沒(méi)有得到鎖的線程將會(huì)被掛起。
重入鎖的好搭檔:Condition條件如果大家理解了Object.wait()和Object.notify()方法的話,就能很容易地理解Condition對(duì)象了。它和wait()和notify()方法的作用是大致相同的。但是wait()方法和notify()方法是和synchronized關(guān)鍵字合作使用的,而Condtion是與重入鎖相關(guān)聯(lián)的。通過(guò)Lock接口(重入鎖就實(shí)現(xiàn)了這個(gè)接口)的Condtion newCondition()方法可以生成一個(gè)與當(dāng)前重入鎖綁定的Condition實(shí)例。利用Condition對(duì)象,我們就可以讓線程在合適的時(shí)間等待,或者在某一個(gè)特定的時(shí)刻得到通知,繼續(xù)執(zhí)行。
Condition接口提供的基本方法如下:
void await() throws InterruptedException; void awaitUninterruptibly(); long awaitNanos(long nanosTimeout) throws InterruptedException; boolean await(long time, TimeUnit unit) throws InterruptedException; boolean awaitUntil(Date deadline) throws InterruptedException; void signal(); void signalAll();
以上方法含義如下
await()方法會(huì)使當(dāng)前線程等待,同時(shí)釋放當(dāng)前鎖,當(dāng)其他線程中使用signal()或者signalAll()方法時(shí)候,線程會(huì)重新獲得鎖并繼續(xù)執(zhí)行?;蛘弋?dāng)線程被中斷時(shí),也能跳出等待。這和Object.wait()方法很相似。
awaitUninterruptibly()和await()方法類似,但它不會(huì)再等待過(guò)程中響應(yīng)中斷。
singal() 用于喚醒一個(gè)等待隊(duì)列中的線程。singalAll()是喚醒所有等待線程。
public class ConditionExample implements Runnable{ public static ReentrantLock lock = new ReentrantLock(); public static Condition condition = lock.newCondition(); @Override public void run() { try { lock.lock(); System.out.println(Thread.currentThread().getName() + " 獲取到鎖..."); //等待 condition.await(); System.out.println(Thread.currentThread().getName() + " 執(zhí)行完成"); } catch (InterruptedException e) { e.printStackTrace(); }finally { //釋放鎖 lock.unlock(); System.out.println(Thread.currentThread().getName() + " 釋放鎖"); } } public static void main(String[] a) throws InterruptedException { ConditionExample re = new ConditionExample(); Thread t1 = new Thread(re,"1 號(hào)線程 "); t1.start(); //主線程sleep,1號(hào)線程會(huì)一直等待.直到獲取到1號(hào)線程的鎖資源,并將其喚醒. Thread.sleep(2000); //獲得鎖 lock.lock(); //喚醒前必須獲得當(dāng)前資源對(duì)象的鎖 condition.signal(); //釋放鎖 lock.unlock(); } }ReadWriteLock讀寫鎖
ReadWriteLock是JDK5中提供的讀寫分離鎖。讀寫分離鎖可以有效地幫助減少鎖競(jìng)爭(zhēng),以提升系統(tǒng)性能。用鎖分離的機(jī)制來(lái)提升性能非常容易理解,比如線程A1、A2、A3進(jìn)行寫操作,B1、B2、B3進(jìn)行讀操作,如果使用重入鎖或者內(nèi)部鎖,則理論上說(shuō)所有讀之間、讀與寫之間、寫和寫之間都是串行操作。當(dāng)B1進(jìn)行讀取時(shí),B2、B3則需要等待鎖。由于讀操作并不對(duì)數(shù)據(jù)的完整性造成破壞,這種等待顯然是不合理的。因此,讀寫鎖就有了發(fā)揮功能的余地。
在這種情況下,讀寫鎖運(yùn)行多個(gè)線程同時(shí)讀。但是考慮到數(shù)據(jù)完整性,寫寫操作和讀寫操作間依然是需要互相等待和持有鎖的??偟膩?lái)說(shuō),讀寫鎖的訪問(wèn)約束如下表。
讀 | 寫 | |
---|---|---|
讀 | 非阻塞 | 阻塞 |
寫 | 阻塞 | 阻塞 |
如果在系統(tǒng)中,讀的次數(shù)遠(yuǎn)遠(yuǎn)大于寫的操作,讀寫鎖就可以發(fā)揮最大的功效,提升系統(tǒng)的性能。
栗子:
public class ReadWriteLockExample { //創(chuàng)建普通重入鎖 private static Lock lock = new ReentrantLock(); //創(chuàng)建讀寫分離鎖 private static ReentrantReadWriteLock rwlock = new ReentrantReadWriteLock(); //創(chuàng)建讀鎖 private static Lock readLock = rwlock.readLock(); //創(chuàng)建寫鎖 private static Lock writeLock = rwlock.writeLock(); private int value; public Object HandleRead(Lock lock) throws InterruptedException { try { //上鎖 lock.lock(); //模擬處理業(yè)務(wù) Thread.sleep(1000); System.out.println(Thread.currentThread().getName() + " Read..."); return value; } finally { //釋放鎖 lock.unlock(); } } public void HandleWrite(Lock lock,int index) throws InterruptedException { try { lock.lock(); Thread.sleep(1000); value = index; System.out.println(Thread.currentThread().getName() + " Write..."); }finally { lock.unlock(); } } public static void main(String[] a ) throws InterruptedException { final ReadWriteLockExample rwle = new ReadWriteLockExample(); //創(chuàng)建讀方法 Runnable readR = new Runnable() { @Override public void run() { try { //rwle.HandleRead(lock); //普通鎖 rwle.HandleRead(readLock); } catch (InterruptedException e) { e.printStackTrace(); } } }; //創(chuàng)建寫方法 Runnable writeR = new Runnable() { @Override public void run() { try { //rwle.HandleWrite(lock,new Random().nextInt()); //普通鎖 rwle.HandleWrite(writeLock,new Random().nextInt()); } catch (InterruptedException e) { e.printStackTrace(); } } }; //18次讀 for (int i=0;i<18;i++){ Thread s = new Thread(readR); s.start(); } //2次寫 for (int i=18;i<20;i++){ Thread s = new Thread(writeR); s.start(); } /** * 結(jié)論: * * 用普通鎖運(yùn)行,大約執(zhí)行20秒左右 * * 用讀寫分離鎖,大約執(zhí)行3秒左右 * */ } }
在讀鎖和寫鎖之間的交互可以采用多種實(shí)現(xiàn)方式。ReadWriteLock中的一些可選實(shí)現(xiàn)包括:
釋放優(yōu)先:當(dāng)一個(gè)寫入操作釋放寫入鎖時(shí),并且隊(duì)列中同時(shí)存在讀線程和寫線程,那么應(yīng)該優(yōu)先選擇哪一個(gè)線程。
讀線程插隊(duì):如果鎖是由讀線程持有,但是寫線程還在等待,是否允許新到的讀線程獲得訪問(wèn)權(quán),還是應(yīng)在寫線程后面等待?若允許的話可以提高并發(fā)性但是可能造成寫線程的饑餓。
重入性:讀取鎖和寫入鎖是否可重入。
降級(jí)和升級(jí):若一個(gè)線程持有寫鎖可否在繼續(xù)持有寫鎖的狀態(tài)下獲取讀鎖?這可能會(huì)使寫鎖“降級(jí)”為讀鎖。讀鎖是否優(yōu)先于其它正在等待的讀線程和寫線程而升級(jí)為一個(gè)寫鎖?在大多數(shù)讀寫鎖實(shí)現(xiàn)中不支持“升級(jí)”,因?yàn)檫@樣容易死鎖(兩個(gè)讀線程試圖同時(shí)升級(jí)為寫鎖,那么二者都不會(huì)釋放寫鎖)。
閉鎖閉鎖是一種同步工具類,可以延遲線程的進(jìn)度直到其到達(dá)終止?fàn)顟B(tài)。閉鎖的作用相當(dāng)于一扇門:在閉鎖到達(dá)結(jié)束狀態(tài)之前,這扇門一直是關(guān)閉的,并且沒(méi)有任何線程能通過(guò),當(dāng)?shù)竭_(dá)結(jié)束狀態(tài)時(shí),這扇門會(huì)打開并允許所有的線程通過(guò)。當(dāng)閉鎖到達(dá)結(jié)束狀態(tài)后,將不會(huì)再改變狀態(tài),因此這扇門將永遠(yuǎn)保持打開狀態(tài)。閉鎖可以用來(lái)確保某些活動(dòng)直到其他活動(dòng)都完成才繼續(xù)執(zhí)行,例如:
確保某個(gè)計(jì)算在其需要的所有資源都被初始化后才繼續(xù)執(zhí)行。二元閉鎖(包括兩個(gè)狀態(tài))可以用來(lái)表示“資源R已經(jīng)被初始化”,而所有需要R的操作都必須先在這個(gè)比鎖上等待。
確保某個(gè)服務(wù)在其依賴的所有其他服務(wù)都已經(jīng)啟動(dòng)之后才啟動(dòng)。每個(gè)服務(wù)都有一個(gè)相關(guān)的二元閉鎖。當(dāng)啟動(dòng)服務(wù)S時(shí),將首先在S依賴的其他服務(wù)的閉鎖上等待,在所有依賴的服務(wù)都啟動(dòng)后會(huì)釋放閉鎖S,這樣其他依賴S的服務(wù)才能繼續(xù)執(zhí)行。
等待直到某個(gè)操作的所有參與者(例如,在多玩家游戲中的所有玩家)都就緒再繼續(xù)執(zhí)行。在這種情況中,當(dāng)所有的玩家都執(zhí)行就緒時(shí),閉鎖將到達(dá)結(jié)束狀態(tài)。
CountDownLatchCountDownLatch 就是一種靈活的閉鎖實(shí)現(xiàn),可以在上述的各種情況中使用,它可以使一個(gè)或多個(gè)線程等待一組時(shí)間發(fā)生CountDown在英文中意為倒計(jì)時(shí),Latch為門閂。閉鎖的狀態(tài)包括一個(gè)計(jì)數(shù)器,該計(jì)數(shù)器被初始化為一個(gè)正數(shù),表示需要等待的事情數(shù)量。countDown方法遞減計(jì)數(shù)器,表示有一個(gè)事件已經(jīng)發(fā)生了,而await方法等待計(jì)數(shù)器達(dá)到零,這表示所有需要等待的事情都已經(jīng)發(fā)生。如果計(jì)數(shù)器的值是非零,那么await會(huì)一直阻塞直到計(jì)數(shù)器為零,或者等待中的線程中斷,或者等待超時(shí)。因此,這個(gè)工具通常用來(lái)控制線程等待,它可以讓某一個(gè)線程等待直到倒計(jì)時(shí)結(jié)束,再開始執(zhí)行。
CountDownLatch的構(gòu)造函數(shù)接收一個(gè)整數(shù)作為參數(shù),即當(dāng)前這個(gè)計(jì)數(shù)器的技術(shù)個(gè)數(shù)。
public CountDownLatch(int count)
下面這個(gè)簡(jiǎn)單的示例,演示了CountDownLatch的使用。
public class CountDownLatchExample implements Runnable{ static final CountDownLatch cdl = new CountDownLatch(10); static final CountDownLatchExample cdle = new CountDownLatchExample(); @Override public void run() { try { Thread.sleep(new Random().nextInt(10) * 1000); System.out.println(Thread.currentThread().getName() + " 部件檢查完畢..."); //一個(gè)線程完成工作,倒計(jì)時(shí)器減1 cdl.countDown(); } catch (InterruptedException e) { e.printStackTrace(); } } public static void main(String[] a) throws InterruptedException { ExecutorService exec = Executors.newFixedThreadPool(10); for (int i=0;i<10;i++){ exec.submit(cdle); } //等待所有線程完成,主線程才繼續(xù)執(zhí)行 cdl.await(); System.out.println(Thread.currentThread().getName() + " 所有檢查完成,上跑道起飛..."); //關(guān)閉線程池 exec.shutdown(); } }FutureTask
FutureTask也可以用做閉鎖。FutureTask表示的計(jì)算是通過(guò)Callable來(lái)實(shí)現(xiàn)的,相當(dāng)于一種可生成結(jié)果的Runnable,并且可以處于以下3種狀態(tài):
等待運(yùn)行(Waiting to run)
正在運(yùn)行(Running)
運(yùn)行完成(Completed)
Future.get的行為取決于任務(wù)的狀態(tài)。如果任務(wù)已經(jīng)完成,那么get會(huì)立即返回結(jié)果,否則get將阻塞直到任務(wù)進(jìn)入完成狀態(tài),然后返回結(jié)果或者拋出異常。FutureTask將計(jì)算結(jié)果從執(zhí)行的計(jì)算的線程傳遞到獲取這個(gè)結(jié)果的線程,而FutureTask的規(guī)劃確保了這種傳遞過(guò)程能夠?qū)崿F(xiàn)結(jié)果的安全發(fā)布。
FutureTask在ExeCutor中表示異步任務(wù),此外還可以用來(lái)表示一些時(shí)間較長(zhǎng)的計(jì)算,這些計(jì)算可以在使用計(jì)算結(jié)果之前啟動(dòng)。
信號(hào)量技術(shù)信號(hào)量(Counting Semaphore)用來(lái)控制同時(shí)訪問(wèn)某個(gè)特定資源的操作數(shù)量,或者同時(shí)執(zhí)行某個(gè)指定操作的數(shù)量。計(jì)數(shù)信號(hào)量還可以用來(lái)實(shí)現(xiàn)某種資源池,或者對(duì)容器施加邊界。
信號(hào)量為多線程協(xié)作提供了更為強(qiáng)大的控制方法。廣義上說(shuō),信號(hào)量是對(duì)鎖的擴(kuò)展。無(wú)論是內(nèi)部鎖synchronized還是重入鎖ReentrantLock,一次都只允許一個(gè)線程訪問(wèn)一個(gè)資源,而信號(hào)量卻可以指定多個(gè)線程,同時(shí)訪問(wèn)某一個(gè)資源。信號(hào)量主要提供了以下構(gòu)造函數(shù):
public Semaphore(int permits) public Semaphore(int permits,boolean fair) //第二個(gè)參數(shù)可以指定是否公平
在構(gòu)造信號(hào)量對(duì)象時(shí),必須要指定信號(hào)量的準(zhǔn)入數(shù),即同時(shí)能申請(qǐng)多少個(gè)許可。當(dāng)每個(gè)線程每次只申請(qǐng)一個(gè)許可時(shí),這就相當(dāng)于指定了同時(shí)有多少個(gè)線程可以訪問(wèn)某一個(gè)資源。信號(hào)量的主要邏輯方法有:
public void acquire() //嘗試獲得一個(gè)準(zhǔn)入的許可。若無(wú)法獲得,則線程會(huì)等待,直到有線程釋放一個(gè)許可或者當(dāng)前線程被中斷 public void acquireUninterruptibly()//和acquire()類似,但是不響應(yīng)中斷 public boolean tryAcquire()//嘗試獲得一個(gè)許可,成功true失敗fasle,不會(huì)等待,立刻返回 public boolean tryAcquire(long timeout,TimeUnit unit) public void release()//線程訪問(wèn)資源結(jié)束后,釋放一個(gè)許可,以使其他等待許可的線程進(jìn)行資源訪問(wèn)
栗子:
public class SemaphoreExample implements Runnable { //指定信號(hào)量,同時(shí)可以有5個(gè)線程訪問(wèn)資源 public static final Semaphore s = new Semaphore(5); @Override public void run() { try { //申請(qǐng)信號(hào)量,也可以直接使用 s.acquire(); if (s.tryAcquire(1500, TimeUnit.SECONDS)) { //模擬耗時(shí)操作 Thread.sleep(2000); System.out.println(Thread.currentThread().getName() + " 完成了任務(wù).."); //離開時(shí)必須釋放信號(hào)量,不然會(huì)導(dǎo)致信號(hào)量泄露——申請(qǐng)了但沒(méi)有釋放 s.release(); } } catch (InterruptedException e) { e.printStackTrace(); } } public static void main(String[] a) throws InterruptedException { //申請(qǐng)20個(gè)線程 ExecutorService exec = Executors.newFixedThreadPool(20); final SemaphoreExample re = new SemaphoreExample(); for (int i=0;i<20;i++){ exec.submit(re); } exec.shutdown(); } }
Semaphore中管理者一組虛擬的許可(permit),許可的初始數(shù)量可通過(guò)構(gòu)造函數(shù)來(lái)指定。在執(zhí)行操作時(shí)可以先獲得許可(只要還有剩余的許可),并在使用以后釋放許可。如果沒(méi)有許可,那么acquire將阻塞直到有許可(或者被中斷或者操作超時(shí))。release方法將返回一個(gè)許可給信號(hào)量。計(jì)算信號(hào)量的一種簡(jiǎn)化形式是二值信號(hào)量,即初始值為1的Semaphore。二值信號(hào)量可以用做互斥體(mutex),并具備不可重入的加鎖語(yǔ)義:誰(shuí)擁有這個(gè)唯一的許可,誰(shuí)就擁有了互斥鎖。
同樣,我們也可以使用Semaphore將任何一種容器變成有界阻塞容器。
CyclicBarrier和之前的CountDownLatch類似,它(循環(huán)柵欄)也可以實(shí)現(xiàn)線程間的技術(shù)等待,但它的功能比CountDownLatch更加復(fù)雜強(qiáng)大。它能阻塞一組線程直到某個(gè)事件發(fā)生。因此,柵欄可以用于實(shí)現(xiàn)一些協(xié)議,例如“開會(huì)一定要在xx地方集合,等其他人到了再討論下一步要做的事情”。
CyclicBarrier的使用場(chǎng)景也很豐富。比如,司令下達(dá)命令,要求10個(gè)士兵一起去完成一項(xiàng)任務(wù)。這時(shí),就會(huì)要求10個(gè)士兵先集合報(bào)道,接著,一起雄赳赳氣昂昂地執(zhí)行任務(wù)。當(dāng)10個(gè)士兵把自己手頭的任務(wù)都執(zhí)行完成了,那么司令才能對(duì)外宣布,任務(wù)完成!
下面的栗子使用CyclicBarrier演示了上述司機(jī)命令士兵完成任務(wù)的場(chǎng)景。
public class CyclicBarrierExample { public static class Soldier implements Runnable{ private String name; private CyclicBarrier cyclicBarrier; public Soldier(String name, CyclicBarrier cyclicBarrier) { this.name = name; this.cyclicBarrier = cyclicBarrier; } @Override public void run() { try { System.out.println(name + " 來(lái)報(bào)道.."); //等待所有士兵到齊 cyclicBarrier.await(); doWork(); //等待所有士兵完成任務(wù) cyclicBarrier.await(); } catch (InterruptedException e) { e.printStackTrace(); } catch (BrokenBarrierException e) { e.printStackTrace(); } } void doWork(){ try { Thread.sleep(new Random().nextInt(10) * 1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(name + " 任務(wù)已完成.."); } } public static class doOrder implements Runnable{ boolean flag; int n; public doOrder(boolean flag, int n) { this.flag = flag; this.n = n; } @Override public void run() { if (flag){ System.out.println("司令 : 士兵 " + n +"個(gè) 任務(wù)完成"); }else { System.out.println("司令 : 士兵 " + n +"個(gè) 集合完畢"); //執(zhí)行完后 改變完成標(biāo)記.當(dāng)下一次調(diào)用doOrder時(shí),可以進(jìn)入if flag = true; } } } public static void main(String[] a){ final int n = 10; //是否完成了任務(wù) boolean flag = false; //創(chuàng)建10個(gè)士兵線程 Thread[] allSoldier = new Thread[n]; //創(chuàng)建CyclicBarrier實(shí)例 //這里的意思是,等待10個(gè)線程都執(zhí)行完,就執(zhí)行doOrder()方法 CyclicBarrier c = new CyclicBarrier(n, new doOrder(flag,n)); for (int i=0;i優(yōu)化鎖 一般我們對(duì)于鎖的優(yōu)化有以下幾個(gè)大致方向:
減少鎖的持有時(shí)間
減少鎖的請(qǐng)求頻率
使用帶有協(xié)調(diào)機(jī)制的獨(dú)占鎖,這些機(jī)制允許更高的并發(fā)性
鎖分段技術(shù)在某些情況下,可以將鎖分解技術(shù)進(jìn)一步擴(kuò)展為對(duì)一組獨(dú)立對(duì)象上的鎖進(jìn)行分解,這種情況被稱為鎖分段。例如,在ConcurrentHashMap的實(shí)現(xiàn)中使用了一個(gè)包含16個(gè)鎖的數(shù)組,每個(gè)鎖保護(hù)所有散列桶的1/16,其中第N個(gè)散列桶由第(N mod 16)個(gè)鎖來(lái)保護(hù)。假設(shè)散列函數(shù)具有合理的分布性,并且關(guān)鍵字能夠均勻分布,那么這大約能把對(duì)于鎖的請(qǐng)求減少到原來(lái)的1/16,正是這項(xiàng)技術(shù)使得ConcurrentHashMap能夠支持多達(dá)16個(gè)并發(fā)的寫入器。(要使得擁有大量處理器的系統(tǒng)在高訪問(wèn)量的情況下實(shí)現(xiàn)更高的并發(fā)性,還可以進(jìn)一步增加鎖的數(shù)量,但僅當(dāng)你能證明并發(fā)寫入線程的競(jìng)爭(zhēng)足夠激烈并需要突破這個(gè)限制時(shí),才能將鎖分段的數(shù)量超過(guò)默認(rèn)的16個(gè)。)
另一個(gè)典型的案例就是LinkedBlockingQueue的實(shí)現(xiàn)。
take()和put()方法雖然都對(duì)隊(duì)列進(jìn)行了修改操作,但由于是鏈表,因此,兩個(gè)操作分別作用于隊(duì)列的前端和末尾,理論上兩者并不沖突。使用獨(dú)占鎖,則要求在進(jìn)行take和put操作時(shí)獲取當(dāng)前隊(duì)列的獨(dú)占鎖,那么take和put就不可能真正的并發(fā),他們會(huì)彼此等待對(duì)方釋放鎖。在JDK的實(shí)現(xiàn)中,取而代之的是兩把不同的鎖,分離了take和put操作.削弱了競(jìng)爭(zhēng)的可能性.實(shí)現(xiàn)類取數(shù)據(jù)和寫數(shù)據(jù)的分離,實(shí)現(xiàn)了真正意義上成為并發(fā)操作。鎖分段的一個(gè)劣勢(shì)在于:與采用單個(gè)鎖來(lái)實(shí)現(xiàn)獨(dú)占訪問(wèn)相比,要獲取多個(gè)鎖來(lái)實(shí)現(xiàn)獨(dú)占訪問(wèn)將更加困難并且開銷更高。通常,在執(zhí)行一個(gè)操作時(shí)最多只需獲取一個(gè)鎖,但在某些情況下需要加鎖整個(gè)容器,例如當(dāng)ConcurrentHashMap需要擴(kuò)展映射范圍,以及重新計(jì)算鍵值的散列值要分布到更大的桶集合中時(shí),就需要獲取分段鎖集合中的所有鎖。
避免熱點(diǎn)域鎖分解和鎖分段技術(shù)都能提高可伸縮性,因?yàn)樗鼈兌寄苁共煌木€程在不同的數(shù)據(jù)(或者同一個(gè)數(shù)據(jù)的不同部分)上操作,而不會(huì)相互干擾。如果程序采用鎖分段或分解技術(shù),那么一定要表現(xiàn)出在鎖上的競(jìng)爭(zhēng)頻率高于在鎖保護(hù)的數(shù)據(jù)上發(fā)生競(jìng)爭(zhēng)的頻率。如果一個(gè)鎖保護(hù)兩個(gè)獨(dú)立變量X和Y,并且線程A想要訪問(wèn)X,而線程B想要訪問(wèn)Y(這類似于在ServerStatus中,一個(gè)線程調(diào)用addUser,而另一個(gè)線程調(diào)用addQuery),那么這兩個(gè)線程不會(huì)在任何數(shù)據(jù)上發(fā)生競(jìng)爭(zhēng),即使它們會(huì)在同一個(gè)鎖上發(fā)生競(jìng)爭(zhēng)。
當(dāng)每個(gè)操作都請(qǐng)求多個(gè)變量時(shí),鎖的粒度將很難降低。這是在性能與可伸縮性之間相互制衡的另一個(gè)方面,一些常見的優(yōu)化措施,例如將一些反復(fù)計(jì)算的結(jié)果緩存起來(lái),都會(huì)引入一些”熱點(diǎn)域“,而這些熱點(diǎn)域往往會(huì)限制可伸縮性。
當(dāng)實(shí)現(xiàn)HashMap時(shí),你需要考慮如何在size方法中計(jì)算Map中的元素?cái)?shù)量。最簡(jiǎn)單的方法就是,在每次調(diào)用時(shí)都統(tǒng)計(jì)一次元素的數(shù)量。一種常見的優(yōu)化措施是,在插入和移除元素時(shí)更新一個(gè)計(jì)數(shù)器,雖然這在put和remove等方法中略微增加了一些開銷,以確保計(jì)數(shù)器是最新的值,但這把size方法的開銷從O(n)降低到O(1)。
在單線程或者采用完全同步的實(shí)現(xiàn)中,使用一個(gè)獨(dú)立的計(jì)算器能很好地提高類似size和isEmpty這些方法的執(zhí)行速度,但卻導(dǎo)致更難以提升實(shí)現(xiàn)的可伸縮性,因?yàn)槊總€(gè)修改map的操作都需要更新這個(gè)共享的計(jì)數(shù)器。即使使用鎖分段技術(shù)來(lái)實(shí)現(xiàn)散列鏈,那么在對(duì)計(jì)數(shù)器的訪問(wèn)進(jìn)行同步時(shí),也會(huì)重新導(dǎo)致在使用獨(dú)占鎖時(shí)存在的可伸縮性問(wèn)題。一個(gè)看似性能優(yōu)化的措施——緩存size操作的結(jié)果,已經(jīng)變成了一個(gè)可伸縮性問(wèn)題。在這種情況下,計(jì)數(shù)器也被稱為熱點(diǎn)域,因?yàn)槊總€(gè)導(dǎo)致元素?cái)?shù)量發(fā)生變化的操作都需要訪問(wèn)它。
一些替代獨(dú)占鎖的方法
為了避免這個(gè)問(wèn)題,ConcurrentHashMap中的size將對(duì)每個(gè)分段進(jìn)行枚舉并將每個(gè)分段中的元素?cái)?shù)量相加,而不是維護(hù)一個(gè)全局計(jì)數(shù)。為了避免枚舉每個(gè)元素,ConcurrentHashMap為每個(gè)分段都維護(hù)一個(gè)獨(dú)立的計(jì)數(shù),并通過(guò)每個(gè)分段的鎖來(lái)維護(hù)這個(gè)值。第三種降低競(jìng)爭(zhēng)鎖的影響的技術(shù)就是放棄使用獨(dú)占鎖,從而有助于使用一種友好并發(fā)的方式來(lái)管理共享狀態(tài)。例如,使用并發(fā)容器、讀-寫鎖、不可變對(duì)象以及原子變量。
ReadWriteLock實(shí)現(xiàn)了一種在多個(gè)讀取操作以及單個(gè)寫入操作情況下的加鎖規(guī)則:如果多個(gè)讀取操作都不會(huì)修改共享資源,那么這些讀取操作可以同時(shí)訪問(wèn)該共享資源,但在執(zhí)行寫入操作時(shí)必須以獨(dú)占方式來(lái)獲取鎖。對(duì)于讀取操作占多數(shù)的數(shù)據(jù)結(jié)構(gòu),ReadWriteLock能夠提供比獨(dú)占鎖更高的并發(fā)性。而對(duì)于只讀的數(shù)據(jù)結(jié)構(gòu),其中包含的不變性可以完全不需要加鎖操作。
原子變量提供了一種方式來(lái)降低更新“熱點(diǎn)域”時(shí)的開銷,例如競(jìng)態(tài)計(jì)數(shù)器、序列發(fā)生器、或者對(duì)鏈表數(shù)據(jù)結(jié)構(gòu)中頭節(jié)點(diǎn)的引用。原子變量類提供了在整數(shù)或者對(duì)象引用上的細(xì)粒度原子操作(因此可伸縮性更高),并使用了現(xiàn)代處理器中提供的底層并發(fā)原語(yǔ)(例如比較并交換)。如果在類中只包含少量的熱點(diǎn)域,并且這些域不會(huì)與其他變量參與到不變性條件中,那么用原子變量來(lái)替代他們能提高可伸縮性。(通過(guò)減少算法中的熱點(diǎn)域,可以提高可伸縮性——雖然原子變量能降低熱點(diǎn)域的更新開銷,但并不能完全消除。)
來(lái)自JVM的鎖優(yōu)化 鎖粗化如果對(duì)一個(gè)鎖不停地進(jìn)行請(qǐng)求,同步和釋放,其本身也會(huì)消耗系統(tǒng)寶貴的資源,反而不利于性能優(yōu)化.
鎖偏向
虛擬機(jī)在遇到需要一連串對(duì)同一把鎖不斷進(jìn)行請(qǐng)求和釋放操作的情況時(shí),便會(huì)把所有的鎖操作整合成對(duì)鎖的一次請(qǐng)求,從而減少對(duì)鎖的請(qǐng)求同步次數(shù),這就是鎖的粗化。偏向鎖是一種針對(duì)加鎖操作的優(yōu)化手段,他的核心思想是:如果一個(gè)線程獲得了鎖,那么鎖就進(jìn)行偏向模式.當(dāng)這個(gè)線程再次請(qǐng)求鎖時(shí),無(wú)需再做任何同步操作.這樣就節(jié)省了大量操作鎖的動(dòng)作,從而提高程序性能.
因此,對(duì)于幾乎沒(méi)有鎖競(jìng)爭(zhēng)的場(chǎng)合,偏向鎖有比較好的優(yōu)化效果.因?yàn)闃O有可能連續(xù)多次是同一個(gè)線程請(qǐng)求相同的鎖.而對(duì)于鎖競(jìng)爭(zhēng)激烈的程序,其效果不佳.
使用Java虛擬機(jī)參數(shù):-XX:+UseBiasedLocking 可以開啟偏向鎖.
輕量級(jí)鎖如果偏向鎖失敗,虛擬機(jī)并不會(huì)立即掛起線程.它還會(huì)使用一種稱為輕量級(jí)的鎖的優(yōu)化手段.輕量級(jí)鎖只是簡(jiǎn)單的將對(duì)象頭部作為指針,指向持有鎖的線程堆棧內(nèi)部,來(lái)判斷一個(gè)線程是否持有對(duì)象鎖.如果線程獲得輕量鎖成功,則可以順利進(jìn)入臨界區(qū).如果失敗,則表示其他線程爭(zhēng)搶到了鎖,那么當(dāng)前線程的鎖請(qǐng)求就會(huì)膨脹為重量級(jí)鎖.
自旋鎖鎖膨脹后,虛擬機(jī)為了避免線程真實(shí)的在操作系統(tǒng)層面掛起,虛擬機(jī)還做了最后的努力就是自旋鎖.如果一個(gè)線程暫時(shí)無(wú)法獲得索,有可能在幾個(gè)CPU時(shí)鐘周期后就可以得到鎖,
鎖消除
那么簡(jiǎn)單粗暴的掛起線程可能是得不償失的操作.虛擬機(jī)會(huì)假設(shè)在很短時(shí)間內(nèi)線程是可以獲得鎖的,所以會(huì)讓線程自己空循環(huán)(這便是自旋的含義),如果嘗試若干次后,可以得到鎖,那么久可以順利進(jìn)入臨界區(qū),
如果還得不到,才會(huì)真實(shí)地講線程在操作系統(tǒng)層面掛起.鎖消除是一種更徹底的鎖優(yōu)化,Java虛擬機(jī)在JIT編譯時(shí),通過(guò)對(duì)運(yùn)用上下文的掃描,去除不可能存在的共享資源競(jìng)爭(zhēng)鎖,節(jié)省毫無(wú)意義的資源開銷.
我們可能會(huì)問(wèn):如果不可能存在競(jìng)爭(zhēng),為什么程序員還要加上鎖呢?
在Java軟件開發(fā)過(guò)程中,我們必然會(huì)用上一些JDK的內(nèi)置API,比如StringBuffer、Vector等。你在使用這些類的時(shí)候,也許根本不會(huì)考慮這些對(duì)象到底內(nèi)部是如何實(shí)現(xiàn)的。比如,你很有可能在一個(gè)不可能存在并發(fā)競(jìng)爭(zhēng)的場(chǎng)合使用Vector。而周所眾知,Vector內(nèi)部使用了synchronized請(qǐng)求鎖,如下代碼:
public String [] createString(){ Vectorv = new Vector (); for (int i =0;i<100;i++){ v.add(Integer.toString(i)); } return v.toArray(new String[]{}); } 上述代碼中的Vector,由于變量v只在createString()函數(shù)中使用,因此,它只是一個(gè)單純的局部變量。局部變量是在線程棧上分配的,屬于線程私有的數(shù)據(jù),因此不可能被其他線程訪問(wèn)。所以,在這種情況下,Vector內(nèi)部所有加鎖同步都是沒(méi)有必要的。如果虛擬機(jī)檢測(cè)到這種情況,就會(huì)將這些無(wú)用的鎖操作去除。
鎖消除設(shè)計(jì)的一項(xiàng)關(guān)鍵技術(shù)是逃逸分析,就是觀察某個(gè)變量是否會(huì)跳出某個(gè)作用域(比如對(duì)Vector的一些操作).在本例中,變量v顯然沒(méi)有逃出createString()函數(shù)之外。以次為基礎(chǔ),虛擬機(jī)才可以大膽將v內(nèi)部逃逸出當(dāng)前函數(shù),也就是說(shuō)v有可能被其他線程訪問(wèn)。如果是這樣,虛擬機(jī)就不能消除v中的鎖操作。
逃逸分析必須在-server模式下進(jìn)行,可以使用-XX:+DoEscapeAnalysis參數(shù)打開逃逸分析。使用-XX:+EliminateLocks參數(shù)可以打開鎖消除。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/66286.html
摘要:本文探討并發(fā)中的其它問(wèn)題線程安全可見性活躍性等等。當(dāng)閉鎖到達(dá)結(jié)束狀態(tài)時(shí),門打開并允許所有線程通過(guò)。在從返回時(shí)被叫醒時(shí),線程被放入鎖池,與其他線程競(jìng)爭(zhēng)重新獲得鎖。 本文探討Java并發(fā)中的其它問(wèn)題:線程安全、可見性、活躍性等等。 在行文之前,我想先推薦以下兩份資料,質(zhì)量很高:極客學(xué)院-Java并發(fā)編程讀書筆記-《Java并發(fā)編程實(shí)戰(zhàn)》 線程安全 《Java并發(fā)編程實(shí)戰(zhàn)》中提到了太多的術(shù)語(yǔ)...
摘要:線程啟動(dòng)規(guī)則對(duì)象的方法先行發(fā)生于此線程的每一個(gè)動(dòng)作。所以局部變量是不被多個(gè)線程所共享的,也就不會(huì)出現(xiàn)并發(fā)問(wèn)題。通過(guò)獲取到數(shù)據(jù),放入當(dāng)前線程處理完之后將當(dāng)前線程中的信息移除。主線程必須在啟動(dòng)其他線程后立即調(diào)用方法。 一、線程安全性 定義:當(dāng)多個(gè)線程訪問(wèn)某個(gè)類時(shí),不管運(yùn)行時(shí)環(huán)境采用何種調(diào)度方式,或者這些線程將如何交替執(zhí)行,并且在主調(diào)代碼中不需要任何額外的同步或協(xié)同,這個(gè)類都能表現(xiàn)出正確的行...
摘要:同時(shí)也會(huì)關(guān)注市場(chǎng)上同崗位薪資,以便對(duì)企業(yè)內(nèi)部薪資結(jié)構(gòu)做出相應(yīng)調(diào)整。一般來(lái)說(shuō),相同崗位和職責(zé)的員工,薪資低于市場(chǎng)不超過(guò),都屬于合理范疇,因?yàn)橐粋€(gè)員工不會(huì)為了的薪酬而跳槽。同時(shí),還能激勵(lì)員工自我提升,以獲得相應(yīng)技能市場(chǎng)所給予的報(bào)酬。 各位職場(chǎng)人都聽說(shuō)過(guò)薪資倒掛這詞兒吧,這個(gè)情況在行業(yè)內(nèi)早就不是什...
摘要:是多線程包里的一個(gè)常見工具類,通過(guò)使用它可以借助線程能力極大提升處理響應(yīng)速度,且實(shí)現(xiàn)方式非常優(yōu)雅。主線程處于狀態(tài),直到的值數(shù)減到,則主線程繼續(xù)執(zhí)行。此時(shí)必須使用線程池,并限定最大可處理線程數(shù)量,否則服務(wù)器不穩(wěn)定性會(huì)大福提升。 countdownlatch是java多線程包c(diǎn)oncurrent里的一個(gè)常見工具類,通過(guò)使用它可以借助線程能力極大提升處理響應(yīng)速度,且實(shí)現(xiàn)方式非常優(yōu)雅。今天我們...
摘要:創(chuàng)建線程的方式方式一將類聲明為的子類。將該線程標(biāo)記為守護(hù)線程或用戶線程。其中方法隱含的線程為父線程?;謴?fù)線程,已過(guò)時(shí)。等待該線程銷毀終止。更多的使當(dāng)前線程在鎖存器倒計(jì)數(shù)至零之前一直等待,除非線 知識(shí)體系圖: showImg(https://segmentfault.com/img/bVbef6v?w=1280&h=960); 1、線程是什么? 線程是進(jìn)程中獨(dú)立運(yùn)行的子任務(wù)。 2、創(chuàng)建線...
閱讀 859·2019-08-30 15:54
閱讀 3325·2019-08-29 15:33
閱讀 2709·2019-08-29 13:48
閱讀 1235·2019-08-26 18:26
閱讀 3342·2019-08-26 13:55
閱讀 1499·2019-08-26 10:45
閱讀 1176·2019-08-26 10:19
閱讀 317·2019-08-26 10:16