07____多線程實現線程通信(Lock和Condition)

Local鎖:


Local鎖也是在java併發庫java.util.concurrent中,不過Lock是一個接口,需要使用時必須new出Lock的具體實現類。
那麼,Local鎖是怎樣使用呢,還是以賬戶存取款爲例子,通過和synchronized鎖的對比一看就會明白:


public class Test {
    public static int cash = 100;

    public static void main(String[] args) {
        
        Thread thread2 = new Thread(new Runnable() {

            public  void run() {
                                //synchronized(this)//此時鎖不起作用
                 synchronized(Test.class){
                     System.out.println("run查看賬戶,餘額爲" + cash);
                     cash += 1000;
                    try {
                         Thread.sleep(5000);
                     } catch (InterruptedException e) {
                         e.printStackTrace();
                    }
                    System.out.println("cash2 = " + cash);
                }
            }
        }

        );
        thread2.start();
        Thread thread1 = new Thread(new Runnable() {

            public  void run() {

                                //synchronized(this)此時鎖不起作用,必須用Test1.class才起作用
                synchronized(Test.class){
                    System.out.println("m查看賬戶,餘額爲" + cash);
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    cash = cash - 100;
                    System.out.println("cash1 = " + cash);
                }
            }
            
        });
        thread1.start();
    }
}

在學習synchronized關鍵字的時我們討論了這個小例子,當synchronized()中的參數爲this時,是不起作用的。因爲兩個
Thread中this指的不是同一個東西。只有使用Test..class的時候,鎖才起作用。
我們將synchronized關鍵字換爲Lock鎖:
因爲Lock是一個接口,所以我們使用時就要new出Lock鎖的具體實現:

Lock lock = new ReentrantLock();
lock.lock();//上鎖
lock.unlock();//解鎖

修改後:
public class Test {
    public static int cash = 100;

    public static void main(String[] args) {
        final Lock lock = new ReentrantLock();
        
        Thread thread2 = new Thread(new Runnable() {

            public  void run() {
                try{
                    lock.lock();//加鎖
                    {
                        System.out.println("run查看賬戶,餘額爲" + cash);
                        cash += 1000;
                        try {
                            Thread.sleep(5000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println("cash2 = " + cash);
                    }
                }catch (Exception e) {
                    e.printStackTrace();
                }finally{
                    //關閉鎖
                   lock.unlock();
                }
                
            }
        }

        );
        thread2.start();
        Thread thread1 = new Thread(new Runnable() {

            public  void run() {
                try{
                    lock.lock();//加鎖
                    {
                        System.out.println("m查看賬戶,餘額爲" + cash);
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        cash = cash - 100;
                        System.out.println("cash1 = " + cash);
                    }
                }catch(Exception e){
                    e.printStackTrace();
                }
                lock.unlock();//關閉鎖
            }
            
        });
        thread1.start();
    }
}
此時的輸出結果:

run查看賬戶,餘額爲100
cash2 = 1100
m查看賬戶,餘額爲1100
cash1 = 1000

結論:

程序運行正常。通過這個例子我們就能看出,Lock鎖通過new出來,加了這把鎖的所有方法不論在哪,都實現了線程阻塞。
相當於synchronized(常量),是個萬能鎖。

一個線程要加不同的鎖,那麼多new幾個Lock,相同的lock加在需要阻塞的方法上就行了。

和synchronized不同的是,在線程執行完以後,要關閉鎖unlock(),如果不關閉,其他在等待的線程就永遠被鎖在外面了。


比如new出lock和lock1,lock加在了方法A()和B()上,lock1加在了方法C()和D()上,那麼,一旦有線程進入方法A(),就不會有線程再進入方法B(),同理,一旦有線程進入方法C(),就不會有線程進入方法D();但是A()和C(),B()和D()之間並沒有形成阻塞。

Lock鎖更加面向對象,因爲lock本身就是一個對象。


Condition

Condition,Condition 將 Object 監視器方法(wait、notify 和 notifyAll)分解成截然不同的對象,以便通過將這些對象與任意 Lock 實現組合使用,爲每個對象提供多個等待 set (wait-set)。其中,Lock 替代了 synchronized 方法和語句的使用,Condition 替代了 Object 監視器方法的使用。

例子1:

啓動兩個線程,子線程運行10次,主線程運行10次,然後又子線程運行10次,主線程運行10次,如此反覆50次:

public class Test{

    public static void main(String[] args) {
        final Busniess test = new Test().new Busniess();

        new Thread(new Runnable() {

            public void run() {
                for (int i = 1; i < 51; i++) {
                    test.sub(i);
                }
            }
        }).start();

        for (int i = 1; i < 51; i++) {
            test.main(i);
        }

    }

    class Busniess {
        boolean isSub = true;
        Lock lock = new ReentrantLock();
        private Condition condition = lock.newCondition();   

        public void sub(int i) {
            lock.lock();
            try {
                while (!isSub) {
                    //this.wait();線程等待,保證子線程先運行
                    condition.await();
                }
                for (int j = 1; j < 11; j++) {
                    System.out.println("sub運行第" + i + "輪,第" + j + "次");
                }
                isSub = false;
                //this.notifyAll();線程喚醒
                 condition.signalAll();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }

        public synchronized void main(int i) {
                try {
                    lock.lock();
                    while (isSub) { 
                        //this.wait();
                        condition.await();
                        for (int j = 1; j < 11; j++) {
                            System.out.println("main運行第" + i + "輪,第" + j + "次");
                        }
                        isSub = true;
                        //this.notifyAll();
                        condition.signalAll();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }finally{
                    lock.unlock();
                }
            
        }
    }
}
打印結果:

sub運行第1輪,第1次
sub運行第1輪,第2次
sub運行第1輪,第3次
sub運行第1輪,第4次
sub運行第1輪,第5次
sub運行第1輪,第6次
sub運行第1輪,第7次
sub運行第1輪,第8次
sub運行第1輪,第9次
sub運行第1輪,第10次
main運行第1輪,第1次
main運行第1輪,第2次
main運行第1輪,第3次
main運行第1輪,第4次
main運行第1輪,第5次
main運行第1輪,第6次
main運行第1輪,第7次
main運行第1輪,第8次
main運行第1輪,第9次
main運行第1輪,第10次
......

在Condition中,用await()替換wait(),用signal()替換notify(),用signalAll()替換notifyAll(),傳統線程的通信方式,Condition都可以實現。

Condition實例實質上被綁定到一個鎖上。要爲特定Lock 實例獲得Condition 實例,請使用其newCondition() 方法。


Condition和傳統的線程通信沒什麼區別,Condition的強大之處在於它可以爲多個線程間建立不同的Condition,具體看下面的例子:

} 假定有一個綁定的緩衝區,它支持 puttake 方法。如果試圖在空的緩衝區上執行take 操作,則在某一個項變得可用之前,線程將一直阻塞;如果試圖在滿的緩衝區上執行put 操作,則在有空間變得可用之前,線程將一直阻塞。我們喜歡在單獨的等待 set 中保存put 線程和take 線程,這樣就可以在緩衝區中的項或空間變得可用時利用最佳規劃,一次只通知一個線程。可以使用兩個Condition 實例來做到這一點。

    class BoundedBuffer {  
       final Lock lock = new ReentrantLock();//鎖對象  
       final Condition notFull  = lock.newCondition();//寫線程條件   
       final Condition notEmpty = lock.newCondition();//讀線程條件   
      
       final Object[] items = new Object[100];//緩存隊列  
       int putptr/*寫索引*/, takeptr/*讀索引*/, count/*隊列中存在的數據個數*/;  
      
       public void put(Object x) throws InterruptedException {  
         lock.lock();  
         try {  
           while (count == items.length)//如果隊列滿了   
             notFull.await();//阻塞寫線程  
             items[putptr] = x;//賦值   
             if (++putptr == items.length) putptr = 0;//如果寫索引寫到隊列的最後一個位置了,那麼置爲0  
             ++count;//個數++  
             notEmpty.signal();//喚醒讀線程  
         } finally {  
             lock.unlock();  
         }  
       }  
      
       public Object take() throws InterruptedException {  
         lock.lock();  
         try {  
           while (count == 0)//如果隊列爲空  
             notEmpty.await();//阻塞讀線程  
             Object x = items[takeptr];//取值   
             if (++takeptr == items.length) takeptr = 0;//如果讀索引讀到隊列的最後一個位置了,那麼置爲0  
             --count;//個數--  
             notFull.signal();//喚醒寫線程  
             return x;  
          } finally {  
             lock.unlock();  
         }  
       }   
     }  

例子2:

以此類推,如果有三個線程,想讓線程1運行完以後運行線程2,線程2運行完以後運行線程3,線程3運行完以後又運行線程1,那麼該如何來實現呢?

public class ThreeConditionCommunication {

	public static void main(String[] args) {

		final Business business = new Business();
		new Thread(new Runnable() {

			public void run() {

				for (int i = 1; i <= 50; i++) {
					business.sub2(i);
				}

			}
		}).start();

		new Thread(new Runnable() {

			public void run() {

				for (int i = 1; i <= 50; i++) {
					business.sub3(i);
				}

			}
		}).start();

		for (int i = 1; i <= 50; i++) {
			business.main(i);
		}

	}

	static class Business {
		Lock lock = new ReentrantLock();
		Condition condition1 = lock.newCondition();
		Condition condition2 = lock.newCondition();
		Condition condition3 = lock.newCondition();
		private int shouldSub = 1;

		public void sub2(int i) {
			lock.lock();
			try {
				while (shouldSub != 2) {
					try {
						condition2.await();
					} catch (Exception e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
				for (int j = 1; j <= 10; j++) {
					System.out.println("sub2 thread sequence of " + j
							+ ",loop of " + i);
				}
				shouldSub = 3;
				condition3.signal();
			} finally {
				lock.unlock();
			}
		}

		public void sub3(int i) {
			lock.lock();
			try {
				while (shouldSub != 3) {
					try {
						condition3.await();
					} catch (Exception e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
				for (int j = 1; j <= 20; j++) {
					System.out.println("sub3 thread sequence of " + j
							+ ",loop of " + i);
				}
				shouldSub = 1;
				condition1.signal();
			} finally {
				lock.unlock();
			}
		}

		public void main(int i) {
			lock.lock();
			try {
				while (shouldSub != 1) {
					try {
						condition1.await();
					} catch (Exception e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
				for (int j = 1; j <= 10; j++) {
					System.out.println("main thread sequence of " + j
							+ ",loop of " + i);
				}
				shouldSub = 2;
				condition2.signal();
			} finally {
				lock.unlock();
			}
		}
	}
}

也就是說:Condition可以控制多個線程之間的運行順序。


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