天天看點

尚矽谷JUC高并發程式設計學習筆記(3)多線程鎖

一、多線程鎖

某一個時刻内,隻能有唯一一個線程去通路這些synchronized 方法。

所有的靜态同步方法用的也是同一把鎖——​

​類對象本身​

​,這兩把鎖是兩個不同的對象,是以靜态同步方法與非靜态同步方法之間是不會有競态條件的。但是一旦一個靜态同步方法擷取鎖後,其他的靜态同步方法都必須等待該方法釋放鎖後才能擷取鎖,而不管是同一個執行個體對象的靜态同步方法之間,還是不同的執行個體對象的靜态同步方法之間,隻要它們同一個類的執行個體對象。

synchronized鎖的是方法,則是對象鎖
同個對象鎖的機制要等待,不同對象鎖的機制調用同一個不用等待
加了static則為class鎖而不是對象鎖      
尚矽谷JUC高并發程式設計學習筆記(3)多線程鎖

通過具體的執行個體進行分析

兩個線程安全方法一個普通方法

class Phone {

    public synchronized void sendSMS() throws Exception {
        //停留4秒
        TimeUnit.SECONDS.sleep(4);
        System.out.println("------sendSMS");
    }

    public synchronized void sendEmail() throws Exception {
        System.out.println("------sendEmail");
    }

    public void getHello() {
        System.out.println("------getHello");
    }
}

public class Lock_8 {
    public static void main(String[] args) throws Exception {

        Phone phone = new Phone();
        Phone phone2 = new Phone();

        new Thread(() -> {
            try {
                phone.sendSMS();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, "AA").start();

        Thread.sleep(100);

        new Thread(() -> {
            try {
               // phone.sendEmail();
               // phone.getHello();
                phone2.sendEmail();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, "BB").start();
    }
}      

具體八種情況為

1 标準通路,先列印短信還是郵件
------sendSMS
------sendEmail
2 停4秒在短信方法内,先列印短信還是郵件
------sendSMS
------sendEmail
3 新增普通的hello方法,是先打短信還是hello
------getHello
------sendSMS
4 現在有兩部手機,先列印短信還是郵件
------sendEmail
------sendSMS
5 兩個靜态同步方法,1部手機,先列印短信還是郵件
------sendSMS
------sendEmail
6 兩個靜态同步方法,2部手機,先列印短信還是郵件
------sendSMS
------sendEmail
7 1個靜态同步方法,1個普通同步方法,1部手機,先列印短信還是郵件
------sendEmail
------sendSMS
8 1個靜态同步方法,1個普通同步方法,2部手機,先列印短信還是郵件
------sendEmail
------sendSMS      

總結:

1.- 同樣的對象通路不同的同步鎖,是按照順序執行
同樣的對象通路同步鎖與不同步鎖,是先不同步鎖執行
不同對象通路不同同步鎖,按照順序執行

2.- 同一對象通路不同靜态同步鎖,按照順序執行
不同對象通路不同靜态同步鎖,按照順序執行

3.- 同一對象通路一個靜态同步鎖,一個同步鎖,先執行同步鎖
不同對象通路一個靜态同步鎖,一個同步鎖,先執行同步鎖
即先出同步鎖在出靜态同步鎖      

二、公平鎖和非公平鎖

公平鎖:效率相對低

非公平鎖:效率高,但是線程容易餓死

通過檢視源碼,帶有參數的ReentrantLock(true)為公平鎖,ReentrantLock(false)為非公平鎖

主要是調用NonfairSync()與FairSync()

public ReentrantLock() {
    sync = new NonfairSync();
}

/**
 * Creates an instance of {@code ReentrantLock} with the
 * given fairness policy.
 *
 * @param fair {@code true} if this lock should use a fair ordering policy
 */
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}      

具體其非公平鎖與公平鎖的源碼

檢視公平鎖的源碼

static final class FairSync extends Sync {
   private static final long serialVersionUID = -3000897897090466540L;

  /**
  * Acquires only if reentrant or queue is empty.
   */
  final boolean initialTryLock() {
   Thread current = Thread.currentThread();
   int c = getState();
   if (c == 0) {
   if (!hasQueuedThreads() && compareAndSetState(0, 1)) {
     setExclusiveOwnerThread(current);
      return true;
    }
    } else if (getExclusiveOwnerThread() == current) {
      if (++c < 0) // overflow
          throw new Error("Maximum lock count exceeded");
         setState(c);
         return true;
       }
    return false;
}      

通過代碼執行個體具體操作

在最前面售票代碼中,繼續引用前面的代碼

//第一步  建立資源類,定義屬性和和操作方法
class LTicket {
    //票數量
    private int number = 30;

    //建立可重入鎖
    private final ReentrantLock lock = new ReentrantLock();
    //賣票方法
    public void sale() {
        //上鎖
        lock.lock();
        try {
            //判斷是否有票
            if(number > 0) {
                System.out.println(Thread.currentThread().getName()+" :賣出"+(number--)+" 剩餘:"+number);
            }
        } finally {
            //解鎖
            lock.unlock();
        }
    }
}

public class LSaleTicket {
    //第二步 建立多個線程,調用資源類的操作方法
    //建立三個線程
    public static void main(String[] args) {

        LTicket ticket = new LTicket();

new Thread(()-> {
    for (int i = 0; i < 40; i++) {
        ticket.sale();
    }
},"AA").start();

        new Thread(()-> {
            for (int i = 0; i < 40; i++) {
                ticket.sale();
            }
        },"BB").start();

        new Thread(()-> {
            for (int i = 0; i < 40; i++) {
                ticket.sale();
            }
        },"CC").start();
    }
}      

輸出結果

AA :賣出30 剩餘:29
AA :賣出29 剩餘:28
AA :賣出28 剩餘:27
AA :賣出27 剩餘:26
AA :賣出26 剩餘:25
AA :賣出25 剩餘:24
AA :賣出24 剩餘:23
AA :賣出23 剩餘:22
AA :賣出22 剩餘:21
AA :賣出21 剩餘:20
AA :賣出20 剩餘:19
AA :賣出19 剩餘:18
AA :賣出18 剩餘:17
AA :賣出17 剩餘:16
AA :賣出16 剩餘:15
AA :賣出15 剩餘:14
AA :賣出14 剩餘:13
AA :賣出13 剩餘:12
AA :賣出12 剩餘:11
AA :賣出11 剩餘:10
AA :賣出10 剩餘:9
AA :賣出9 剩餘:8
AA :賣出8 剩餘:7
AA :賣出7 剩餘:6
AA :賣出6 剩餘:5
AA :賣出5 剩餘:4
AA :賣出4 剩餘:3
AA :賣出3 剩餘:2
AA :賣出2 剩餘:1
AA :賣出1 剩餘:0

Process finished with exit code 0      

都是A線程執行,而BC線程都沒執行到(或者某個線程出現的機率很大,其餘線程出現的機率很小),出現了非公平鎖。

具體改變其設定可以通過可重入鎖中的一個有參構造方法

修改代碼為​

​private final ReentrantLock lock = new ReentrantLock(true);​

這樣就是​

​公平鎖​

​,可以讓每個線程都有出現的機會。

結果如下:

AA :賣出30 剩餘:29
AA :賣出29 剩餘:28
AA :賣出28 剩餘:27
AA :賣出27 剩餘:26
AA :賣出26 剩餘:25
AA :賣出25 剩餘:24
AA :賣出24 剩餘:23
AA :賣出23 剩餘:22
AA :賣出22 剩餘:21
AA :賣出21 剩餘:20
AA :賣出20 剩餘:19
AA :賣出19 剩餘:18
AA :賣出18 剩餘:17
AA :賣出17 剩餘:16
BB :賣出16 剩餘:15
AA :賣出15 剩餘:14
CC :賣出14 剩餘:13
BB :賣出13 剩餘:12
AA :賣出12 剩餘:11
CC :賣出11 剩餘:10
BB :賣出10 剩餘:9
AA :賣出9 剩餘:8
CC :賣出8 剩餘:7
BB :賣出7 剩餘:6
AA :賣出6 剩餘:5
CC :賣出5 剩餘:4
BB :賣出4 剩餘:3
AA :賣出3 剩餘:2
CC :賣出2 剩餘:1
BB :賣出1 剩餘:0

Process finished with exit code 0      

三、可重入鎖

synchronized和lock都是可重入鎖

sychronized是隐式鎖,不用手工上鎖與解鎖,而lock為顯示鎖,需要手工上鎖與解鎖。

可重入鎖也叫遞歸鎖

而且有了可重入鎖之後,破解第一把之後就可以一直進入到内層結構。

Object o = new Object();
new Thread(()->{
    synchronized(o) {
        System.out.println(Thread.currentThread().getName()+" 外層");

        synchronized (o) {
            System.out.println(Thread.currentThread().getName()+" 中層");

            synchronized (o) {
                System.out.println(Thread.currentThread().getName()+" 内層");
            }
        }
    }

},"t1").start();      

結果

t1 外層
t1 中層
t1 内層

Process finished with exit code 0      

synchronized (o)代表鎖住目前{ }内的代碼塊

以上都是synchronized鎖機制

下面講解lock鎖機制

public class SyncLockDemo {

    public synchronized void add() {
        add();
    }

    public static void main(String[] args) {
        //Lock示範可重入鎖
        Lock lock = new ReentrantLock();
        //建立線程
        new Thread(()->{
            try {
                //上鎖
                lock.lock();
                System.out.println(Thread.currentThread().getName()+" 外層");

                try {
                    //上鎖
                    lock.lock();
                    System.out.println(Thread.currentThread().getName()+" 内層");
                }finally {
                    //釋放鎖
                    lock.unlock();
                }
            }finally {
                //釋放做
                lock.unlock();
            }
        },"t1").start();

        //建立新線程
        new Thread(()->{
            lock.lock();
            System.out.println("aaaa");
            lock.unlock();
        },"aa").start();
        }
 }      

結果

t1 外層
t1 内層
aaaa

Process finished with exit code 0      

在同一把鎖中的嵌套鎖,内部嵌套鎖沒解鎖還是可以輸出,但是如果跳出該線程,執行另外一個線程就會造成死鎖。

要把握上鎖與解鎖的概念,都要寫上。

四、死鎖

兩個或以上的程序因為争奪資源而造成互相等待資源的現象稱為死鎖。

尚矽谷JUC高并發程式設計學習筆記(3)多線程鎖

産生死鎖的原因:

系統資源不足
系統資源配置設定不當
程序運作順序不當      

驗證是否是死鎖

jps 類似于linux中的ps -ef檢視程序号
jstack 自帶的堆棧跟蹤工具      

死鎖驗證過程:

1、死鎖代碼:

public class DeadLock {

    //建立兩個對象
    static Object a = new Object();
    static Object b = new Object();

    public static void main(String[] args) {
        new Thread(()->{
            synchronized (a) {
                System.out.println(Thread.currentThread().getName()+" 持有鎖a,試圖擷取鎖b");
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (b) {
                    System.out.println(Thread.currentThread().getName()+" 擷取鎖b");
                }
            }
        },"A").start();

        new Thread(()->{
            synchronized (b) {
                System.out.println(Thread.currentThread().getName()+" 持有鎖b,試圖擷取鎖a");
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (a) {
                    System.out.println(Thread.currentThread().getName()+" 擷取鎖a");
                }
            }
        },"B").start();
    }
}      

程式沒有停止

尚矽谷JUC高并發程式設計學習筆記(3)多線程鎖

通過用idea自帶的指令行輸入 ​

​jps -l​

檢視其編譯代碼的程序号後​

​jstack 程序号​

尚矽谷JUC高并發程式設計學習筆記(3)多線程鎖