鎖的概念及synchronized使用原理決議
前言
前篇文章主要對鎖的型別和synchronized如何使用,及鎖物件在堆中各個變化狀態做了一個分析,然后這篇文章會繼續講解lock的實作ReentrantLock 和condition如何實作及原理決議,與synchronized的對比分析,ReentrantLock相比synchronized而言功能更加豐富,使用起來更為靈活,也更適合復雜的并發場景,
鎖
java除了synchronized關鍵字可以實作鎖,也可以由Doug Lea 大神寫juc.locks包下面實作類實作,從reentrantlock開始充分理解和分析資訊
本質
鎖是一種控制用戶訪問共享資源的工具,多執行緒,通常,鎖提供對資料庫的獨占訪問共享資源:一次只有一個執行緒可以獲取鎖和對共享資源的所有訪問都要求鎖定先得的,但是,某些鎖可能允許并發訪問共享資源,如{@link ReadWriteLock}的讀鎖, -------截取自lock介面的注釋
加鎖是加一種限制,獲取鎖是獲取一個權限,
例如:一個場景,房子出租,房東貼出出租資訊,而就是給這間房子加了把鎖,有個人租下了房子,就說這個人有這個房子的使用權限,當這個房子租期到了,釋放了鎖,
public class MyLock implements Lock {
//鎖擁有者
AtomicReference<Thread> owner = new AtomicReference<>();
//等待佇列
private LinkedBlockingQueue<Thread> waiters = new LinkedBlockingQueue<>();
@Override
public boolean tryLock() {
return owner.compareAndSet(null, Thread.currentThread());
}
@Override
public void lock() {
while (!tryLock()){
waiters.offer(Thread.currentThread());
LockSupport.park();
}
}
@Override
public void unlock() {
if (owner.compareAndSet(Thread.currentThread(), null)){
Thread th = waiters.poll();
LockSupport.unpark(th);
}
}
}
Locks Api
lock層次結構


上圖為整個介面實作的層次圖
Lock介面

- lock方法一般都是我們用到的方法,一定要獲得到鎖為止, lock()最常用;
- trylock方法只獲取鎖一次,獲取不到就不獲取了
-
lockInterruptibly()方法一般更昂貴,有的impl可能沒有實作lockInterruptibly(),只有真的需要效應中斷時,才使用.
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
即該執行緒阻塞時被中斷,拋中斷例外后執行緒退出,不會執行后面陳述句,
在interrupt過后,lock方法是不會中斷的,回應中斷
根據Lock介面的原始碼注釋,Lock介面的實作,具備和同步關鍵字同樣的記憶體語意,代碼在運行時并且不能指令重排,不會被快取
{@code Lock}實作提供了更廣泛的鎖定 使用{@code synchronized}方法可以獲得的操作和宣告,它們允許更靈活的結構,可能有完全不同的屬性,并且可能支持多個關聯的{@link Condition}物件,
ReentrantLock
ReentrantLock是lock介面的最經典實作,是一個可重入鎖、悲觀鎖、獨享鎖、自旋鎖;
默認是非公平鎖
public ReentrantLock() {
sync = new NonfairSync();
}
可以在初始化時設定為公平鎖
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
重字面意思說reentrantlock是可重入的鎖,可重入的概念在于下面的 代碼執行過后
static Lock lc = new ReentrantLock();
static volatile int i = 0;
public static void add() throws InterruptedException {
lc.lock();
i++;
System.out.println("here im ..");
Thread.sleep(1000L);
add();
lc.unlock();
}
public static void main(String args[]) throws InterruptedException {
add();
}
得到結果會一直列印值,
相同的執行緒,不互斥,會再次獲取到鎖,執行鎖內代碼,
reentrantlock怎么實作可重入鎖的,
首先實作一個簡單的不重入鎖
public class MyLock implements Lock {
//當前鎖擁有者,若onwer為null,說明沒有執行緒占用鎖
private Thread owner = null;
//鎖占用是,執行緒被掛起,掛起的執行緒的參考被放到waiters佇列
private BlockingQueue<Thread> waiters = new LinkedBlockingQueue<>();
@Override
public boolean tryLock() { //嘗試獲取鎖
//若鎖未占用
if (owner == null){
owner = Thread.currentThread(); //當前執行緒獲得鎖
return true; //獲得鎖,回傳true
}else{
return false; //獲取鎖失敗,回傳false
}
}
@Override
public void lock() {
while(!tryLock()){ //嘗試拿鎖,如果失敗,
Thread curTh = Thread.currentThread(); //獲取當前執行緒參考
waiters.offer(curTh); //將當前執行緒參考放入等待佇列
LockSupport.park();
}
}
@Override
public void unlock() { //釋放鎖
if (owner == Thread.currentThread()){ //若確實是我占有了鎖
owner = null; //將onwer置為null,釋放鎖
Thread th = waiters.poll(); //取出佇列頭部的元素,并移除該元素
LockSupport.unpark(th); //喚醒佇列頭部的元素
}
}
}
這里只用一個owner來判斷當前那個鎖被占用 ,并用一個佇列來存盤阻塞執行緒
在ReentrantLock中去判斷是否重入鎖,需要添加State值,來判斷當前鎖是否被被占用
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}

這里新加了個同步狀態來維護,
實作的一個簡單的可重入鎖
public class MyLock implements Lock {
//鎖擁有者
volatile AtomicReference<Thread> owner = new AtomicReference<>();
//等待佇列
private LinkedBlockingQueue<Thread> waiters = new LinkedBlockingQueue<>();
//記錄重入的次數
volatile AtomicInteger state = new AtomicInteger(0);
@Override
public boolean tryLock() {
int s = state.get();
if (s != 0) {
// 判斷state是否為0,state!=0,說明鎖被占用
if (owner.get() == Thread.currentThread()) {
state.set(s + 1);
return true;
}
} else {
// 如state==0,說明鎖未未被占用,CAS操作來搶鎖,修改state的值
while (state.compareAndSet(s, s + 1)) {
owner.set(Thread.currentThread());
return true;
}
}
return false;
}
@Override
public void lock() {
if (!tryLock()){
waiters.offer(Thread.currentThread());
//自旋搶鎖
for (;;){
Thread head = waiters.peek();
if (head == Thread.currentThread()){
if (!tryLock()){
LockSupport.park();
}else{
waiters.poll();
return;
}
}else{
LockSupport.park();
}
}
}
}
@Override
public void unlock() {
if (tryUnlock()){
Thread th = waiters.peek();
if (th !=null){
LockSupport.unpark(th);
}
}
}
public boolean tryUnlock(){
//判斷owner是不是自己
if (owner.get() != Thread.currentThread()){
throw new IllegalMonitorStateException();
}else{
//釋放鎖,state-1,
int ct = state.get();
int nextc = ct -1;
state.set(nextc);
//是不是一定將onwer該問null
if (nextc == 0){
owner.set(null);
return true;
}else{
return false;
}
}
}
}
這里主要是為了實作一個簡單的可重入鎖,因此并沒有考慮到其他情況
Condition
final ConditionObject newCondition() {
return new ConditionObject();
}
包含awit方法的實作 和sign方法的實作

- 搭配lock物件進行使用,就是condition的使用方法
public class Condition {
private static ReentrantLock lock = new ReentrantLock();
private static java.util.concurrent.locks.Condition condition = lock.newCondition();
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
lock.lock();
System.out.println("子執行緒或得鎖...\n");
try {
System.out.println("開始wait...\n");
condition.await();
System.out.println("喚醒了...\n");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
lock.unlock();
}
}
});
thread.start();
Thread.sleep(4000L);
lock.lock();
System.out.println("喚醒子執行緒...");
condition.signal();
Thread.sleep(10000L);
lock.unlock();
}
}
- 當signal只有unlock釋放掉鎖,wait的方法才會結束等待,這個和synchronized一樣
- condition.awit底層是使用的park,因此這時的執行緒狀態也是awit

- 這里在原始碼中去校驗鎖物件不同時,也會和synchronized一樣拋出monitorstate例外,雖然這里不存在 monitor監聽器 重量級鎖, 應該是Doug Lea大神為了和虛擬機synchronized拋出例外一致
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
- condition.awit 和awit方法是一樣,在等待期間,也會把鎖給釋放掉,主執行緒可以拿到鎖
- 造成死鎖,例如park和unpark ,我隨意寫的一段小代碼,這個park的時候,是會造成死鎖的,park的時候,沒有釋放掉鎖, 因此park 和unpark是沒辦法在鎖中用的;包括先notify 在 wait也是會出現死鎖的情況
Thread th=new Thread(new Runnable(){
public void run(){
synchronized(lock){
Locksupport.park();
}
}
}
).start();
synchronized(lock){
Locksupport.unpark(th);
}
- condition 底層是由park和unpark實作的,因此不會出現 先unpark造成死鎖;并且在wait的時候會釋放鎖物件,因此也不會造成死鎖,總的來說,這是最好的選擇
condition 在兩種死鎖方式里面都不會死鎖,
condition代碼應用
實作一個阻塞佇列,只能存盤 n個元素; 這里做了一個很簡單的阻塞佇列,主要利用的兩個Condition 一個讀取condition 一個寫入condition 分別取控制
public class MyQueue {
Lock lock = new ReentrantLock();
Condition putCondition = lock.newCondition();
Condition takeCondition = lock.newCondition();
private volatile int size = 0;
public MyQueue() {
size = 11;
};
public MyQueue(int size) {
this.size = size;
};
private List<Object> list = new ArrayList<Object>();
public void put(Object o) throws InterruptedException {
lock.lock();
try {
if (list.size() < size) {
list.add(o);
takeCondition.signal();
} else {
while (true) {
putCondition.await();
}
}
} finally {
lock.unlock();
}
}
public Object take() throws InterruptedException {
lock.lock();
Object obj;
try {
if (list.size() == 0) {
while (true) {
takeCondition.await();
}
} else {
obj = list.get(0);
list.remove(0);
putCondition.signal();
}
} finally {
lock.unlock();
}
return obj;
}
}

Synchronized和Lock介面
總結
整篇文章介紹的lock經典的實作reentrantLock與condition,而這個實作是對比著synchronized關鍵字實作的,有很多相似的地方,但由于是doug lea 自己實作的,因此有很大的可變性,而且還非常好用,希望大家看了有所理解和成長
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/300964.html
標籤:其他
上一篇:【威脅情報】威脅情報平臺鏈接
下一篇:iptables防火墻
