文章目錄
1 可重入特性
1.1 可重入的含義及synchronized可重入特性演示
可重入的含義:
指的是同一個線程獲得鎖之後,再不釋放鎖的情況下,可以直接再次獲取到該鎖。
- synchronized爲可重入鎖驗證demo:
package com.nrsc.ch1.base.jmm.syn_study.texing;
import lombok.extern.slf4j.Slf4j;
/***
* 可重入特性: 指的是同一個線程獲得鎖之後,可以直接再次獲取該鎖
* 最常出現的場景: 遞歸
* synchronized爲可重入鎖驗證demo
*/
@Slf4j
public class SynReentrantDemo {
public static void main(String[] args) {
Runnable sellTicket = new Runnable() {
@Override
public void run() {
synchronized (this) {
String name = Thread.currentThread().getName();
log.info("我是run,搶到鎖的是{}", name);
test01();
} //正常來說走出臨界區(這個括號)纔會釋放鎖,但是再沒走出之前,又進入test01,
//而test01需要和本方法一樣的鎖
//如果不可重入的話,就將出現死鎖了-->即test01方法等着釋放鎖,而run方法又不會釋放鎖
//因此synchronized只有可以在不釋放run方法的鎖的情況下,又再次獲得該鎖纔不會有問題
}
public void test01() {
synchronized (this) {
String name = Thread.currentThread().getName();
log.info("我是test01,搶到鎖的是{}", name);
}
}
};
new Thread(sellTicket).start();
new Thread(sellTicket).start();
}
}
- 運行結果:
1.2 簡單說一下synchronized可重入原理
首先應該知道synchronized鎖的並不是同步代碼塊,而是鎖對象關聯的一個monitor對象(在java中每一個對象都會關聯一個monitor對象),而這個對象裏有一個變量叫recursions
— 中文是遞歸的意思(我想大概是因爲遞歸的時候發生可重入的機率應該是最大的,所以才用這個當變量名的吧),其實可以將它簡單理解爲一個計數器。
以上面的栗子爲例:
- (1)當線程1搶到run方法的執行權即搶到鎖時,這個recursions的值就變爲了1;
- (2)線程1接着運行並進入test01方法後,發現還是線程1且還是要this這把鎖,就將recursions的值再+1;
- (3)當線程1,執行完test01方法時,recursions的值又-1
- (4)執行完run方法時recursions的值又-1,就變爲了0,也就是表示線程1已經釋放了this鎖。
- (5)之後其他線程就可以繼續搶this鎖了。
當然ReentrantLock肯定也是可重入的,人家的名字翻譯過來就是可重入鎖☺☺☺ ,這裏就不貼代碼了。
2 synchronized不可中斷特性 — interrupt和stop都不可中斷
2.1 不可中斷的含義及synchronized不可中斷特性演示
不可中斷的含義
: 第一個線程獲得某把鎖後,第二個線程也想要獲得該鎖,則它必須處於阻塞或等待狀態。如果第一個線程不釋放鎖,那第二個線程就會一直阻塞或等待,不可被中斷。
- synchronized不可中斷性驗證
package com.nrsc.ch1.base.jmm.syn_study.texing;
import lombok.extern.slf4j.Slf4j;
/***
* 演示synchronized不可中斷的思路
* 1.定義一個Runnable
* 2.在Runnable定義同步代碼塊
* 3.先開啓一個線程來執行同步代碼塊,保證不退出同步代碼塊
* 4.後開啓一個線程來執行同步代碼塊(阻塞狀態)
* 5.停止第二個線程 --- 可以發現無法停止
*/
@Slf4j
public class UninterruptibleDemo {
private static Object obj = new Object();
public static void main(String[] args) throws InterruptedException {
// 1. 定義一個Runnable
Runnable run = () -> {
// 2.在Runnable定義同步代碼塊
synchronized (obj) {
try {
String name = Thread.currentThread().getName();
log.info("線程{}進入同步代碼塊", name);
Thread.sleep(888888); //保證不退出同步代碼塊
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
// 3. 先開啓一個線程來執行同步代碼塊
Thread t1 = new Thread(run);
t1.start();
Thread.sleep(1000);
// 4. 後開啓一個線程來執行同步代碼塊(阻塞狀態)
Thread t2 = new Thread(run);
t2.start();
// 5.停止第二個線程
log.info("線程t2嘗試停止線程前");
t2.interrupt();
//t2.stop(); //無論使用interrupt還是stop都不能把t2給中斷
log.info("線程t2嘗試停止線程後");
log.info("線程t1的狀態:{}", t1.getState());
log.info("線程t2的狀態:{}", t2.getState());
}
}
- 測試結果
2.2 ReentrantLock的可中斷與不可中斷
有沒有感覺synchronized的不可中斷機制其實是有一定問題的。比如說有一個線程搶了很久很久發現一直就是搶不到執行權,竟然還不能讓它知難而退了 —> 這感覺像極了追女朋友,我追了很久很久很久,就是追不到,但是還不准許我不追了。。
—》 我想也許Doug Lea正是發現了這一點,所以在設計ReentrantLock時搞了個tryLock方法。
2.2.1 ReentrantLock使用lock()加鎖 — interrupt不可中斷,stop可中斷
- 將2.1中的代碼修改成使用ReentrantLock加鎖:
package com.nrsc.ch1.base.jmm.syn_study.texing;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
@Slf4j
public class ReentrantLockUninterruptibleDemo {
private static Lock lock = new ReentrantLock();
public static void main(String[] args) throws InterruptedException {
// 1. 定義一個Runnable
Runnable run = () -> {
// 2.對方法進行加鎖
lock.lock();
try {
String name = Thread.currentThread().getName();
log.info("線程{}進入同步代碼塊", name);
Thread.sleep(888888); //保證不退出同步代碼塊
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
};
// 3. 先開啓一個線程來執行同步代碼塊
Thread t1 = new Thread(run);
t1.start();
Thread.sleep(1000);
// 4. 後開啓一個線程來執行同步代碼塊(阻塞狀態)
Thread t2 = new Thread(run);
t2.start();
// 5.停止第二個線程
log.info("線程t2嘗試停止線程前");
t2.interrupt(); //使用interrupt不可中斷
//t2.stop(); //使用stop可中斷
log.info("線程t2嘗試停止線程後");
log.info("線程t1的狀態:{}", t1.getState());
log.info("線程t2的狀態:{}", t2.getState());
}
}
- 使用Interrupt()方法嘗試中斷線程時的結果如下:
- 使用stop方法嘗試中斷線程時的結果如下:
這裏需要再提醒一下,在併發編程時,是不推薦使用stop進行中斷線程的,因爲stop會釋放掉線程所有的資源。 —》 更推薦的是使用interrupt方法 — 》 可以看一下我的另一篇文章《【併發編程】— interrupt、interrupted和isInterrupted使用詳解》。
2.2.2 ReentrantLock使用tryLock()加鎖 — interrupt和stop都可中斷
- code
package com.nrsc.ch1.base.jmm.syn_study.texing;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
@Slf4j
public class ReentrantLockCanInterruptibleDemo {
private static Lock lock = new ReentrantLock();
public static void main(String[] args) throws InterruptedException {
// 1. 定義一個Runnable
Runnable run = () -> {
boolean flag = false;
try {
// 2.對方法進行加鎖
flag = lock.tryLock();
String name = Thread.currentThread().getName();
if (flag) {
log.info("線程{}進入同步代碼塊", name);
Thread.sleep(888888); //保證不退出同步代碼塊
} else {
log.info("線程{}未搶到鎖", name);
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if (flag) {
lock.unlock();
}
}
};
// 3. 先開啓一個線程來執行同步代碼塊
Thread t1 = new Thread(run);
t1.start();
Thread.sleep(1000);
// 4. 後開啓一個線程來執行同步代碼塊(阻塞狀態)
Thread t2 = new Thread(run);
t2.start();
//等待5秒,確保已經搶了
Thread.sleep(5000);
log.info("線程t2嘗試停止線程前");
t2.interrupt(); //使用interrupt不可中斷
//t2.stop(); //使用stop可中斷
log.info("線程t2嘗試停止線程後");
log.info("線程t1的狀態:{}", t1.getState());
log.info("線程t2的狀態:{}", t2.getState());
}
}
- 測試結果
簡單提一句
tryLock還可以指定在一定時間內嘗試獲取鎖,如果獲取不到,就不再獲取了,這其實也可以避免追女朋友,追了很久很久很久,就是追不到,但是還不准許我不追的情況。
end