java多線程之Lock類的使用

原文網址 http://blog.csdn.net/ya_1249463314/article/details/52671169


1.ReentrantLock類的使用

   1.1ReentrantLock實現線程間同步

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

public class MyThread extends Thread{
    private MyService myService;
    public MyThread(MyService myService) {
        super();
        this.myService = myService;
    }
    @Override
    public void run() {
        myService.service();
    }    
}

public class Test {
    public static void main(String[] args) {
        MyService myService=new MyService();
        MyThread mt1=new MyThread(myService);
        MyThread mt2=new MyThread(myService);
        MyThread mt3=new MyThread(myService);
        MyThread mt4=new MyThread(myService);
        MyThread mt5=new MyThread(myService);
        mt1.start();
        mt2.start();
        mt3.start();
        mt4.start();
        mt5.start();
    }
}

結果:

ThreadName=Thread-1 1
ThreadName=Thread-1 2
ThreadName=Thread-1 3
ThreadName=Thread-1 4
ThreadName=Thread-1 5
ThreadName=Thread-0 1
ThreadName=Thread-0 2
ThreadName=Thread-0 3
ThreadName=Thread-0 4
ThreadName=Thread-0 5
ThreadName=Thread-2 1
ThreadName=Thread-2 2
ThreadName=Thread-2 3
ThreadName=Thread-2 4
ThreadName=Thread-2 5
ThreadName=Thread-3 1
ThreadName=Thread-3 2
ThreadName=Thread-3 3
ThreadName=Thread-3 4
ThreadName=Thread-3 5
ThreadName=Thread-4 1
ThreadName=Thread-4 2
ThreadName=Thread-4 3
ThreadName=Thread-4 4
ThreadName=Thread-4 5
解釋:線程間是競爭的關係,所以線程間執行的順序是不同的。

   1.2通過Condition實現等待通知機制

     Condition類可以實現多路通知功能,就是Lock對象可以創建多個Condition,將線程對象註冊在指定的Condition中,就可以實現

     有通知性地進行線程通知,在調度線程上更加靈活了。


public class MyService {
	private Lock lock=new ReentrantLock();
	public Condition condition=lock.newCondition();
	public void awaitService(){
		try{
			lock.lock();
			System.out.println("await時間:   "+System.currentTimeMillis());
			condition.await();
		}catch(InterruptedException e){
			e.printStackTrace();
		}finally{
			lock.unlock();
		}
	}
	public void signalService(){
		try{
			lock.lock();
			System.out.println("signal時間:"+System.currentTimeMillis());
			condition.signal();
		}finally{
			lock.unlock();
		}
	}
}

public class ThreadA extends Thread{
    private MyService myService;
    public ThreadA(MyService myService) {
        super();
        this.myService = myService;
    }
    @Override
    public void run() {
        myService.awaitService();
    }
}

public class Test {
    public static void main(String[] args) throws InterruptedException {
        MyService myService=new MyService();
        ThreadA a=new ThreadA(myService);
        a.start();
        Thread.sleep(3000);
        myService.signalService();
    }
} 

結果:

await時間 :  1474879823968
signal時間:  1474879826978

   1.3實現一生產一消費

public class MyService {
	private Lock lock=new ReentrantLock();
	private Condition condition=lock.newCondition();
	private boolean hasValue=true;
	public void set(){
		try{
			lock.lock();
			while(hasValue){
				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){
				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) {
        super();
        this.myService = myService;
    }
    @Override
    public void run() {
        for(int i=0;i<3;i++){
            myService.set();
        }
    }
}

public class ThreadB extends Thread{
    private MyService myService;
    public ThreadB(MyService myService) {
        super();
        this.myService = myService;
    }
    @Override
    public void run() {
        for(int i=0;i<3;i++){
            myService.get();
        }
    }
}

public class Test {
    public static void main(String[] args) throws InterruptedException {
        MyService myService=new MyService();
        ThreadA a=new ThreadA(myService);
        a.start();
        ThreadB b=new ThreadB(myService);
        b.start();
    }
}

結果:

打印&
打印#
打印&
打印#
打印&
打印#

  1.4.實現多生產多消費

     程序照上面一生產一消費沒有區別,只是Test測試時,裏面new了多個生產者和多個消費者。上篇線程通信中瞭解到當出現多個生產者和多個消費者會出現所謂的線程假死現象,最終導致項目停掉。在我看來其原因是生產者和消費者共用了同一個condition,

以至於將消費者的鎖和生產者的鎖用了同一把鎖,以至於有時候喚醒的是同類,就是生產者喚醒生產者,而沒有喚醒消費者,以至於生產者沒有對應的消費者,產生線程的假死。解決此辦法就是將signal();喚醒方法換成signalAll();方法。將所有的線程都喚醒,這樣每時每刻都有生產者和消費者相互對應了。

   1.5公平鎖與非公平鎖

     公平鎖:從字面來看 "公平"兩個字體現,線程獲取鎖的順序是按照線程加鎖的順序來分配的,也就是隊列中常說的先進先出的順序。

     非公平鎖:就是以一種搶佔的方式來獲取鎖,是隨機獲得鎖,這樣靠搶佔的方式肯定會出現有的線程會獲不到鎖,所以說叫非公平的。

      1.5.1實現:

      先看下ReentrantLock爲我們提供的API,是通過構造傳參的方式來實現的。


public class Service {
	private ReentrantLock lock;

	public Service(boolean isFair) {
		lock=new ReentrantLock(isFair);
	}
	public void toService(){
		try{
			lock.lock();
			System.out.println("ThreadName="+Thread.currentThread().getName()+"獲得鎖定!");
		}finally{
			lock.unlock();
		}
	}
}

public class Test {
    public static void main(String[] args) {
        final Service service=new Service(true);
        Runnable runnable=new Runnable(){
            @Override
            public void run() {
                // TODO Auto-generated method stub
                System.out.println("線程"+Thread.currentThread().getName()+"運行了!");
                service.toService();
            }    
        };
        Thread[] threadArr=new Thread[10];
        for(int i=0;i<10;i++){
            threadArr[i]=new Thread(runnable);
        }
        for(int i=0;i<10;i++){
            threadArr[i].start();
        }
    }
}

結果:

線程Thread-0運行了!
ThreadName=Thread-0獲得鎖定!
線程Thread-8運行了!
線程Thread-2運行了!
ThreadName=Thread-8獲得鎖定!
ThreadName=Thread-2獲得鎖定!
線程Thread-1運行了!
線程Thread-3運行了!
線程Thread-7運行了!
線程Thread-4運行了!
線程Thread-5運行了!
ThreadName=Thread-1獲得鎖定!
ThreadName=Thread-3獲得鎖定!
線程Thread-9運行了!
線程Thread-6運行了!
ThreadName=Thread-7獲得鎖定!
ThreadName=Thread-4獲得鎖定!
ThreadName=Thread-5獲得鎖定!
ThreadName=Thread-9獲得鎖定!
ThreadName=Thread-6獲得鎖定!

解釋:查看結果,發現也不是完全呈有序狀態,所以說公平鎖 "基本" 呈有序狀態。也就是說基本公平吧。

對於非公平鎖,只要在Test中寫Service service=new Service(false);測試發現基本上是亂序的,也就是說基本不公平吧。

    1.6常用方法介紹:

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

public class Service {
	private ReentrantLock lock=new ReentrantLock();
	public void toService1(){
		try{
			lock.lock();
			System.out.println("toService1 getHoldCount="+lock.getHoldCount());
			toService2();
		}finally{
			lock.unlock();
		}
	}
	
	public void toService2(){
		try{
			lock.lock();
			System.out.println("toService2 getHoldCount="+lock.getHoldCount());
		}finally{
			lock.unlock();
		}
	}
}

public class Test {
    public static void main(String[] args) {
        Service service=new Service();
        service.toService1();
    }
} 

結果:toService1 getHoldCount=1

toService2 getHoldCount=2

      <2>方法int getQueueLength();的作用是返回正等待獲取此鎖定的線程估計數。通過英文名叫獲得隊列的長度,我在上一篇多線程通信的博文中提到了那個阻塞隊列,如果當前線程等待了,就是被wait了,那麼就會進入阻塞隊列,而這個方法正是獲取這個阻塞隊列的長度,獲取隊列中有多少個正在等待的線程。

      <3>方法int getWaitQueueLength(Condition condition);作用就是返回等待與此鎖定相關的給定條件Condition的線程估計數。假如我有3個線程,都執行了同一個Condition對象的await();方法,而調用此方法就會返回3。

      <4>方法boolean hasQueuedThread();方法作用是查詢是否有線程正在等待獲取此鎖定。

      <5>方法boolean hasQueuedThread(Thread thread);方法的作用是查詢指定線程是否正在等待獲取此鎖定。

      <6>方法boolean hasWaiters(Condition condition);的作用是查詢是否有線程正在等待與此鎖定有關的condition條件。

      <7>方法boolean isFair();的作用是判斷是不是公平鎖。

      <8>方法boolean isHeldByCurrentThread()的作用是查詢當前線程是否保持鎖定,就是當前線程是否在new Lock().lock與

new Lock().unlock();之間。

      <9>方法boolean isLock();作用是查詢此鎖定是否由任意線程保持。

      <10>方法 void lockInterruptibly();的作用是如果當前線程未被中斷,則獲取鎖定,如果已經被中斷則出現異常。

      <11>方法 boolean tryLock();作用僅在調用時鎖定未被另一個線程保持的情況下,才獲取該鎖定。

public class Service {
	private ReentrantLock lock=new ReentrantLock();
	public void waitMethod(){
		if(lock.tryLock()){
			System.out.println(Thread.currentThread().getName()+"獲得鎖!");
		}else{
			System.out.println(Thread.currentThread().getName()+"未獲得鎖!");
		}
	}
}

public class Test {
    public static void main(String[] args) {
        final Service service=new Service();
        Runnable runnable=new Runnable(){
            @Override
            public void run() {
                // TODO Auto-generated method stub
                service.waitMethod();
            }    
        };
        Thread threadA=new Thread(runnable);
        threadA.setName("A");
        threadA.start();
        Thread threadB=new Thread(runnable);
        threadB.setName("B");
        threadB.start();
    }
}

結果:

A獲得鎖!
B未獲得鎖!

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

      <13>awaitUtil();方法:作用是造成當前線程在接到信號之前,被中斷、或到達指定最後期限之前一直處於等待狀態。

   1.7Condition的執行順序

     原來我們都是Lock對象可以創建1個Condition,將不同線程對象註冊在指定的Condition中,這樣其實對於線程的規劃很不靈活。我們可以通過Lock對象創建多個Condition,然後再去對線程規劃,如改變執行順序就很方便。

public class Test {
	//由於這個標誌位作爲線程之間是否等待的條件,所以線程間應當是可見的,才用volatile關鍵字修飾。
	private volatile static int nextThread=1;
	private static ReentrantLock lock=new ReentrantLock();
	final private static Condition conditionA=lock.newCondition();
	final private static Condition conditionB=lock.newCondition();
	final private static Condition conditionC=lock.newCondition();
	public static void main(String[] args) {
		Thread threadA=new Thread(){
			@Override
			public void run() {
				try{
					lock.lock();
					while(nextThread !=1){
						conditionA.await();
					}
					for(int i=0;i<3;i++){
						System.out.println("ThreadA"+(i+1));
					}
					nextThread=2;
					conditionB.signalAll();
				}catch(InterruptedException e){
					e.printStackTrace();
				}finally{
					lock.unlock();
				}
			}	
		};
		
		Thread threadB=new Thread(){
			@Override
			public void run() {
				try{
					lock.lock();
					while(nextThread !=2){
						conditionB.await();
					}
					for(int i=0;i<3;i++){
						System.out.println("ThreadB"+(i+1));
					}
					nextThread=3;
					conditionC.signalAll();
				}catch(InterruptedException e){
					e.printStackTrace();
				}finally{
					lock.unlock();
				}
			}	
		};
		
		Thread threadC=new Thread(){
			@Override
			public void run() {
				try{
					lock.lock();
					while(nextThread !=3){
						conditionC.await();
					}
					for(int i=0;i<3;i++){
						System.out.println("ThreadC"+(i+1));
					}
					nextThread=1;
					conditionA.signalAll();
				}catch(InterruptedException e){
					e.printStackTrace();
				}finally{
					lock.unlock();
				}
			}
		};
		Thread[] aArr=new Thread[2];
		Thread[] bArr=new Thread[2];
		Thread[] cArr=new Thread[2];
		for(int i=0;i<2;i++){
			aArr[i]=new Thread(threadA);
			bArr[i]=new Thread(threadB);
			cArr[i]=new Thread(threadC);
			aArr[i].start();
			bArr[i].start();
			cArr[i].start();
		}
	}
}

結果:

ThreadA1
ThreadA2
ThreadA3
ThreadB1
ThreadB2
ThreadB3
ThreadC1
ThreadC2
ThreadC3
ThreadA1
ThreadA2
ThreadA3
ThreadB1
ThreadB2
ThreadB3
ThreadC1
ThreadC2
ThreadC3

2.ReentrantReadWriteLock類的使用

ReentrantLock實現了互斥排他的效果了。意思就是同一時間只有一個線程在執行ReentrantLock.lock();方法。效率不高。JDK因此提供了一個ReentrantReadWriteLock類來提升效率。

搞讀寫鎖就關鍵這幾句話:讀操作的鎖叫共享鎖,寫操作的鎖叫排他鎖。就是遇見寫鎖就需互斥。那麼以此可得出讀讀共享,寫寫互斥,讀寫互斥,寫讀互斥。

   2.1讀讀共享

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

public class ThreadA extends Thread{
    private Service service;
    public ThreadA(Service service) {
        super();
        this.service = service;
    }
    @Override
    public void run() {
        service.read();
    }
}

public class ThreadB extends Thread{
    private Service service;
    public ThreadB(Service service) {
        super();
        this.service = service;
    }
    @Override
    public void run() {
        service.read();
    }
}


public class Test {
    public static void main(String[] args) {
        Service service=new Service();
        ThreadA a=new ThreadA(service);
        a.setName("A");
        ThreadB b=new ThreadB(service);
        b.setName("B");
        a.start();
        b.start();
    }
}


結果:

獲取讀鎖A, 1474902942515
獲取讀鎖B, 1474902942515

解釋:由程序可以看出線程進行讀操作,是同時進入lock();的,不存在互斥,就更沒什麼同步了。這樣減少了等待的時間,提高的運行的效率。所以說是讀讀共享。

   2.2寫寫互斥

     service中提供的是寫方法,然後線程調用寫方法,其他的都和上面是一樣測試。


public void write(){
		try{
			try{
				lock.writeLock().lock();
				System.out.println("獲取寫鎖"+Thread.currentThread().getName()+", "+System.currentTimeMillis());
				Thread.sleep(10000);
			}finally{
				lock.writeLock().unlock();
			}
		}catch(InterruptedException e){
			e.printStackTrace();
		}
	}

結果:

獲取寫鎖A, 1474903347768
獲取寫鎖B, 1474903357769

解釋:通過程序結果可以看出來,線程A獲取寫鎖,然後執行代碼睡眠10秒後,才釋放鎖。然後線程B才獲取鎖,然後睡眠10秒,釋放鎖。意思就是寫鎖同一時間只允許一個線程執行lock();方法後面的代碼。體現了寫鎖是互斥的。

   2.3寫讀互斥


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

public class ThreadA extends Thread{
    private Service service;
    public ThreadA(Service service) {
        super();
        this.service = service;
    }
    @Override
    public void run() {
        service.write();
    }
}

public class ThreadB extends Thread{
    private Service service;
    public ThreadB(Service service) {
        super();
        this.service = service;
    }
    @Override
    public void run() {
        service.read();
    }
}

public class Test {
    public static void main(String[] args) {
        Service service=new Service();
        ThreadA a=new ThreadA(service);
        a.setName("A");
        ThreadB b=new ThreadB(service);
        b.setName("B");
        a.start();
        b.start();
    }
} 

結果:

獲取寫鎖A, 1474903606138
獲取讀鎖B, 1474903616145

解釋:線程A先獲得寫鎖,然後睡眠10秒,釋放鎖。然後線程B纔去獲得讀鎖,然後睡眠10秒,釋放鎖。可以看出來寫鎖的時候,讀鎖是不可獲取鎖的。所以說讀寫是互斥的。也證明了之前總結的,只要見到寫鎖就是互斥的。

   2.4讀寫互斥

     其他的都不變,就是先寫再讀,程序還是上面的。只是Test中先寫線程,然後再讀線程。


public class Test {
    public static void main(String[] args) throws InterruptedException {
        Service service=new Service();
        ThreadB b=new ThreadB(service);
        b.setName("B");
        b.start();
        Thread.sleep(1000);
        ThreadA a=new ThreadA(service);
        a.setName("A");
        a.start();
    }
}

結果:

獲取讀鎖B, 1474904025583
獲取寫鎖A, 1474904035586

解釋:從程序結果也看出來了讀寫也是互斥的。



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章