1. 程式人生 > >多線程編程學習四(Lock 的使用).

多線程編程學習四(Lock 的使用).

stat 情況 closed 查詢 ++ 線程鎖 notifyall 估計 bsp

一、前言

本文要介紹使用Java5中 Lock 對象,同樣也能實現同步的效果,而且在使用上更加方便、靈活,主要包括 ReentrantLock 類的使用和ReentrantReadWriteLock 類的使用。

二、使用ReentrantLock 類

1、在java多線程中,可以使用synchronized關鍵字來實現線程之間同步互斥,但在JDK1.5中新增加的ReentrantLock也能達到同樣的效果,並且在擴展功能上也更加強大,比如具有嗅探鎖定、多路分支通知等功能,而且在使用上也比synchronized更加的靈活。

2、調用lock.lock()代碼的線程就持有了“對象監視器”,即lock 持有的是對象鎖,依賴於該類的實例存在。

技術分享
public class MyService {
    private Lock lock=new ReentrantLock();
    public void testMethod(){
        lock.lock();
        for(int i=0;i<5;i++){
            System.out.println(Thread.currentThread().getName()+(i+1));
        }
        lock.unlock();
    }
}
View Code

3、關鍵字synchronized 與wait() 和 notify()/notifyAll() 方法相結合可以實現等待/通知模式,類ReentrantLock 也可以實現同樣的功能,但需要借助於Condition對象。

Object類中的wait()方法相當於Condition類中的await()方法 Object類中的wait(long timeout)方法相當於Condition類中的await(long time,TimeUnit unit)方法 Object類中的notify()方法相當於Condition類中的signal()方法 Object類中的notifyAll()方法相當於Condition類中的signalAll()方法 技術分享
public class Myservice {
    private Lock lock=new ReentrantLock();
    
private Condition condition=lock.newCondition(); //等待 public void waitMethod(){ try { lock.lock(); System.out.println("A"); condition.await();//調用的Condition的await等待方法也需要在同步方法中,否則會報錯 System.out.println("B"); } catch (InterruptedException e) { e.printStackTrace(); }finally { lock.unlock(); } } //喚醒 public void signal(){ try { lock.lock(); System.out.println("現在開始喚醒..."); condition.signal(); }finally { lock.unlock(); } } }
View Code

4、使用多個Condition對象 實現線程之間的選擇性通知。

技術分享
public class MyService {
    private Lock lock=new ReentrantLock();
    //通過定義多個Condition實現選擇性通知,可以喚醒指定種類的線程,這是
    //控制部分線程行為的方便形式
    private Condition conditionA=lock.newCondition();
    private Condition conditionB=lock.newCondition();

    public void awaitA(){
        try {
            lock.lock();
            System.out.println("awaitA begin");
            conditionA.await();
            System.out.println("awaitA end");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

    public void awaitB(){
        try {
            lock.lock();
            System.out.println("awaitB begin");
            conditionB.await();
            System.out.println("awaitB end");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

    public void signalA(){
        try {
            lock.lock();
            System.out.println("現在開始喚醒awaitA");
            conditionA.signalAll();
        }finally {
            lock.unlock();
        }
    }

    public void signalB(){
        try {
            lock.lock();
            System.out.println("現在開始喚醒awaitB");
            conditionB.signalAll();
        }finally {
            lock.unlock();
        }
    }
}
View Code 技術分享
public class Run
{
    public static void main(String[] args) throws InterruptedException
    {
        MyService myService=new MyService();
        Thread threadA=new Thread(){
            @Override
            public void run()
            {
                super.run();
                myService.awaitA();
            }
        };

        Thread threadB=new Thread(){
            @Override
            public void run()
            {
                super.run();
                myService.awaitB();
            }
        };

        threadA.start();
        threadB.start();
        Thread.sleep(1000);
        myService.signalA();
        Thread.sleep(1000);
        myService.signalB();

    }
}
View Code

5、公平鎖和非公平鎖

公平鎖:表示線程獲得鎖的順序是按照線程加鎖的順序來分配的,即先來先得的FIFO先進先出順序。 非公平鎖:一種獲得鎖的搶占機制,是隨機獲取鎖的,和公平鎖不一樣的就是先來的不一定先得到鎖,這種方式可能造成某些線程一直拿不到鎖,結果也就是不公平的了。 技術分享
public class Service {
    private Lock lock;

    public Service(boolean isFair)
    {
        //通過這種方式創建公平鎖(true)和非公平鎖(false)
        lock=new ReentrantLock(isFair);
    }

    public void methodA(){
        try {
            lock.lock();
            System.out.println(Thread.currentThread().getName()+"正在運行");
        }finally {
            lock.unlock();
        }
    }
}
View Code 技術分享
public class Run {
    public static void main(String[] args)
    {
        final Service service=new Service(true);
        Runnable runnable=new Runnable() {
            @Override
            public void run()
            {
             service.methodA();
            }
        };

        Thread[] threads=new Thread[10];
        for (int i=0;i<10;i++){
            threads[i]=new Thread(runnable);
            threads[i].setName("線程"+(i+1));
            threads[i].start();
        }
    }
}
View Code

技術分享

6、ReentrantLock 常用方法介紹

(1) int getHoldCount() 查詢當前線程保持此鎖定的個數,也就是線程中調用lock方法的次數。

(2) int getQueueLength() 返回正等待此鎖定的線程估計數,比如有5個線程,1個線程正占用了這個Lock鎖在執行,則調用此方法返回的就是4。該值僅是估計的數字,因為在此方法遍歷內部數據結構的同時,線程的數目可能動態地變化。此方法用於監視系統狀態,不用於同步控制。

(3) int getWaitQueueLength(Condition condition) 返回等待與此鎖定相關的給定條件Condition的線程估計數,比如有五個線程,每個線程都執行了同一個condition對象的await()方法,則調用此方法返回的值就是5。

技術分享
public class Service {
    private ReentrantLock lock=new ReentrantLock();
    private Condition condition=lock.newCondition();

    public void methodA(){
        try {
            lock.lock();
            System.out.println("A getHoldCount 調用lock的次數=>"+lock.getHoldCount());
            Thread.sleep(2000);
            System.out.println("A getQueueLength 正在等待的線程數=>"+lock.getQueueLength());
            condition.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    //測試getWaitQueueLength方法
    public Integer methodC(){
        try {
            lock.lock();
            return lock.getWaitQueueLength(condition);
        }finally {
            lock.unlock();
        }

    }
}
View Code 技術分享
public class Run{

    public static void main(String[] args) throws InterruptedException {
        Service service=new Service();
        Runnable runnable=new Runnable()
        {
            @Override
            public void run()
            {
               service.methodA();
            }
        };

        Thread[] threads=new Thread[5];
        for (int i=0;i<5;i++){
            threads[i]=new Thread(runnable);
            threads[i].start();
        }

        Thread.sleep(1000);
        System.out.println("執行了同一個Condition對象的的await()的線程有:"+service.methodC());
    }
}
View Code

(4) boolean hasQueuedThread(Thread thread) 查詢指定的線程是否正在等待獲取此鎖定。

(5) boolean hasQueuedThreads() 查詢是否有線程正在等待獲取此鎖定。

(6) boolean hasWaiters(Condition condition) 查詢是否有線程正在等待與此鎖定有關的condition條件

(7) boolean isFair() 判斷是不是公平鎖。

(8) boolean isHelpByCurrentThread() 查詢當前線程是否保持此鎖定。

(9) boolean isLocked() 查詢此鎖定是否由任意線程保持。

(10) void lockInterruptibly() 如果當前線程未被中斷,則獲取鎖定,如果已經被中斷,則出現異常。

(11) boolean tryLock() 僅在調用時鎖定未被另一個線程鎖定的情況下,才獲得此鎖定。

(12) boolean tryLock(long timeout,TimeUnit unit) 如果鎖定在給定等待時間內沒有被另一個線程保持,且當前線程未被中斷,則獲取該鎖定。

技術分享
public class Service
{
    private ReentrantLock lock=new ReentrantLock();
    private Condition condition=lock.newCondition();

    //測試lockInterruptibly
    public void methodA(){
        try {
            lock.lockInterruptibly();
            System.out.println("methodA=》"+Thread.currentThread().getName());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            if (lock.isHeldByCurrentThread()){//如果當前線程依舊保持對此鎖的鎖定,則釋放
                lock.unlock();
            }
        }
    }
    //測試tryLock
    public void methodB(){
          if (lock.tryLock()){
              System.out.println(Thread.currentThread().getName()+"獲得鎖");
          }else{
              System.out.println(Thread.currentThread().getName()+"未獲得鎖");
          }
    }
}
View Code 技術分享
public class Run
{
    public static void main(String[] args) throws InterruptedException
    {
        Service service=new Service();
        Runnable runnable=new Runnable()
        {
            @Override
            public void run()
            {
                service.methodA();
                service.methodB();
            }
        };

        Thread threadA=new Thread(runnable);
        threadA.setName("A");
        threadA.start();

        Thread.sleep(1000);
        Thread threadB=new Thread(runnable);
        threadB.setName("B");
        threadB.start();
        threadB.interrupt();
    }
}
View Code

(13) lock.awaitUninterruptibly():這個線程將不會被中斷,一直睡眠直到其他線程調用signal()或signalAll()方法。

(14) lock.awaitUntil(Date date):這個線程將會一直睡眠直到:

  • 它被中斷
  • 其他線程在這個condition上調用singal()或signalAll()方法
  • 指定的日期已經到了

三、使用ReentrantReadWriteLock 類

類RenntrantLock具有完全互斥排他的效果,即同一時間只有一個線程在執行RenntrantLock.lock()方法後面的任務。這樣做雖然保證了實例變量的線程安全性,但效率卻是非常低下的,因為即使有時候鎖內沒有寫入內容,而也要等鎖釋放後,才能進行讀取。所以JDK提供了一種讀寫鎖ReentrantReadWriteLock 類,使用它可以加快運行效率。 ReentrantReadWriteLock有兩個鎖,一個是讀操作相關的鎖,也稱為共享鎖;另一個是寫操作相關的鎖,也叫排他鎖。也就是 多個讀鎖之間不互斥、讀鎖與寫鎖互斥、寫鎖與寫鎖互斥。在沒有線程Thread進行寫入操作時,進行讀取操作的多個Thread 都可以獲取讀鎖。而進行寫入操作的Thread 只有在獲取寫鎖後才能進行寫入操作。即多個Thread可以同時進行讀取操作,但是同一個時刻只允許一個Thread 進行寫入操作。 讀讀不互斥: 技術分享
public class Read
{
    private ReentrantReadWriteLock lock=new ReentrantReadWriteLock();

    public void read(){
        try {
            lock.readLock().lock();
            System.out.println(Thread.currentThread().getName()+"正在讀"+System.currentTimeMillis());
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.readLock().unlock();
        }
    }
}
View Code 技術分享
public class Run
{
    public static void main(String[] args)
    {
        Read read=new Read();
        Runnable runnable=new Runnable()
        {
            @Override
            public void run()
            {
                read.read();
            }
        };
        Thread[] threads=new Thread[10];
        for (int i=0;i<10;i++){
            threads[i]=new Thread(runnable);
            threads[i].start();
            //通過結果可以看到所有線程幾乎同時進入lock()方法
            //後面的代碼,讀讀不互斥,可以提高程序運行效率,允許
            //多個線程同時執行lock()方法後面的代碼
        }
    }
}
View Code 寫寫互斥: 技術分享
public class Write
{
    private ReentrantReadWriteLock lock=new ReentrantReadWriteLock();

    public void write(){
        try {
            lock.writeLock().lock();
            System.out.println(Thread.currentThread().getName()+"正在寫"+System.currentTimeMillis());
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.writeLock().unlock();
        }
    }
}
View Code 技術分享
public class Run
{
    public static void main(String[] args)
    {
        Write write=new Write();
        Runnable runnable=new Runnable()
        {
            @Override
            public void run()
            {
                write.write();
            }
        };
        Thread[] threads=new Thread[10];
        for (int i=0;i<10;i++){
            threads[i]=new Thread(runnable);
            threads[i].start();
            //通過結果可以看到所有線程每隔兩秒運行一次,寫寫互斥,線程之間是同步運行的
        }
    }
}
View Code

另外,寫讀、讀寫都是互斥的,就不舉例了。總之,只要出現"寫"操作,就是互斥的!

多線程編程學習四(Lock 的使用).