ReentrantLock你了解多少?

內(nèi)容簡介

ReentrantLock類的使用。
ReentrantReadWriteLock類的使用。

4.1 使用ReentrantLock類

在多線程中,可以使用synchronized來實(shí)現(xiàn)線程之間的同步互斥,在JDK1.5中新增了ReentrantLock類也可打到同樣的效果,并在拓展功能上更強(qiáng)大,比如:嗅探鎖定、多路分支通知等,使用上比synchronized更加靈活,下面我們在學(xué)習(xí)中來任務(wù)兩者的異同吧!

4.1.1 使用ReentrantLock實(shí)現(xiàn)同步

public class MyService {
    private Lock lock = new ReentrantLock();
    public void testMethod() {
        lock.lock();
        for (int i = 0; i < 2; i++) {
            System.out.println("ThreadName" + Thread.currentThread().getName() + " " + i);
        }
        lock.unlock();
    }
}

lock()獲取鎖,unlock()釋放鎖。

public class MyThread extends Thread {
    private MyService myService;
    public MyThread(MyService myService) {
        this.myService = myService;
    }
    @Override
    public void run() {
        myService.testMethod();
    }
}
public class Run {
    public static void main(String[] args) {
        MyService myService = new MyService();
        MyThread m1 = new MyThread(myService);
        MyThread m2 = new MyThread(myService);
        MyThread m3 = new MyThread(myService);
        MyThread m4 = new MyThread(myService);
        MyThread m5 = new MyThread(myService);
        m1.start();
        m2.start();
        m3.start();
        m4.start();
        m5.start();
    }
}
ThreadNameThread-1 0
ThreadNameThread-1 1
ThreadNameThread-0 0
ThreadNameThread-0 1
ThreadNameThread-3 0
ThreadNameThread-3 1
ThreadNameThread-4 0
ThreadNameThread-4 1
ThreadNameThread-2 0
ThreadNameThread-2 1

從打印結(jié)果來看,當(dāng)一個(gè)線程調(diào)用unlock()方法釋放鎖后,其他線程搶到鎖繼續(xù)執(zhí)行,下面我們繼續(xù)做個(gè)試驗(yàn):

public class MyService {
    private Lock lock = new ReentrantLock();
    public void methodA() {
        try {
            lock.lock();
            System.out.println("methodA begin threadNAME=" + Thread.currentThread().getName() + " Time=" + System.currentTimeMillis());
            Thread.sleep(2000);
            System.out.println("methodA   end threadNAME=" + Thread.currentThread().getName() + " Time=" + System.currentTimeMillis());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public void methodB() {
        try {
            lock.lock();
            System.out.println("methodB begin threadNAME=" + Thread.currentThread().getName() + " Time=" + System.currentTimeMillis());
            Thread.sleep(2000);
            System.out.println("methodB   end threadNAME=" + Thread.currentThread().getName() + " Time=" + System.currentTimeMillis());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}
public class ThreadA extends Thread {
    private MyService myService;
    public ThreadA(MyService myService) {
        this.myService = myService;
    }
    @Override
    public void run() {
        myService.methodA();
    }
}
public class ThreadAA extends Thread {
    private MyService myService;
    public ThreadAA(MyService myService) {
        this.myService = myService;
    }
    @Override
    public void run() {
        myService.methodA();
    }
}
public class ThreadB extends Thread {
    private MyService myService;
    public ThreadB(MyService myService) {
        this.myService = myService;
    }
    @Override
    public void run() {
        myService.methodB();
    }
}
public class ThreadBB extends Thread {
    private MyService myService;
    public ThreadBB(MyService myService) {
        this.myService = myService;
    }
    @Override
    public void run() {
        myService.methodB();
    }
}
public class Run {
    public static void main(String[] args) throws InterruptedException {
        MyService myService = new MyService();
        ThreadA a = new ThreadA(myService);
        a.setName("a");
        a.start();
        ThreadAA aa = new ThreadAA(myService);
        aa.setName("aa");
        aa.start();
        Thread.sleep(100);
        ThreadB b = new ThreadB(myService);
        b.setName("b");
        b.start();
        ThreadBB bb = new ThreadBB(myService);
        bb.setName("bb");
        bb.start();
    }
}
methodA begin threadNAME=a Time=1590990695499
methodA   end threadNAME=a Time=1590990697502
methodA begin threadNAME=aa Time=1590990697502
methodA   end threadNAME=aa Time=1590990699506
methodB begin threadNAME=bb Time=1590990699506
methodB   end threadNAME=bb Time=1590990701507
methodB begin threadNAME=b Time=1590990701507
methodB   end threadNAME=b Time=1590990703509

驗(yàn)證結(jié)果:調(diào)用lock()代碼的線程就持有了對(duì)象監(jiān)視器,其他線程只有等待持有鎖的線程釋放。

4.1.2 使用Condition實(shí)現(xiàn)等待/通知

synchronized與wait(),notify()或notifyAll()方法相結(jié)合,可以實(shí)現(xiàn)等待/通知模式。(對(duì)wait(),notify()或notifyAll()需要了解的可以看看一文詳解wait與notify
ReentrantLock也可以實(shí)現(xiàn)同樣的功能,但需要借助與Condition對(duì)象。Condition是JDK5中出現(xiàn)的,它有更好的靈活性,比如實(shí)現(xiàn)多路通知功能,也就是在一個(gè)Lock對(duì)象里面可以創(chuàng)建多個(gè)Condition(即對(duì)象監(jiān)視器)實(shí)例,線程對(duì)象可以注冊在指定的Condition中,從而有選擇的進(jìn)行通知,在調(diào)度線程上更加靈活。
使用nodify()/notifyAll()方法進(jìn)行通知時(shí),被通知的線程是JVM隨機(jī)選擇的。ReentrantLock結(jié)合Condition類是可以實(shí)現(xiàn)選擇性通知。
synchronize就相當(dāng)于整個(gè)Lock對(duì)象中只有一個(gè)單一的Condition對(duì)象,所有線程都注冊在它一個(gè)對(duì)象的身上,notifyAll()時(shí),需要通知所有的等待該對(duì)象的線程,JVM隨機(jī)喚醒其中一個(gè)。

Object與Condition對(duì)比

調(diào)用Condition.await方法前,如果沒有持有合適的鎖,則會(huì)報(bào)java.lang.IllegalMonitorStateException的異常,我們可以通過ReentrantLock.lock()來獲取鎖。

下面我們使用Condition來實(shí)現(xiàn)一個(gè)等待通知模式:

public class MyService {
    private Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();

    public void await() {
        try {
            lock.lock();
            System.out.println("await start 時(shí)間為:" + System.currentTimeMillis());
            condition.await();
            System.out.println("await end   時(shí)間為:" + System.currentTimeMillis());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
    public void signal(){
        try {
            lock.lock();
            System.out.println("signal時(shí)間為:"+System.currentTimeMillis());
            condition.signal();
        }finally {
            lock.unlock();
        }
    }
}
public class MyThread extends Thread {
    private MyService myService;
    public MyThread(MyService myService) {
        this.myService = myService;
    }
    @Override
    public void run() {
        myService.await();
    }
}
public class Run {
    public static void main(String[] args) throws InterruptedException {
        MyService myService = new MyService();
        MyThread myThread = new MyThread(myService);
        myThread.start();
        Thread.sleep(3000);
        myService.signal();
    }
}
await start 時(shí)間為:1590995422964
signal時(shí)間為:1590995425971
await end   時(shí)間為:1590995425971

1)Object中的wait()方法相當(dāng)于Condition類中的await()。
2)Object中的wait(long timeout)相當(dāng)于Condition中的await(long time,TimeUnit unit)
3)Object中的notify()相當(dāng)于Condition中的signal()。
4)Object中的notifyAll()相當(dāng)于Condition中的signalAll()。

4.1.3 使用多個(gè)Condition實(shí)現(xiàn)通知部分線程

如何使用單個(gè)Condition來通知所有線程呢?

public class MyService {
    private Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();
    public void awaitA() {
        try {
            lock.lock();
            System.out.println("begin awaitA time = " + System.currentTimeMillis() + " thread Name = " + Thread.currentThread().getName());
            condition.await();
            System.out.println("  end awaitA time = " + System.currentTimeMillis() + " thread Name = " + Thread.currentThread().getName());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public void awaitB() {
        try {
            lock.lock();
            System.out.println("begin awaitB time = " + System.currentTimeMillis() + " thread Name = " + Thread.currentThread().getName());
            condition.await();
            System.out.println("  end awaitB time = " + System.currentTimeMillis() + " thread Name = " + Thread.currentThread().getName());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public void signalAll() {
        try {
            lock.lock();
            System.out.println("   signalAll time = " + System.currentTimeMillis() + " thread Name = " + Thread.currentThread().getName());
            condition.signalAll();
        } finally {
            lock.unlock();
        }
    }
}
public class ThreadA extends Thread {
    private MyService myService;
    public ThreadA(MyService myService) {
        this.myService = myService;
    }
    @Override
    public void run() {
        myService.awaitA();
    }
}
public class ThreadB extends Thread {
    private MyService myService;
    public ThreadB(MyService myService) {
        this.myService = myService;
    }
    @Override
    public void run() {
        myService.awaitB();
    }
}

public class Run {
    public static void main(String[] args) throws InterruptedException {
        MyService myService = new MyService();
        ThreadA a = new ThreadA(myService);
        a.setName("a");
        a.start();
        ThreadB b = new ThreadB(myService);
        b.setName("b");
        b.start();
        Thread.sleep(2000);
        myService.signalAll();
    }
}
begin awaitB time = 1590998606632 thread Name = b
begin awaitA time = 1590998606632 thread Name = a
   signalAll time = 1590998608626 thread Name = main
  end awaitB time = 1590998608626 thread Name = b
  end awaitA time = 1590998608626 thread Name = a

那么如何使用多個(gè)Condition通知部分線程呢?

public class MyService {
    private Lock lock = new ReentrantLock();
    private Condition conditionA = lock.newCondition();
    private Condition conditionB = lock.newCondition();
    public void awaitA() {
        try {
            lock.lock();
            System.out.println("begin awaitA time = " + System.currentTimeMillis() + " thread Name = " + Thread.currentThread().getName());
            conditionA.await();
            System.out.println("  end awaitA time = " + System.currentTimeMillis() + " thread Name = " + Thread.currentThread().getName());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public void awaitB() {
        try {
            lock.lock();
            System.out.println("begin awaitB time = " + System.currentTimeMillis() + " thread Name = " + Thread.currentThread().getName());
            conditionB.await();
            System.out.println("  end awaitB time = " + System.currentTimeMillis() + " thread Name = " + Thread.currentThread().getName());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public void signalAllA() {
        try {
            lock.lock();
            System.out.println("  signalAllA time = " + System.currentTimeMillis() + " thread Name = " + Thread.currentThread().getName());
            conditionA.signalAll();
        } finally {
            lock.unlock();
        }
    }
    public void signalAllB() {
        try {
            lock.lock();
            System.out.println("  signalAllB time = " + System.currentTimeMillis() + " thread Name = " + Thread.currentThread().getName());
            conditionA.signalAll();
        } finally {
            lock.unlock();
        }
    }
}
public class ThreadA extends Thread {
    private MyService myService;
    public ThreadA(MyService myService) {
        this.myService = myService;
    }
    @Override
    public void run() {
        myService.awaitA();
    }
}
public class ThreadB extends Thread {
    private MyService myService;
    public ThreadB(MyService myService) {
        this.myService = myService;
    }
    @Override
    public void run() {
        myService.awaitB();
    }
}

public class Run {
    public static void main(String[] args) throws InterruptedException {
        MyService myService = new MyService();
        ThreadA a = new ThreadA(myService);
        a.setName("a");
        a.start();
        ThreadB b = new ThreadB(myService);
        b.setName("b");
        b.start();
        Thread.sleep(1000);
        myService.signalAllA();
        Thread.sleep(1000);
        myService.signalAllB();
    }
}
begin awaitB time = 1590999063461 thread Name = b
begin awaitA time = 1590999063485 thread Name = a
  signalAllA time = 1590999064453 thread Name = main
  end awaitA time = 1590999064453 thread Name = a
  signalAllB time = 1590999065453 thread Name = main

4.1.4 使用Condition實(shí)現(xiàn)生產(chǎn)者/消費(fèi)者模式

單生產(chǎn)者與單消費(fèi)者代碼示例:

public class MyService {
    private ReentrantLock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();
    private boolean hasValue = false;

    public void set() {
        try {
            lock.lock();
            while (hasValue == true) {
                condition.await();
            }
            System.out.println("打印★");
            hasValue = true;
            condition.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public void get() {
        try {
            lock.lock();
            while (hasValue == false) {
                condition.await();
            }
            System.out.println("打印☆");
            hasValue = false;
            condition.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}
public class ThreadA extends Thread {
    private MyService myService;
    public ThreadA(MyService myService) {
        this.myService = myService;
    }
    @Override
    public void run() {
        for (int i = 0; i < Integer.MAX_VALUE; i++) {
            myService.set();
        }
    }
}
public class ThreadB extends Thread {
    private MyService myService;
    public ThreadB(MyService myService) {
        this.myService = myService;
    }
    @Override
    public void run() {
        for (int i = 0; i < Integer.MAX_VALUE; i++) {
            myService.get();
        }
    }
}
public class Run {
    public static void main(String[] args) {
        MyService myService = new MyService();
        ThreadA a = new ThreadA(myService);
        a.start();
        ThreadB b = new ThreadB(myService);
        b.start();
    }
}
···
打印☆
打印★
打印☆
打印★
打印☆
打印★
···

那么多生產(chǎn)與多消費(fèi)怎么控制呢 ?

public class MyService {
    private ReentrantLock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();
    private boolean hasValue = false;

    public void set() {
        try {
            lock.lock();
            while (hasValue == true) {
                System.out.println("有可能★★連續(xù)");
                condition.await();
            }
            System.out.println("打印★");
            hasValue = true;
            condition.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public void get() {
        try {
            lock.lock();
            while (hasValue == false) {
                System.out.println("有可能☆☆連續(xù)");
                condition.await();
            }
            System.out.println("打印☆");
            hasValue = false;
            condition.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}
public class ThreadA extends Thread {
    private MyService myService;
    public ThreadA(MyService myService) {
        this.myService = myService;
    }
    @Override
    public void run() {
        for (int i = 0; i < Integer.MAX_VALUE; i++) {
            myService.set();
        }
    }
}
public class ThreadB extends Thread {
    private MyService myService;
    public ThreadB(MyService myService) {
        this.myService = myService;
    }
    @Override
    public void run() {
        for (int i = 0; i < Integer.MAX_VALUE; i++) {
            myService.get();
        }
    }
}
public class Run {
    public static void main(String[] args) {
        MyService myService = new MyService();
        ThreadA[] a = new ThreadA[10];
        ThreadB[] b = new ThreadB[10];
        for (int i = 0; i < 10; i++) {
            a[i] = new ThreadA(myService);
            b[i] = new ThreadB(myService);
            a[i].start();
            b[i].start();
        }
    }
}
...
打印★
有可能★★連續(xù)
打印☆
打印★
有可能★★連續(xù)
有可能★★連續(xù)
有可能★★連續(xù)
有可能★★連續(xù)
有可能★★連續(xù)
有可能★★連續(xù)
打印☆
有可能☆☆連續(xù)
...

4.1.9 公平鎖與非公平鎖

Lock分為公平鎖和非公平鎖。
公平鎖:線程獲取鎖的順序是按照線程加鎖的順序來分配的,即FIFO(先進(jìn)先出)。
非公平鎖:隨機(jī)獲得鎖。

那么下面我們就用代碼來了解兩種鎖的特性吧。
非公平鎖示例:

public class Service {
    private ReentrantLock lock;

    public Service(boolean isFair) {
        this.lock = new ReentrantLock(isFair);
    }
    public void serviceMethod(){
        try {
            lock.lock();
            System.out.println("ThreadName="+Thread.currentThread().getName()+"獲取鎖");
        }finally {
            lock.unlock();
        }
    }
}
public class Run {
    public static void main(String[] args) {
        final Service service = new Service(false);
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                System.out.println("★線程" + Thread.currentThread().getName() + "開始執(zhí)行");
                service.serviceMethod();
            }
        };
        Thread[] threads = new Thread[10];
        for (int i = 0; i < 10; i++) {
            threads[i] = new Thread(runnable);
        }
        for (int i = 0; i < threads.length; i++) {
            threads[i].start();
        }
    }
}
★線程Thread-0開始執(zhí)行
ThreadName=Thread-0獲取鎖
★線程Thread-2開始執(zhí)行
★線程Thread-1開始執(zhí)行
ThreadName=Thread-2獲取鎖
★線程Thread-4開始執(zhí)行
ThreadName=Thread-4獲取鎖
★線程Thread-5開始執(zhí)行
★線程Thread-8開始執(zhí)行
ThreadName=Thread-5獲取鎖
★線程Thread-9開始執(zhí)行
ThreadName=Thread-9獲取鎖
★線程Thread-3開始執(zhí)行
ThreadName=Thread-3獲取鎖
ThreadName=Thread-1獲取鎖
ThreadName=Thread-8獲取鎖
★線程Thread-7開始執(zhí)行
ThreadName=Thread-7獲取鎖
★線程Thread-6開始執(zhí)行
ThreadName=Thread-6獲取鎖

非公平鎖的運(yùn)行結(jié)果基本上是亂序的,先啟動(dòng)的線程不代表先獲得鎖。

公平鎖示例:
修改Run.java代碼如下

public class Run {
    public static void main(String[] args) {
        final Service service = new Service(true);
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                System.out.println("★線程" + Thread.currentThread().getName() + "開始執(zhí)行");
                service.serviceMethod();
            }
        };
        Thread[] threads = new Thread[10];
        for (int i = 0; i < 10; i++) {
            threads[i] = new Thread(runnable);
        }
        for (int i = 0; i < threads.length; i++) {
            threads[i].start();
        }
    }
}
★線程Thread-1開始執(zhí)行
★線程Thread-2開始執(zhí)行
ThreadName=Thread-1獲取鎖
★線程Thread-5開始執(zhí)行
★線程Thread-6開始執(zhí)行
ThreadName=Thread-2獲取鎖
ThreadName=Thread-5獲取鎖
★線程Thread-0開始執(zhí)行
★線程Thread-4開始執(zhí)行
ThreadName=Thread-6獲取鎖
★線程Thread-8開始執(zhí)行
ThreadName=Thread-0獲取鎖
★線程Thread-9開始執(zhí)行
ThreadName=Thread-4獲取鎖
ThreadName=Thread-8獲取鎖
ThreadName=Thread-9獲取鎖
★線程Thread-3開始執(zhí)行
ThreadName=Thread-3獲取鎖
★線程Thread-7開始執(zhí)行
ThreadName=Thread-7獲取鎖

觀察執(zhí)行結(jié)果可以發(fā)現(xiàn),啟動(dòng)的順序與獲得鎖的順序一致。

4.1.10 ReentrantLock中方法的使用

4.1.10.1 getHoldCount()

getHoldCount()查詢當(dāng)前線程保持鎖定的個(gè)數(shù),也就是獲取當(dāng)前線程調(diào)用lock()的次數(shù)。
示例代碼:

public class Service {
    private ReentrantLock lock = new ReentrantLock();
    private void serviceMethod1() {
        try {
            lock.lock();
            System.out.println("serviceMethod1 getHoldCount=" + lock.getHoldCount());
            serviceMethod2();
        } finally {
            lock.unlock();
        }
    }
    private void serviceMethod2() {
        try {
            lock.lock();
            System.out.println("serviceMethod2 getHoldCount=" + lock.getHoldCount());
        } finally {
            lock.unlock();
        }
    }
    public static void main(String[] args) {
        Service service = new Service();
        service.serviceMethod1();
    }
}
serviceMethod1 getHoldCount=1
serviceMethod2 getHoldCount=2

4.1.10.2 getQueueLength()

getQueueLength()返回正等待獲取此鎖定的線程估計(jì)數(shù)。
代碼示例:

public class Service {
    private ReentrantLock lock = new ReentrantLock();

    public void serviceMethod1() {
        try {
            lock.lock();
            System.out.println("ThreadName=" + Thread.currentThread().getName());
            Thread.sleep(10000);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        final Service service = new Service();
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                service.serviceMethod1();
            }
        };
        Thread[] threads = new Thread[10];
        for (int i = 0; i < 10; i++) {
            threads[i] = new Thread(runnable);
        }
        for (int i = 0; i < 10; i++) {
            threads[i].start();
        }
        Thread.sleep(2000);
        System.out.println("有" + service.lock.getQueueLength() + "個(gè)線程在等待獲取鎖!");
    }
}
ThreadName=Thread-0
有9個(gè)線程在等待獲取鎖!
···

4.1.10.3 getWaitQueueLength(Condition condition)

getWaitQueueLength(Condition condition) 返回等待與此鎖定相關(guān)的給定條件的Condition的線程估計(jì)數(shù)。
代碼示例:

public class Service {
    private ReentrantLock lock = new ReentrantLock();
    private Condition newCondition = lock.newCondition();

    public void waitMethod() {
        try {
            lock.lock();
            newCondition.await();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void notifyMethod() {
        try {
            lock.lock();
            System.out.println("有" + lock.getWaitQueueLength(newCondition) + "個(gè)線程正在等待newCondition");
            newCondition.signalAll();
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        final Service service = new Service();
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                service.waitMethod();
            }
        };
        Thread[] threads = new Thread[10];
        for (int i = 0; i < 10; i++) {
            threads[i] = new Thread(runnable);
        }
        for (int i = 0; i < 10; i++) {
            threads[i].start();
        }
        Thread.sleep(2000);
        service.notifyMethod();
    }
}
有10個(gè)線程正在等待newCondition

4.1.10.4 hasQueuedThreads()、hasQueuedThread()

boolean hasQueuedThread(Thread thread)查詢指定線程是否正在等待獲取此鎖定。
boolean hasQueuedThreads()查詢是否有線程正在等待獲取此鎖定。
代碼示例:

public class Service {
    private ReentrantLock lock = new ReentrantLock();
    public void waitMethod() {
        try {
            lock.lock();
            System.out.println("threadName="+ Thread.currentThread().getName());
            Thread.sleep(5000);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        final Service service = new Service();
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                service.waitMethod();
            }
        };
        Thread threadA = new Thread(runnable);
        threadA.start();
        Thread.sleep(500);
        Thread threadB = new Thread(runnable);
        threadB.start();
        Thread.sleep(500);
        System.out.println(service.lock.hasQueuedThread(threadA));
        System.out.println(service.lock.hasQueuedThread(threadB));
        System.out.println(service.lock.hasQueuedThreads());
    }
}
threadName=Thread-0
false
true
true
threadName=Thread-1

4.1.10.5 hasWaiters()

查詢是否有線程正在等候與此鎖定有關(guān)的condition條件。
代碼示例:

public class Service {
    private ReentrantLock lock = new ReentrantLock();
    private Condition newCondition = lock.newCondition();
    public void waitMethod() {
        try {
            lock.lock();
            newCondition.await();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public void notifyMethod() {
        try {
            lock.lock();
            if (lock.hasWaiters(newCondition)) {
                System.out.println("存在正在等待newCondition的線程,線程數(shù):" + lock.getWaitQueueLength(newCondition));
                newCondition.signalAll();
            }
        } finally {
            lock.unlock();
        }
    }
    public static void main(String[] args) throws InterruptedException {
        final Service service = new Service();
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                service.waitMethod();
            }
        };
        Thread[] threads = new Thread[10];
        for (int i = 0; i < 10; i++) {
            threads[i] = new Thread(runnable);
        }
        for (int i = 0; i < 10; i++) {
            threads[i].start();
        }
        Thread.sleep(2000);
        service.notifyMethod();
    }
}
存在正在等待newCondition的線程,線程數(shù):10

4.1.10.6 isFair()

判斷是否是公平鎖。

    /**
     * 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();
    }
    /**
     * Returns {@code true} if this lock has fairness set true.
     *
     * @return {@code true} if this lock has fairness set true
     */
    public final boolean isFair() {
        return sync instanceof FairSync;
    }

4.1.10.6 isHeldByCurrentThread()

查詢當(dāng)前線程是否持有該鎖。

public class Service {
    private ReentrantLock lock ;

    public Service(boolean isFair) {
        lock = new ReentrantLock(isFair);
    }
    public void serviceMethod(){
        try {
            System.out.println(lock.isHeldByCurrentThread());
            lock.lock();
            System.out.println(lock.isHeldByCurrentThread());
        }finally {
            lock.unlock();
        }
    }
    public static void main(String[] args) {
        final Service service = new Service(true);
        new Runnable() {
            @Override
            public void run() {
                service.serviceMethod();
            }
        }.run();
    }
}
false
true

4.1.10.7 isLocked()

查詢鎖定是否由任意線程持有。

public class Service {
    private ReentrantLock lock ;

    public Service(boolean isFair) {
        lock = new ReentrantLock(isFair);
    }
    public void serviceMethod(){
        try {
            System.out.println(lock.isLocked());
            lock.lock();
            System.out.println(lock.isLocked());
        }finally {
            lock.unlock();
        }
    }
    public static void main(String[] args) {
        final Service service = new Service(true);
        new Runnable() {
            @Override
            public void run() {
                service.serviceMethod();
            }
        }.run();
    }
}
false
true

4.1.10.7 lockInterruptibly()

如果當(dāng)前線程未被中斷,則獲取鎖定,如果已經(jīng)被中斷則出現(xiàn)異常。

public class Service {
    private ReentrantLock lock = new ReentrantLock();
    public void serviceMethod() {
        try {
            lock.lockInterruptibly();
            System.out.println("lock begin " + Thread.currentThread().getName());
            for (int i = 0; i < Integer.MAX_VALUE / 10; i++) {
            }
            System.out.println("lock   end " + Thread.currentThread().getName());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        final Service service = new Service();
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                service.serviceMethod();
            }
        };
        new Thread(runnable).start();
        Thread.sleep(500);
        Thread threadB = new Thread(runnable);
        threadB.start();
        threadB.interrupt();
    }
}
lock begin Thread-0
lock   end Thread-0
java.lang.InterruptedException
    at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1220)
    at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:335)

4.1.10.7 tryLock()

調(diào)用時(shí)鎖定未被另一個(gè)線程持有的情況下,才獲取該鎖定。

public class Service {
    private ReentrantLock lock = new ReentrantLock();
    public void getLock() {
        if (lock.tryLock()) {
            System.out.println(Thread.currentThread().getName() + " 獲得鎖");
        } else {
            System.out.println(Thread.currentThread().getName() + " 未獲得鎖");
        }
    }
    public static void main(String[] args) {
        final Service service  = new Service();
        Runnable runnable = new Runnable(){
            @Override
            public void run() {
                service.getLock();
            }
        };
        Thread threadA = new Thread(runnable);
        threadA.start();
        Thread threadB = new Thread(runnable);
        threadB.start();
    }
}
Thread-0 獲得鎖
Thread-1 未獲得鎖

4.1.10.7 tryLock(long timeout, TimeUnit unit)

如果鎖定在給定等待時(shí)間內(nèi)沒有被另一個(gè)線程持有,且當(dāng)前線程未被終端,則獲取該鎖定。

public class Service {
    private ReentrantLock lock = new ReentrantLock();
    public void getLock() {
        try {
            if (lock.tryLock(3, TimeUnit.SECONDS)) {
                System.out.println(Thread.currentThread().getName() + " 獲得鎖的時(shí)間:" + System.currentTimeMillis());
                Thread.sleep(4000);
            } else {
                System.out.println(Thread.currentThread().getName() + " 未獲得鎖時(shí)間:" + System.currentTimeMillis());
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }
    public static void main(String[] args) {
        final Service service = new Service();
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "調(diào)用時(shí)間:" + System.currentTimeMillis());
                service.getLock();
            }
        };
        Thread threadA = new Thread(runnable);
        threadA.start();
        Thread threadB = new Thread(runnable);
        threadB.start();
    }
}
Thread-0調(diào)用時(shí)間:1593673259015
Thread-0 獲得鎖的時(shí)間:1593673259016
Thread-1調(diào)用時(shí)間:1593673259015
Thread-1 未獲得鎖時(shí)間:1593673262017

4.1.10.7 awaitUninterruptibly()

public class Service {
    private ReentrantLock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();
    public void testMethod() {
        try {
            lock.lock();
            System.out.println("wait begin");
            condition.await();
            System.out.println("wait end");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }
    public static void main(String[] args) throws InterruptedException {
        final Service service = new Service();
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                service.testMethod();
            }
        };
        Thread thread = new Thread(runnable);
        thread.start();
        Thread.sleep(1000);
        thread.interrupt();
    }
}
wait begin
java.lang.InterruptedException
    at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.reportInterruptAfterWait(AbstractQueuedSynchronizer.java:2014)
    at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2048)

在sleep狀態(tài)下,停止某一線程,會(huì)進(jìn)入catch語句,并清除停止?fàn)顟B(tài)值,使之變成false。對(duì)于Thread基礎(chǔ)不是很扎實(shí)的話,可以看看Thread基礎(chǔ)回顧一下。

修改代碼如下:

public class Service {
    private ReentrantLock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();
    public void testMethod() {
        try {
            lock.lock();
            System.out.println("wait begin");
            condition.awaitUninterruptibly();
            System.out.println("wait end");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }
    public static void main(String[] args) throws InterruptedException {
        final Service service = new Service();
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                service.testMethod();
            }
        };
        Thread thread = new Thread(runnable);
        thread.start();
        Thread.sleep(1000);
        thread.interrupt();
    }
}
wait begin

對(duì)比結(jié)果:awaitUninterruptibly()await()作用相同,但不會(huì)再等待過程中響應(yīng)中斷。

4.1.16 使用Condition實(shí)現(xiàn)順序執(zhí)行

使用Condition對(duì)象可以對(duì)線程執(zhí)行的業(yè)務(wù)進(jìn)行排序規(guī)劃。
代碼如下:

public class Run {
    volatile private static int nextPrintWho = 1;
    private static ReentrantLock lock = new ReentrantLock();
    private static Condition conditionA = lock.newCondition();
    private static Condition conditionB = lock.newCondition();
    private static Condition conditionC = lock.newCondition();

    public static void main(String[] args) {
        Thread threadA = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    lock.lock();
                    while (nextPrintWho != 1) {
                        conditionA.await();
                    }
                    System.out.println("threadA" + nextPrintWho);
                    nextPrintWho = 2;
                    conditionB.signalAll();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
            }
        });
        Thread threadB = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    lock.lock();
                    while (nextPrintWho != 2) {
                        conditionB.await();
                    }
                    System.out.println("threadB" + nextPrintWho);
                    nextPrintWho = 3;
                    conditionC.signalAll();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
            }
        });
        Thread threadC = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    lock.lock();
                    while (nextPrintWho != 3) {
                        conditionC.await();
                    }
                    System.out.println("threadC" + nextPrintWho);
                    nextPrintWho = 1;
                    conditionA.signalAll();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
            }
        });
        Thread[] threadsA = new Thread[5];
        Thread[] threadsB = new Thread[5];
        Thread[] threadsC = new Thread[5];
        for (int i = 0; i < 5; i++) {
            threadsA[i] = new Thread(threadA);
            threadsB[i] = new Thread(threadB);
            threadsC[i] = new Thread(threadC);
            threadsA[i].start();
            threadsB[i].start();
            threadsC[i].start();
        }
    }
}
threadA1
threadB2
threadC3
threadA1
threadB2
threadC3
threadA1
threadB2
threadC3
threadA1
threadB2
threadC3
threadA1
threadB2
threadC3

4.2 使用ReentrantReadWriteLock

ReentrantLock具有完全互斥排他的效果,即同一時(shí)間只有一個(gè)線程在執(zhí)行ReentrantLock.lock()方法后面的任務(wù)。這樣做雖然保證了實(shí)例變量的線程安全性,但效率卻是非常低下的,所以在JDK中提供了一種讀寫鎖ReentrantReadWriteLock類,在某些不需要操作實(shí)例變量的方法中,完全可以使用讀寫鎖來提升代碼運(yùn)行速度。

讀寫鎖表示也有兩個(gè)鎖,一個(gè)是讀操作相關(guān)的鎖,也稱為共享鎖;另一個(gè)是寫操作相關(guān)的鎖,也叫排他鎖。也就是多個(gè)讀鎖之間不互斥,讀鎖與寫鎖互斥,寫鎖與寫鎖互斥。即多個(gè)線程可以同時(shí)進(jìn)行讀取操作,但是同一時(shí)刻只允許一個(gè)線程進(jìn)行寫操作。

4.2.1 讀讀共享

public class Service {
    private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
    private void read() {
        try {
            lock.readLock().lock();
            System.out.println(Thread.currentThread().getName() + " 獲取到讀鎖 " + System.currentTimeMillis());
            Thread.sleep(5000);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.readLock().unlock();
        }
    }

    public static void main(String[] args) {
        final Service service = new Service();
        Thread threadA = new Thread(new Runnable() {
            @Override
            public void run() {
                service.read();
            }
        });
        Thread threadB = new Thread(new Runnable() {
            @Override
            public void run() {
                service.read();
            }
        });
        threadA.start();
        threadB.start();
    }
}
Thread-1 獲取到讀鎖 1593755228144
Thread-0 獲取到讀鎖 1593755228144

4.2.2 寫寫互斥

public class Service {
    private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
    private void read() {
        try {
            lock.writeLock().lock();
            System.out.println(Thread.currentThread().getName() + " 獲取到寫鎖 " + System.currentTimeMillis());
            Thread.sleep(5000);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.writeLock().unlock();
        }
    }

    public static void main(String[] args) {
        final Service service = new Service();
        Thread threadA = new Thread(new Runnable() {
            @Override
            public void run() {
                service.read();
            }
        });
        Thread threadB = new Thread(new Runnable() {
            @Override
            public void run() {
                service.read();
            }
        });
        threadA.start();
        threadB.start();
    }
}
Thread-0 獲取到寫鎖 1593755364185
Thread-1 獲取到讀鎖 1593755369185

writeLock()在同一時(shí)間只允許一個(gè)線程執(zhí)行l(wèi)ock后面的方法。

4.2.3 讀寫互斥

public class Service {
    private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
    private void read() {
        try {
            lock.readLock().lock();
            System.out.println(Thread.currentThread().getName() + " 獲取到讀鎖 " + System.currentTimeMillis());
            Thread.sleep(5000);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.readLock().unlock();
        }
    }

    private void write() {
        try {
            lock.writeLock().lock();
            System.out.println(Thread.currentThread().getName() + " 獲取到寫鎖 " + System.currentTimeMillis());
            Thread.sleep(5000);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.writeLock().unlock();
        }
    }

    public static void main(String[] args) {
        final Service service = new Service();
        Thread threadA = new Thread(new Runnable() {
            @Override
            public void run() {
                service.read();
            }
        });
        Thread threadB = new Thread(new Runnable() {
            @Override
            public void run() {
                service.write();
            }
        });
        threadA.start();
        threadB.start();
    }
}
Thread-0 獲取到讀鎖 1593756165771
Thread-1 獲取到寫鎖 1593756170771

4.2.4 讀寫互斥

修改以上代碼的main方法如下:

    public static void main(String[] args) throws InterruptedException {
        final Service service = new Service();
        Thread threadA = new Thread(new Runnable() {
            @Override
            public void run() {
                service.read();
            }
        });
        Thread threadB = new Thread(new Runnable() {
            @Override
            public void run() {
                service.write();
            }
        });
        threadB.start();
        Thread.sleep(1000);
        threadA.start();
    }
Thread-1 獲取到寫鎖 1593756318244
Thread-0 獲取到讀鎖 1593756323244

從以上實(shí)驗(yàn)結(jié)果看來,“讀寫”,“寫讀”,“寫寫”都是互斥的,“讀讀”是異步非互斥的。

參考文獻(xiàn)

《Java多線程編程核心技術(shù)》高紅巖

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容