【併發編程】 --- synchronized/ReentrantLock兩大特性(可重入性和不可中斷性)介紹

源碼地址:https://github.com/nieandsun/concurrent-study.git


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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章