①. 可重入鎖
1>. 可重入鎖(遞回鎖)
-
①. 指的是同一執行緒外層函式獲得鎖后,再進入該執行緒的內層方法會自動獲取鎖 (
前提,鎖物件是同一個物件)
類似于家里面的大門,進入之后可以進入廁所、廚房等 -
②. Java中ReentranLock(顯示鎖)和synchronized(隱式鎖)都是可重入鎖,可重入鎖的一個優點是可在一定程度避免死鎖
-
③.
隱式鎖:(即synchronized關鍵字使用的鎖)默認是可重入鎖(同步塊、同步方法)
原理如下:掌握
- 每個鎖物件擁有一個鎖計數器和一個指向持有該鎖的執行緒的指標
- 當執行monitorenter時,如果目標鎖物件的計數器為零,那么說明它沒有被其他執行緒持有,Java虛擬機會將該鎖物件的持有執行緒設定為當前執行緒,并且將其計數器加1,否則需要等待,直至持有執行緒釋放該鎖
- 當執行monitorexit時,Java虛擬機則鎖物件的計數器減1,計數器為零代表鎖已經被釋放

//1.同步塊
public class SychronizedDemo {
Object object=new Object();
public void sychronizedMethod(){
new Thread(()->{
synchronized (object){
System.out.println(Thread.currentThread().getName()+"\t"+"外層....");
synchronized (object){
System.out.println(Thread.currentThread().getName()+"\t"+"中層....");
synchronized (object){
System.out.println(Thread.currentThread().getName()+"\t"+"內層....");
}
}
}
},"A").start();
}
public static void main(String[] args) {
new SychronizedDemo().sychronizedMethod();
/*
輸出結果:
A 外層....
A 中層....
A 內層....
* */
}
}
//2.同步代碼塊
class Phone{
public synchronized void sendSms() throws Exception{
System.out.println(Thread.currentThread().getName()+"\tsendSms");
sendEmail();
}
public synchronized void sendEmail() throws Exception{
System.out.println(Thread.currentThread().getName()+"\tsendEmail");
}
}
/**
* Description:
* 可重入鎖(也叫做遞回鎖)
* 指的是同一執行緒外層函式獲得鎖后,內層遞回函式任然能獲取該鎖的代碼
* 在同一執行緒外外層方法獲取鎖的時候,在進入內層方法會自動獲取鎖
* 也就是說,執行緒可以進入任何一個它已經標記的鎖所同步的代碼塊
* **/
public class ReenterLockDemo {
/**
* t1 sendSms
* t1 sendEmail
* t2 sendSms
* t2 sendEmail
* @param args
*/
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(()->{
try {
phone.sendSms();
} catch (Exception e) {
e.printStackTrace();
}
},"t1").start();
new Thread(()->{
try {
phone.sendSms();
} catch (Exception e) {
e.printStackTrace();
}
},"t2").start();
}
}
- ④.
顯示鎖:(即lock)也有ReentrantLock這樣的可重入鎖
(注意:有多少個lock,就有多少個unlock,他們是配對使用的;如果多一個或者少一個會使得其他執行緒處于等待狀態)
class Phone2{
static ReentrantLock reentrantLock=new ReentrantLock();
public static void sendSms(){
reentrantLock.lock();
/*
//reentrantLock.lock();
注意有多少個lock,就有多少個unlock,他們是配對使用的
如果多了一個lock(),那么會出現執行緒B一直處于等待狀態
* */
reentrantLock.lock();
try {
System.out.println(Thread.currentThread().getName()+"\t"+"sendSms");
sendEmails();
}catch (Exception e){
e.printStackTrace();
}finally {
reentrantLock.unlock();
}
}
private static void sendEmails() {
reentrantLock.lock();
try {
System.out.println(Thread.currentThread().getName()+"\t"+"sendEmails...");
}catch (Exception e){
e.printStackTrace();
}finally {
reentrantLock.unlock();
}
}
}
public class ReentrantLockDemo {
public static void main(String[] args) {
Phone2 phone2=new Phone2();
new Thread(()->{phone2.sendSms();},"A").start();
new Thread(()->{phone2.sendSms();},"B").start();
}
}
②. 為什么要使用LockSupport
2>.為什么要使用LockSupport(先來了解下傳統的等待喚醒機制)
- ①. 3種讓執行緒等待喚醒的方法
- 使用Object中的wait()方法讓執行緒等待,使用Object中的notify方法喚醒執行緒
- 使用JUC包中Condition的await()方法讓執行緒等待,使用signal()方法喚醒執行緒
- LockSupport類可以阻塞當前執行緒以及喚醒指定被阻塞的執行緒
- ②. Object類中wait( )和notify( )實作執行緒的等待喚醒
- wait和notify方法必須要在同步塊或同步方法里且成對出現使用, wait和notify方法兩個都去掉同步代碼塊后看運行效果出現例外情況:
Exception in thread “A” Exception in thread “B”
java.lang.IllegalMonitorStateException - 先wait后notify才可以(如果先notify后wait會出現另一個執行緒一直處于等待狀態)
- synchronized是關鍵字屬于JVM層面,monitorenter(底層是通過monitor物件來完成,其實wait/notify等方法也依賴monitor物件只能在同步塊或方法中才能呼叫wait/notify等方法)
public class SynchronizedDemo {
//等待執行緒
public void waitThread(){
// 1.如果將synchronized (this){}注釋,會拋出例外,因為wait和notify一定要在同步塊或同步方法中
synchronized (this){
try {
System.out.println(Thread.currentThread().getName()+"\t"+"coming....");
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"\t"+"end....");
}
}
//喚醒執行緒
public void notifyThread(){
synchronized (this){
System.out.println("喚醒A執行緒....");
notify();
}
}
public static void main(String[] args) {
SynchronizedDemo synchronizedDemo = new SynchronizedDemo();
new Thread(()->{
// 2.如果把下行這句代碼打開,先notify后wait,會出現A執行緒一直處于等待狀態
// try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) {e.printStackTrace();}
synchronizedDemo.waitThread();
},"A").start();
new Thread(()->{
synchronizedDemo.notifyThread();
},"B").start();
}
}
- ③. Condition介面中的await和signal方法實作執行緒等待和喚醒
(出現的問題和object中wait和notify一樣)
public class LockDemo {
static Object object=new Object();
public static void main(String[] args) {
Lock lock=new ReentrantLock();
Condition condition = lock.newCondition();
new Thread(()->{
//如果把下行這句代碼打開,先signal后await,會出現A執行緒一直處于等待狀態
//try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) {e.printStackTrace();}
lock.lock();
try {
System.out.println(Thread.currentThread().getName()+"\t"+"coming....");
condition.await();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
System.out.println(Thread.currentThread().getName()+"\t"+"END....");
},"A").start();
new Thread(()->{
lock.lock();
try {
System.out.println(Thread.currentThread().getName()+"\t"+"喚醒A執行緒****");
condition.signal();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
},"B").start();
}
}
③. JUC強大的三個工具類
3>. JUC強大的三個工具類 掌握
為什么這里要介紹下JUC強大的工具類?
CountDownLatch | CyclicBarrier | Semaphore 底層都是AQS來實作的
①. CountDownLatch(閉鎖)
-
①. CountDownLatch主要有兩個方法,當一個或多個執行緒呼叫await方法時,這些執行緒會阻塞
-
②. 其它執行緒呼叫countDown方法會將計數器減1(呼叫countDown方法的執行緒不會阻塞)
-
③. 計數器的值變為0時,因await方法阻塞的執行緒會被喚醒,繼續執行
//需求:要求6個執行緒都執行完了,mian執行緒最后執行
public class CountDownLatchDemo {
public static void main(String[] args) throws Exception{
CountDownLatch countDownLatch=new CountDownLatch(6);
for (int i = 1; i <=6; i++) {
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"\t");
countDownLatch.countDown();
},i+"").start();
}
countDownLatch.await();
System.out.println(Thread.currentThread().getName()+"\t班長關門走人,main執行緒是班長");
}
}
- ④. 利用列舉減少if else的判斷
public enum CountryEnum {
one(1,"齊"),two(2,"楚"),three(3,"燕"),
four(4,"趙"),five(5,"魏"),six(6,"韓");
private Integer retCode;
private String retMessage;
private CountryEnum(Integer retCode,String retMessage){
this.retCode=retCode;
this.retMessage=retMessage;
}
public static CountryEnum getCountryEnum(Integer index){
CountryEnum[] countryEnums = CountryEnum.values();
for (CountryEnum countryEnum : countryEnums) {
if(countryEnum.getRetCode()==index){
return countryEnum;
}
}
return null;
}
public Integer getRetCode() {
return retCode;
}
public String getRetMessage() {
return retMessage;
}
}
/*
楚 **國,被滅
魏 **國,被滅
趙 **國,被滅
燕 **國,被滅
齊 **國,被滅
韓 **國,被滅
main **秦國一統江湖
* */
public class CountDownLatchDemo {
public static void main(String[] args) throws Exception{
CountDownLatch countDownLatch=new CountDownLatch(6);
for (int i = 1; i <=6; i++) {
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"\t"+"**國,被滅");
countDownLatch.countDown();
},CountryEnum.getCountryEnum(i).getRetMessage()).start();
}
countDownLatch.await();
System.out.println(Thread.currentThread().getName()+"\t"+"**秦國一統江湖");
}
}
②. CyclicBarrier
2>. CyclicBarrier
-
①. CyclicBarrier的字面意思是可回圈(Cyclic) 使用的屏障(barrier).它要做的事情是,讓一組執行緒到達一個屏障(也可以叫做同步點)時被阻塞,知道最后一個執行緒到達屏障時,屏障才會開門,所有被屏障攔截的執行緒才會繼續干活,執行緒進入屏障通過CyclicBarrier的await()方法
-
②. 代碼驗證:
//集齊7顆龍珠就能召喚神龍
public class CyclicBarrierDemo {
public static void main(String[] args) {
// public CyclicBarrier(int parties, Runnable barrierAction) {}
CyclicBarrier cyclicBarrier=new CyclicBarrier(7,()->{
System.out.println("召喚龍珠");
});
for (int i = 1; i <=7; i++) {
final int temp=i;
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"\t收集到了第"+temp+"顆龍珠");
try {
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
}
}
}
③.Semaphore(信號量)
3>. Semaphore(信號量)
-
①. acquire(獲取) 當一個執行緒呼叫acquire操作時,它要么通過成功獲取信號量(信號量減1),要么一直等下去,直到有執行緒釋放信號量,或超時,
-
②. release(釋放)實際上會將信號量的值加1,然后喚醒等待的執行緒,
-
③. 信號量主要用于兩個目的,一個是用于多個共享資源的互斥使用,另一個用于并發執行緒數的控制,
-
④. 代碼驗證
public class SemaphoreDemo {
public static void main(String[] args) {
Semaphore semaphore=new Semaphore(3);
for (int i = 1; i <=6; i++) {
new Thread(()->{
try {
System.out.println(Thread.currentThread().getName()+"\t搶占了車位");
semaphore.acquire();
System.out.println(Thread.currentThread().getName()+"\t離開了車位");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
semaphore.release();
}
},String.valueOf(i)).start();
}
}
}
④. LockSupport詳解
4>. LockSupport詳解
- ①. 什么是LockSupport?
- 通過park()和unpark(thread)方法來實作阻塞和喚醒執行緒的操作
- LockSupport是一個執行緒阻塞工具類,所有的方法都是靜態方法,可以讓執行緒在任意位置阻塞,阻塞之后也有對應的喚醒方法,歸根結底,LockSupport呼叫的Unsafe中的native代碼,
- 官網解釋:
LockSupport是用來創建鎖和其他同步類的基本執行緒阻塞原語
LockSupport類使用了一種名為Permit(許可)的概念來做到阻塞和喚醒執行緒的功能,每個執行緒都有一個許可(permit),permit只有兩個值1和零,默認是零
可以把許可看成是一種(0,1)信號量(Semaphore),但與Semaphore不同的是,許可的累加上限是1
- ②. 阻塞方法
- permit默認是0,所以一開始呼叫park()方法,當前執行緒就會阻塞,直到別的執行緒將當前執行緒的permit設定為1時, park方法會被喚醒,然后會將permit再次設定為0并回傳,
- static void park( ):底層是unsafe類native方法
- static void park(Object blocker)

- ③.喚醒方法(注意這個permit最多只能為1)
- 呼叫unpark(thread)方法后,就會將thread執行緒的許可permit設定成1(注意多次呼叫unpark方法,不會累加,permit值還是1)會自動喚醒thread執行緒,即之前阻塞中的LockSupport.park()方法會立即回傳
- static void unpark( )

- ④. LockSupport它的解決的痛點
- LockSupport不用持有鎖塊,不用加鎖,程式性能好
- 先后順序,不容易導致卡死(因為unpark獲得了一個憑證,之后再呼叫park方法,就可以名正言順的憑證消費,故不會阻塞)
- ⑤. 代碼演示:
/*
(1).阻塞
(permit默認是O,所以一開始呼叫park()方法,當前執行緒就會阻塞,直到別的執行緒將當前執行緒的permit設定為1時,
park方法會被喚醒,然后會將permit再次設定為O并回傳)
static void park()
static void park(Object blocker)
(2).喚醒
static void unpark(Thread thread)
(呼叫unpark(thread)方法后,就會將thread執行緒的許可permit設定成1(注意多次呼叫unpark方法,不會累加,
permit值還是1)會自動喚醒thread執行緒,即之前阻塞中的LockSupport.park()方法會立即回傳)
static void unpark(Thread thread)
* */
public class LockSupportDemo {
public static void main(String[] args) {
Thread t1=new Thread(()->{
System.out.println(Thread.currentThread().getName()+"\t"+"coming....");
LockSupport.park();
/*
如果這里有兩個LockSupport.park(),因為permit的值為1,上一行已經使用了permit
所以下一行被注釋的打開會導致程式處于一直等待的狀態
* */
//LockSupport.park();
System.out.println(Thread.currentThread().getName()+"\t"+"被B喚醒了");
},"A");
t1.start();
//下面代碼注釋是為了A執行緒先執行
//try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) {e.printStackTrace();}
Thread t2=new Thread(()->{
System.out.println(Thread.currentThread().getName()+"\t"+"喚醒A執行緒");
//有兩個LockSupport.unpark(t1),由于permit的值最大為1,所以只能給park一個通行證
LockSupport.unpark(t1);
//LockSupport.unpark(t1);
},"B");
t2.start();
}
}
- ⑥. 面試題目:
- 為什么可以先喚醒執行緒后阻塞執行緒?(因為unpark獲得了一個憑證,之后再呼叫park方法,就可以名正言順的憑證消費,故不會阻塞)
- 為什么喚醒兩次后阻塞兩次,但最終結果還會阻塞執行緒?(因為憑證的數量最多為1,連續呼叫兩次unpark和呼叫一次unpark效果一樣,只會增加一個憑證;而呼叫兩次park卻需要消費兩個憑證,證不夠,不能放行)
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/187893.html
標籤:其他
上一篇:多個Vue專案如何部署到服務器
