Java中多個線程交替循環執行


有些時候面試官經常會問,兩個線程怎么交替執行呀,如果是三個線程,又怎么交替執行呀,這種問題一般人還真不一定能回答上來。多線程這塊如果理解的不好,學起來是很吃力的,更別說面試了。下面我們就來剖析一下怎么實現多個線程順序輸出。

兩個線程循環交替打印

//首先我們來看一種比較簡單的方式
public class ThreadCq {
	
	public static void main(String[] args) {
		 Stack<Integer> stack = new Stack<>();
		 for(int i=1;i<100;i++) {
			 stack.add(i);
		 }
		 Draw draw = new Draw(stack);
		 new Thread(draw).start();
		 new Thread(draw).start();
	}
}

class Draw implements Runnable{
	
	private Stack<Integer> stack;
	public Draw(Stack<Integer> stack) {
		this.stack = stack;
	}
	
	@Override
	public void run() {
		while(!stack.isEmpty()) {
			synchronized (this) {
				notify();
				System.out.println(Thread.currentThread().getName()+"---"+stack.pop());
				try {
					wait();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}
	}
}

這種方式是用Condition對象來完成的:

public class ThreadCq3 {

	//聲明一個鎖
	static ReentrantLock lock = new ReentrantLock();

	public static void main(String[] args) {
		
		//創建兩個Condition對象
		Condition c1 = lock.newCondition();
		Condition c2 = lock.newCondition();
		Stack<Integer> stack = new Stack<>();
		for (int i = 0; i <= 100; i++) {
			stack.add(i);
		}

		new Thread(() -> {
			try {
				Thread.sleep(500);
			} catch (InterruptedException e1) {
				e1.printStackTrace();
			}
			while (true) {
				lock.lock();
				// 打印偶數
				try {
					if (stack.peek() % 2 != 0) {
						c1.await();
					}
					System.out.println(Thread.currentThread().getName() + "-----" + stack.pop());
					c2.signal();
				} catch (InterruptedException e) {
					e.printStackTrace();
				} finally {
					lock.unlock();
				}
			}
		}).start();

		
		new Thread(() -> {
			while (true) {
				try {
					Thread.sleep(500);
				} catch (InterruptedException e1) {
					e1.printStackTrace();
				}
				lock.lock();
				try {
					// 打印奇數
					if (stack.peek() % 2 != 1) {
						c2.await();
					}
					System.out.println(Thread.currentThread().getName() + "-----" + stack.pop());
					c1.signal();
				} catch (InterruptedException e) {
					e.printStackTrace();
				} finally {
					lock.unlock();
				}
			}
		}).start();
	}
}

這種方式是通過Semaphore來實現的:

public class ThreadCq4 {

	//利用信號量來限制
	private static Semaphore s1 = new Semaphore(1);
	private static Semaphore s2 = new Semaphore(1);

	public static void main(String[] args) {
		
		try {
			//首先調用s2為 acquire狀態
			s1.acquire();
//			s2.acquire();  調用s1或者s2先占有一個
		} catch (InterruptedException e1) {
			e1.printStackTrace();
		}
		
		new Thread(()->{
			while(true) {
				try {
					s1.acquire();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				try {
					Thread.sleep(500);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println("A");
				s2.release();
			}
		}).start();
		
		new Thread(()->{
			while(true) {
				try {
					s2.acquire();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				try {
					Thread.sleep(500);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println("B");
				s1.release();
			}
		}).start();
	}
}

上面就是三種比較常用的,最常用的要屬第一種和第二種。

三個線程交替打印輸出

上面我們看了兩個線程依次輸出的實例,這里我們來看看三個線程如何做呢。

public class LockCond {

	private static int count = 0;
	private static Lock lock = new ReentrantLock();
	
	
	public static void main(String[] args) {
		Condition c1 = lock.newCondition();
		Condition c2 = lock.newCondition();
		Condition c3 = lock.newCondition();
		
		new Thread(()->{
			while(true) {
				lock.lock();
				try {
					while(count %3 != 0) {
						//剛開始count為0  0%3=0 所以此線程執行  執行完之后 喚醒現成2,由於此時count已經進行了++,所有while成立,c1進入等待狀態,其他兩個也一樣
						c1.await();
					}
					System.out.println(Thread.currentThread().getName()+"========:A");
					count++;
					//喚醒線程2
					c2.signal(); 
				} catch (InterruptedException e) {
					e.printStackTrace();
				} finally {
					lock.unlock();
				}
			}
		}) .start();
		
		new Thread(()->{
			while(true) {
				lock.lock();
				try {
					while(count %3 != 1) {
						c2.await();
					}
					System.out.println(Thread.currentThread().getName()+"========:B");
					count++;
					//喚醒線程3
					c3.signal();
				} catch (InterruptedException e) {
					e.printStackTrace();
				} finally {
					lock.unlock();
				}
			}
		}) .start();
		
		new Thread(()->{
			while(true) {
				lock.lock();
				try {
					while(count %3 != 2) {
						c3.await();
					}
					System.out.println(Thread.currentThread().getName()+"========:C");
					count++;
					//喚醒線程1
					c1.signal();
				} catch (InterruptedException e) {
					e.printStackTrace();
				} finally {
					lock.unlock();
				}
			}
		}) .start();
	}
	
}

三個線程的也可以寫三種,這里寫一種就行了,寫法和上面兩個線程的都一樣。大家可以自己試一下。

Condition介紹

我們在沒有學習Lock之前,使用的最多的同步方式應該是synchronized關鍵字來實現同步方式了。配合Object的wait()、notify()系列方法可以實現等待/通知模式。Condition接口也提供了類似Object的監視器方法,與Lock配合可以實現等待/通知模式,但是這兩者在使用方式以及功能特性上還是有差別的。Object和Condition接口的一些對比。摘自《Java並發編程的藝術》

Condition接口常用方法

        condition可以通俗的理解為條件隊列。當一個線程在調用了await方法以后,直到線程等待的某個條件為真的時候才會被喚醒。這種方式為線程提供了更加簡單的等待/通知模式。Condition必須要配合鎖一起使用,因為對共享狀態變量的訪問發生在多線程環境下。一個Condition的實例必須與一個Lock綁定,因此Condition一般都是作為Lock的內部實現。

  1. await() :造成當前線程在接到信號或被中斷之前一直處於等待狀態。

  2. await(long time, TimeUnit unit) :造成當前線程在接到信號、被中斷或到達指定等待時間之前一直處於等待狀態。

  3. awaitNanos(long nanosTimeout) :造成當前線程在接到信號、被中斷或到達指定等待時間之前一直處於等待狀態。返回值表示剩余時間,如果在nanosTimesout之前喚醒,那么返回值 = nanosTimeout - 消耗時間,如果返回值 <= 0 ,則可以認定它已經超時了。

  4. awaitUninterruptibly() :造成當前線程在接到信號之前一直處於等待狀態。【注意:該方法對中斷不敏感】。

  5. awaitUntil(Date deadline) :造成當前線程在接到信號、被中斷或到達指定最后期限之前一直處於等待狀態。如果沒有到指定時間就被通知,則返回true,否則表示到了指定時間,返回返回false。

  6. signal() :喚醒一個等待線程。該線程從等待方法返回前必須獲得與Condition相關的鎖。

  7. signal()All :喚醒所有等待線程。能夠從等待方法返回的線程必須獲得與Condition相關的鎖。

Semaphore介紹

Semaphore 是 synchronized 的加強版,作用是控制線程的並發數量。就這一點而言,單純的synchronized 關鍵字是實現不了的。他可以保證某一個資源在一段區間內有多少給線程可以去訪問。

從源碼我們可以看出來,它new了一個靜態內部類,繼承Sync接口。他同時也提供了一些構造方法

比如說通過這個構造方法可以創建一個是否公平的Semaphore類。關於公平鎖和非公平鎖大家可以看我的另外一篇文章。

關於這塊更多的了解可以看我其他的文章去了解。

有問題可以在下面評論,技術問題可以私聊我。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM