Java多線程(七):ReentrantLock

加鎖和解鎖

咱們來看下ReentrantLock的基本用法
ThreadDomain35類java

public class ThreadDomain35 {

    private Lock lock = new ReentrantLock();

    public void testMethod()
    {
        try
        {
            lock.lock();
            for (int i = 0; i < 2; i++)
            {
                System.out.println("ThreadName = " + Thread.currentThread().getName() + ", i  = " + i);
            }
        }
        finally
        {
            lock.unlock();
        }
    }
}

線程和main方法異步

public class MyThread35 extends Thread {

    private ThreadDomain35 td;

    public MyThread35(ThreadDomain35 td)
    {
        this.td = td;
    }

    public void run()
    {
        td.testMethod();
    }

    public static void main(String[] args)
    {
        ThreadDomain35 td = new ThreadDomain35();
        MyThread35 mt0 = new MyThread35(td);
        MyThread35 mt1 = new MyThread35(td);
        MyThread35 mt2 = new MyThread35(td);
        mt0.start();
        mt1.start();
        mt2.start();
    }
}

輸出結果ide

ThreadName = Thread-2, i  = 0
ThreadName = Thread-2, i  = 1
ThreadName = Thread-0, i  = 0
ThreadName = Thread-0, i  = 1
ThreadName = Thread-1, i  = 0
ThreadName = Thread-1, i  = 1

一個線程必須執行完才能執行下一個線程,說明ReentrantLock能夠加鎖。函數

ReentrantLock持有的對象監視器和synchronized不一樣

ThreadDomain37類,methodB用synchronized修飾this

public class ThreadDomain37 {
    private Lock lock = new ReentrantLock();

    public void methodA()
    {
        try
        {
            lock.lock();
            System.out.println("MethodA begin ThreadName = " + Thread.currentThread().getName());
            Thread.sleep(5000);
            System.out.println("MethodA end ThreadName = " + Thread.currentThread().getName());
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
        finally
        {
            lock.unlock();
        }

    }

    public synchronized void methodB()
    {
        System.out.println("MethodB begin ThreadName = " + Thread.currentThread().getName());
        System.out.println("MethodB begin ThreadName = " + Thread.currentThread().getName());
    }
}

MyThread37_0類線程

public class MyThread37_0 extends Thread {

    private ThreadDomain37 td;

    public MyThread37_0(ThreadDomain37 td)
    {
        this.td = td;
    }

    public void run()
    {
        td.methodA();
    }
}

MyThread37_1類debug

public class MyThread37_1 extends Thread {
    private ThreadDomain37 td;

    public MyThread37_1(ThreadDomain37 td)
    {
        this.td = td;
    }

    public void run()
    {
        td.methodB();
    }
}

MyThread37_main方法code

public class MyThread37_main {

    public static void main(String[] args)
    {
        ThreadDomain37 td = new ThreadDomain37();
        MyThread37_0 mt0 = new MyThread37_0(td);
        MyThread37_1 mt1 = new MyThread37_1(td);
        mt0.start();
        mt1.start();
    }

}

運行結果以下對象

MethodA begin ThreadName = Thread-0
MethodB begin ThreadName = Thread-1
MethodB begin ThreadName = Thread-1
MethodA end ThreadName = Thread-0

加了synchronized依然是異步執行,說明ReentrantLock和synchronized持有的對象監視器不一樣。ReentrantLock須要手動加鎖和釋放鎖。rem

Condition

基本用法

synchronized與wait()和nitofy()/notifyAll()方法能夠實現等待/喚醒模型,ReentrantLock一樣能夠,須要藉助Condition的await()和signal/signalAll(),await()釋放鎖。
ThreadDomain38類

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

    public void await()
    {
        try
        {
            lock.lock();
            System.out.println("await時間爲:" + System.currentTimeMillis());
            condition.await();
            System.out.println("await等待結束");
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
        finally
        {
            lock.unlock();
        }
    }

    public void signal()
    {
        try
        {
            lock.lock();
            System.out.println("signal時間爲:" + System.currentTimeMillis());
            condition.signal();
            System.out.println("signal等待結束");
        }
        finally
        {
            lock.unlock();
        }
    }
}

MyThread38類,線程和main方法

public class MyThread38 extends Thread
{
    private ThreadDomain38 td;

    public MyThread38(ThreadDomain38 td)
    {
        this.td = td;
    }

    public void run()
    {
        td.await();
    }

    public static void main(String[] args) throws Exception
    {
        ThreadDomain38 td = new ThreadDomain38();
        MyThread38 mt = new MyThread38(td);
        mt.start();
        Thread.sleep(3000);
        td.signal();
    }
}

運行結果以下

await時間爲:1563505465346
signal時間爲:1563505468345
signal等待結束
await等待結束

能夠看到,ReentrantLock和Condition實現了等待/通知模型。
一個Lock能夠建立多個Condition;
notify()喚醒的線程是隨機的,signal()能夠有選擇性地喚醒。

Condition選擇 喚醒/等待

如今看一個利用Condition選擇等待和喚醒的例子
ThreadDomain47類,定義add和sub方法

public class ThreadDomain47 {
    private final Lock lock = new ReentrantLock();

    private final Condition addCondition = lock.newCondition();

    private final Condition subCondition = lock.newCondition();


    private static int num = 0;
    private List<String> lists = new LinkedList<String>();

    public void add() {
        lock.lock();

        try {
            while(lists.size() == 10) {//當集合已滿,則"添加"線程等待
                addCondition.await();
            }

            num++;
            lists.add("add Banana" + num);
            System.out.println("The Lists Size is " + lists.size());
            System.out.println("The Current Thread is " + "增長線程");
            System.out.println("==============================");
            this.subCondition.signal();

        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {//釋放鎖
            lock.unlock();
        }
    }


    public void sub() {
        lock.lock();

        try {
            while(lists.size() == 0) {//當集合爲空時,"減小"線程等待
                subCondition.await();
            }

            String str = lists.get(0);
            lists.remove(0);
            System.out.println("The Token Banana is [" + str + "]");
            System.out.println("The Current Thread is " + "減小線程");
            System.out.println("==============================");
            num--;
            addCondition.signal();

        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

MyThread40_0類,增長線程

public class MyThread40_0 implements Runnable {

    private ThreadDomain47 task;

    public MyThread40_0(ThreadDomain47 task) {
        this.task = task;
    }

    @Override
    public void run() {
        task.add();
    }

}

MyThread40_1類,減小線程

public class MyThread40_1 implements Runnable {
    private ThreadDomain47 task;

    public MyThread40_1(ThreadDomain47 task) {
        this.task = task;
    }

    @Override
    public void run() {
        task.sub();
    }

}

main方法,啓動線程

public class MyThread40_main {
    public static void main(String[] args) {
        ThreadDomain47 task = new ThreadDomain47();

        Thread t1=new Thread(new MyThread40_0(task));
        Thread t3=new Thread(new MyThread40_0(task));
        Thread t7=new Thread(new MyThread40_0(task));
        Thread t8=new Thread(new MyThread40_0(task));
        Thread t2 = new Thread(new MyThread40_1(task));
        Thread t4 = new Thread(new MyThread40_1(task));
        Thread t5 = new Thread(new MyThread40_1(task));
        Thread t6 = new Thread(new MyThread40_1(task));

        t1.start();
        t2.start();
        t3.start();
        t4.start();
        t5.start();
        t6.start();
        t7.start();
        t8.start();
    }
}

輸出結果以下

The Lists Size is 1
The Current Thread is 增長線程
==============================
The Lists Size is 2
The Current Thread is 增長線程
==============================
The Token Banana is [add Banana1]
The Current Thread is 減小線程
==============================
The Token Banana is [add Banana2]
The Current Thread is 減小線程
==============================
The Lists Size is 1
The Current Thread is 增長線程
==============================
The Token Banana is [add Banana1]
The Current Thread is 減小線程
==============================
The Lists Size is 1
The Current Thread is 增長線程
==============================
The Token Banana is [add Banana1]
The Current Thread is 減小線程
==============================

能夠看到,lists的數量不會增長太多,也不會減小太多。當集合滿,使增長線程等待,喚醒減小線程;當集合空,使減小線程等待,喚醒增長線程。咱們用wait()/notify()機制沒法實現該效果,這裏體現了Condition的強大之處。

ReentrantLock中的方法

公平鎖和非公平鎖
ReentrantLock能夠指定公平鎖和非公平鎖,公平鎖根據線程運行的順序獲取鎖,非公平鎖則經過搶佔得到鎖,不按線程運行順序。synchronized是非公平鎖。在ReentrantLock(boolean fair)構造函數傳入true/false來指定公平鎖/非公平鎖。
看個例子
ThreadDomain39類和main方法

public class ThreadDomain39 {
    private Lock lock = new ReentrantLock(true);

    public void testMethod()
    {
        try
        {
            lock.lock();
            System.out.println("ThreadName" + Thread.currentThread().getName() + "得到鎖");
        }
        finally
        {
            lock.unlock();
        }
    }

    public static void main(String[] args) throws Exception
    {
        final ThreadDomain39 td = new ThreadDomain39();
        Runnable runnable = new Runnable()
        {
            public void run()
            {
                System.out.println("線程" + Thread.currentThread().getName() + "運行了");
                td.testMethod();
            }
        };
        Thread[] threads = new Thread[5];
        for (int i = 0; i < 5; i++)
            threads[i] = new Thread(runnable);
        for (int i = 0; i < 5; i++)
            threads[i].start();
    }
}

輸出結果以下

線程Thread-0運行了
ThreadNameThread-0得到鎖
線程Thread-1運行了
線程Thread-2運行了
ThreadNameThread-1得到鎖
線程Thread-3運行了
線程Thread-4運行了
ThreadNameThread-2得到鎖
ThreadNameThread-3得到鎖
ThreadNameThread-4得到鎖

能夠看到公平鎖得到鎖的順序和線程運行的順序相同。公平鎖儘量地讓線程獲取鎖的順序和線程運行順序保持一致,再執行幾回,可能不一致。
ReentrantLock構造函數傳入false,輸出結果以下:

線程Thread-0運行了
線程Thread-2運行了
線程Thread-4運行了
線程Thread-3運行了
ThreadNameThread-0得到鎖
線程Thread-1運行了
ThreadNameThread-1得到鎖
ThreadNameThread-2得到鎖
ThreadNameThread-4得到鎖
ThreadNameThread-3得到鎖

非公平鎖得到鎖的順序和線程運行的順序不一樣

getHoldCount()

獲取當前線程調用lock()的次數,通常debug使用。
看個例子

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

    public void testMethod1()
    {
        try
        {
            lock.lock();
            System.out.println("testMethod1 getHoldCount = " + lock.getHoldCount());
            testMethod2();
        }
        finally
        {
            lock.unlock();
        }
    }

    public void testMethod2()
    {
        try
        {
            lock.lock();
            System.out.println("testMethod2 getHoldCount = " + lock.getHoldCount());
        }
        finally
        {
            lock.unlock();
        }
    }



    public static void main(String[] args)
    {
        ThreadDomain40 td = new ThreadDomain40();
        td.testMethod1();
    }


}

輸出結果以下

testMethod1 getHoldCount = 1
testMethod2 getHoldCount = 2

能夠看到,testMethod1()被調用了一次,testMethod2()被調用了兩次,ReentrantLock和synchronized同樣,鎖都是可重入的。

getQueueLength()和isFair()

getQueueLength()獲取等待的線程數量,isFair()判斷是不是公平鎖。
ThreadDomain41類和main方法,Thread.sleep(2000)使第一個線程以後的線程都來不及啓動,Thread.sleep(Integer.MAX_VALUE)使線程沒法unlock()。

public class ThreadDomain41 {
    public ReentrantLock lock = new ReentrantLock();

    public void testMethod()
    {
        try
        {
            lock.lock();
            System.out.println("ThreadName = " + Thread.currentThread().getName() + "進入方法!");
            System.out.println("是否公平鎖?" + lock.isFair());
            Thread.sleep(Integer.MAX_VALUE);
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
        finally
        {
            lock.unlock();
        }
    }

    public static void main(String[] args) throws InterruptedException
    {
        final ThreadDomain41 td = new ThreadDomain41();
        Runnable runnable = new Runnable()
        {
            public void run()
            {
                td.testMethod();
            }
        };
        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("有" + td.lock.getQueueLength() + "個線程正在等待!");
    }
}

輸出結果以下

ThreadName = Thread-1進入方法!
是否公平鎖?false
有9個線程正在等待!

ReentrantLock默認是非公平鎖,只有一個線程lock(),9個線程在等待。

hasQueuedThread()和hasQueuedThreads()

hasQueuedThread(Thread thread)查詢指定線程是否在等待鎖,hasQueuedThreads()查詢是否有線程在等待鎖。
看個例子
ThreadDomain41類和main方法,和上面例子相似,Thread.sleep(Integer.MAX_VALUE); 讓線程不釋放鎖,Thread.sleep(2000);讓第一個線程以後的線程都沒法啓動。

public class ThreadDomain42 extends ReentrantLock {
    public void waitMethod()
    {
        try
        {
            lock();
            Thread.sleep(Integer.MAX_VALUE);
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
        finally
        {
            unlock();
        }
    }

    public static void main(String[] args) throws InterruptedException
    {
        final ThreadDomain42 td = new ThreadDomain42();
        Runnable runnable = new Runnable()
        {
            public void run()
            {
                td.waitMethod();
            }
        };
        Thread t0 = new Thread(runnable);
        t0.start();
        Thread.sleep(500);
        Thread t1 = new Thread(runnable);
        t1.start();
        Thread.sleep(500);
        Thread t2 = new Thread(runnable);
        t2.start();
        Thread.sleep(500);
        System.out.println("t0 is waiting?" + td.hasQueuedThread(t0));
        System.out.println("t1 is waiting?" + td.hasQueuedThread(t1));
        System.out.println("t2 is waiting?" + td.hasQueuedThread(t2));
        System.out.println("Is any thread waiting?" + td.hasQueuedThreads());
    }
}

輸出結果以下

t0 is waiting?false
t1 is waiting?true
t2 is waiting?true
Is any thread waiting?true

t0線程得到了鎖,t0沒有釋放鎖,致使t1,t2等待鎖。

isHeldByCurrentThread()和isLocked()

isHeldByCurrentThread()判斷鎖是否由當前線程持有,isLocked()判斷鎖是否由任意線程持有。
請看示例
ThreadDomain43類和main方法

public class ThreadDomain43 extends ReentrantLock {
    public void testMethod()
    {
        try
        {
            lock();
            System.out.println(Thread.currentThread().getName() + "線程持有了鎖!");
            System.out.println(Thread.currentThread().getName() + "線程是否持有鎖?" +
                    isHeldByCurrentThread());
            System.out.println("是否任意線程持有了鎖?" + isLocked());
        } finally
        {
            unlock();
        }
    }

    public void testHoldLock()
    {
        System.out.println(Thread.currentThread().getName() + "線程是否持有鎖?" +
                isHeldByCurrentThread());
        System.out.println("是否任意線程持有了鎖?" + isLocked());
    }

    public static void main(String[] args)
    {
        final ThreadDomain43 td = new ThreadDomain43();
        Runnable runnable0 = new Runnable()
        {
            public void run()
            {
                td.testMethod();
            }
        };
        Runnable runnable1 = new Runnable()
        {
            public void run()
            {
                td.testHoldLock();
            }
        };
        Thread t0 = new Thread(runnable0);
        Thread t1 = new Thread(runnable1);
        t0.start();
        t1.start();
    }
}

輸出結果以下

Thread-0線程持有了鎖!
Thread-1線程是否持有鎖?false
Thread-0線程是否持有鎖?true
是否任意線程持有了鎖?true
是否任意線程持有了鎖?true

Thread-0線程testMethod方法持有鎖,Thread-1線程testHoldLock方法沒有lock操做,因此不持有鎖。

tryLock()和tryLock(long timeout, TimeUnit unit)

tryLock()有加鎖的功能,得到了鎖且鎖沒有被另一個線程持有,此時返回true,不然返回false,能夠有效避免死鎖。tryLock(long timeout, TimeUnit unit)表示在給定的時間內得到了鎖,鎖沒有被其餘線程持有,且不處於中斷狀態。返回true,不然返回false;
看個例子

public class MyThread39 {
    public static void main(String[] args) {

        System.out.println("開始");
        final Lock lock = new ReentrantLock();
        new Thread() {
            @Override
            public void run() {
                String tName = Thread.currentThread().getName();
                if (lock.tryLock()) {
                    System.out.println(tName + "獲取到鎖!");
                } else {
                    System.out.println(tName + "獲取不到鎖!");
                    return;
                }
                try {
                    for (int i = 0; i < 5; i++) {
                        System.out.println(tName + ":" + i);
                    }
                    Thread.sleep(5000);
                } catch (Exception e) {
                    System.out.println(tName + "出錯了!");
                } finally {
                    System.out.println(tName + "釋放鎖!");
                    lock.unlock();
                }

            }
        }.start();

        new Thread() {
            @Override
            public void run() {
                String tName = Thread.currentThread().getName();

                try {
                    if (lock.tryLock(1,TimeUnit.SECONDS)) {
                        System.out.println(tName + "獲取到鎖!");
                    } else {
                        System.out.println(tName + "獲取不到鎖!");
                        return;
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                try {
                    for (int i = 0; i < 5; i++) {
                        System.out.println(tName + ":" + i);
                    }

                } catch (Exception e) {
                    System.out.println(tName + "出錯");
                } finally {
                    System.out.println(tName + "釋放鎖!");
                    lock.unlock();
                }
            }
        }.start();

        System.out.println("結束");
    }
}

輸出結果以下

開始
Thread-0獲取到鎖!
Thread-0:0
Thread-0:1
Thread-0:2
Thread-0:3
Thread-0:4
結束
Thread-1獲取不到鎖!
Thread-0釋放鎖!

Thread-0先得到了鎖,且sleep了5秒,致使Thread-1獲取不到鎖,咱們給Thread-1的tryLock設置1秒,一秒內獲取不到鎖就會返回false。
若是Thread.sleep(0),那麼Thread-0和Thread-1均可以得到鎖,園友能夠本身試下。

synchronized和ReentrantLock的比較

1.synchronized關鍵字是語法層面的實現,ReentrantLock要手動lock()和unlock(); 2.synchronized是不公平鎖,ReentrantLock能夠指定是公平鎖仍是非公平鎖; 3.synchronized等待/喚醒機制是隨機的,ReentrantLock藉助Condition的等待/喚醒機制能夠自行選擇等待/喚醒;

相關文章
相關標籤/搜索