【併發】Lock與ReentrantLock 1 Lock基本使用 2 ReentrantLock

1 Lock基本使用

Lock能實現代碼同步,它比synchronized更具靈活性,什麼時候鎖住,什麼時候釋放鎖等都是看得見的,使用時必須使用try{}finally{},意思是萬一發生異常或者錯誤都可以釋放鎖。

try{
}finally{
    //釋放鎖
}
  • 使用示例
public class SaleTicket implements Runnable {
    private int ticket = 10 * 100000;
    private Lock mLock = new ReentrantLock();

    @Override
    public void run() {
        try {
            while (ticket > 0) {
                mLock.lock();
                if(ticket>0){
                    if (ticket % 100000 == 0) {
                        System.out.println("名稱:" + Thread.currentThread().getName() + "窗口賣出第" + (ticket / 100000) + "張票");
                    }
                    ticket--;
                }
            }
        } finally {
            mLock.unlock();
        }
    }
}
        Runnable saleTicket = new SaleTicket();
        Thread thread1 = new Thread(saleTicket);
        Thread thread2 = new Thread(saleTicket);
        Thread thread3 = new Thread(saleTicket);
        Thread thread4 = new Thread(saleTicket);

        thread1.start();
        thread2.start();
        thread3.start();
        thread4.start();

結果:


2 ReentrantLock

ReentrantLock有兩種鎖機制:忽略中斷鎖和響應中斷鎖。如:如果A、B兩個線程去競爭鎖,A獲得鎖,B等待,但A線程要做的事情太多,一直不返回鎖,B線程就想先中斷自己不再等待這個鎖,轉而去處理其他事情。這時候ReentrantLock提供了2種機制,第一種,B線程中斷自己(或者別的線程中斷它),但是ReentrantLock不去響應,繼續讓B線程等待,這就是忽略中斷鎖(lock());第二種,B線程中斷自己(或者別的線程中斷它),ReentrantLock處理了這個中斷,並且不再等待這個鎖的到來,完全放棄,這就是響應中斷鎖(lockInterruptibly())。

  • 響應中斷鎖示例:
public class BufferInterruptibly {
    private ReentrantLock mLock = new ReentrantLock();
    public void write(){
        mLock.lock();
        try {
            long startTime = System.currentTimeMillis();
            System.out.println("開始往這個buff寫入數據…");
            for (; ; ) {
                if (System.currentTimeMillis() - startTime > Integer.MAX_VALUE) {
                    break;
                }
            }
            System.out.println("終於寫完了");
        }finally {
            mLock.unlock();
        }
    }
    public void read() throws InterruptedException{
        mLock.lockInterruptibly();
        try{
            System.out.println("從這個buff讀數據");
        }finally {
            mLock.unlock();
        }
    }
}
final BufferInterruptibly buff = new BufferInterruptibly();
        Thread write = new Thread(new Runnable() {
            @Override
            public void run() {
                buff.write();
            }
        });
        final Thread read = new Thread(new Runnable() {
            @Override
            public void run() {
                try{
                    Thread.sleep(500);
                    buff.read();
                }catch (Exception e){
                    System.out.println("我不讀了");
                }
                System.out.println("讀結束");
            }
        });

        write.start();
        read.start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                long start = System.currentTimeMillis();
                for (;;){
                    if (System.currentTimeMillis()-start>500){
                        System.out.println("不等了,嘗試中斷");
                        read.interrupt();
                        break;
                    }
                }
            }
        }).start();

結果:


ReentrantLock叫做重入鎖,意思是外層方法獲取到鎖後,內層方法會自動獲取到鎖。synchronized也是可重入鎖。我們先看一下synchronized鎖的可重入性

class SychronizedReentrant implements Runnable{
    private Object object = new Object();
    /**
     * 方法一調用方法二
     */
    public void method1(){
        synchronized (object){
            System.out.println(Thread.currentThread().getName()+ " method1 ");
            method2();
        }
    }
    
    /**
        方法二,打印獲取obj鎖
        如果是同一線程,鎖不可以重入,method2需要等待method1釋放鎖
         */
    public void method2(){
        
        synchronized (object){
            System.out.println(Thread.currentThread().getName()+ " method2 ");
        }
    }

    @Override
    public void run() {
        method1();
    }
}

結果

Thread-0 method1
Thread-0 method2

method1有一個同步塊,同步塊中調用了method2,而method2中也有個同步塊,一般來說,synchronized塊裏面的內容執行完纔會釋放鎖,其它synchronized塊才能競爭到鎖,而向上述調用步驟,明顯是外部方法的synchronized還沒有釋放鎖,內部方法的synchronized就可以得到鎖,這就是重入鎖。

例子(摘抄網上):

package tags;

import java.util.Calendar;

public class TestLock {
    private ReentrantLock lock = null;
    
    public int data = 100;     // 用於線程同步訪問的共享數據

    public TestLock() {
        lock = new ReentrantLock(); // 創建一個自由競爭的可重入鎖
    }
    public ReentrantLock getLock() {
        return lock;
    }
    
    public void testReentry() {
        lock.lock();
        Calendar now = Calendar.getInstance();
        System.out.println(now.getTime() + " " + Thread.currentThread()    + " get lock.");
    }

    public static void main(String[] args) {
        TestLock tester = new TestLock();

        //1、測試可重入
        tester.testReentry();
        tester.testReentry(); // 能執行到這裏而不阻塞,表示鎖可重入
        tester.testReentry(); // 再次重入

        // 釋放重入測試的鎖,要按重入的數量解鎖,否則其他線程無法獲取該鎖。
        tester.getLock().unlock();
        tester.getLock().unlock();
        tester.getLock().unlock();

        //2、測試互斥
        // 啓動3個線程測試在鎖保護下的共享數據data的訪問
        new Thread(new workerThread(tester)).start();
        new Thread(new workerThread(tester)).start();
        new Thread(new workerThread(tester)).start();
    }


    // 線程調用的方法
    public void testRun() throws Exception {
        lock.lock();

        Calendar now = Calendar.getInstance();
        try {
            // 獲取鎖後顯示 當前時間 當前調用線程 共享數據的值(並使共享數據 + 1)
            System.out.println(now.getTime() + " " + Thread.currentThread()+ " accesses the data " + data++);
            Thread.sleep(1000);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

// 工作線程,調用TestServer.testRun
class workerThread implements Runnable {

    private TestLock tester = null;

    public workerThread(TestLock testLock) {
        this.tester = testLock;
    }

    public void run() {
        try {
            tester.testRun();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章