【Java】多線程

題目

面試題如下:

考點

本題考查的是線程通信的問題。更側重的是考查sync+wait+notify的組合使用。

代碼

Solution1: LockSupport

比較優的解法。

import java.util.concurrent.locks.LockSupport;

public class Maureen_LockSupport {
	static Thread t1 = null, t2 = null;

	public static void main(String[] args) throws Exception {
		char[] aI = "1234567".toCharArray();
		char[] aC = "ABCDEFG".toCharArray();

		t1 = new Thread(() -> {
			for (char c : aI) {
				System.out.print(c);
				LockSupport.unpark(t2); // t2線程繼續運行
				LockSupport.park(); // 當前線程暫停
			}
		}, "t1");

		t2 = new Thread(() -> {
			for (char c : aC) {
				LockSupport.park();
				System.out.print(c);
				LockSupport.unpark(t1);
			}
		}, "t2");

		t1.start();
		t2.start();
	}
}

運行結果:1A2B3C4D5E6F7G

Solution2:CAS -- 自旋鎖


public class Maureen_CAS {
	enum ReadyToRun {
		T1, T2
	}

	static volatile ReadyToRun r = ReadyToRun.T1;

	public static void main(String[] args) {
		char[] aI = "1234567".toCharArray();
		char[] aC = "ABCDEFG".toCharArray();

		new Thread(() -> {
			for (char c : aI) {
				while (r != ReadyToRun.T1) {
					//空轉,一直佔着CPU,也就是自旋(原地打轉)
				}
				System.out.print(c);
				r = ReadyToRun.T2; //每輸出一個字符,就將r標記進行修改 
			}
		}, "t1").start();

		new Thread(() -> {
			for (char c : aC) {
				while (r != ReadyToRun.T2) {
				}
				System.out.print(c);
				r = ReadyToRun.T1;
			}
		}, "t2").start();

	}
}

輸出結果:1A2B3C4D5E6F7G

Solution3:AtomicInteger

import java.util.concurrent.atomic.AtomicInteger;

public class Maureen_AtomicInteger {

	static AtomicInteger threadNo = new AtomicInteger(1);

	public static void main(String[] args) {
		char[] aI = "1234567".toCharArray();
		char[] aC = "ABCDEFG".toCharArray();

		new Thread(() -> {
			for (char c : aI) {
				while (threadNo.get() != 1) {
				}
				System.out.print(c);
				threadNo.set(2);
			}
		}, "t1").start();

		new Thread(() -> {
			for (char c : aC) {
				while (threadNo.get() != 2) {
				}
				System.out.print(c);
				threadNo.set(1);
			}
		}, "t2").start();
	}
}

輸出結果:1A2B3C4D5E6F7G

Solution4:BlockingQueue

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

public class Maureen_BlockingQueue {

	static BlockingQueue<String> q1 = new ArrayBlockingQueue(1);
	static BlockingQueue<String> q2 = new ArrayBlockingQueue(1);

	public static void main(String[] args) throws Exception {
		char[] aI = "1234567".toCharArray();
		char[] aC = "ABCDEFG".toCharArray();

		new Thread(() -> {
			for (char c : aI) {
				System.out.print(c);
				try {
					q1.put("ok");
					q2.take();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}, "t1").start();

		new Thread(() -> {
			for (char c : aC) {
				try {
					q1.take(); //等着q1中有內容,取出內容再繼續運行;如果沒有內容,就阻塞
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.print(c);
				try {
					q2.put("ok");
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}, "t2").start();
	}

}

輸出結果:1A2B3C4D5E6F7G

Solution5:PipedStream

import java.io.IOException;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;

public class Maureen_PipedStream {

	public static void main(String[] args) throws IOException {
		char[] aI = "1234567".toCharArray();
		char[] aC = "ABCDEFG".toCharArray();

		PipedInputStream input1 = new PipedInputStream();
		PipedInputStream input2 = new PipedInputStream();
		PipedOutputStream output1 = new PipedOutputStream();
		PipedOutputStream output2 = new PipedOutputStream();

		input1.connect(output2);
		input2.connect(output1);

		String msg = "Your Turn";

		new Thread(() -> {
			byte[] buffer = new byte[9];
			try {
				for (char c : aI) {
					input1.read(buffer);
					if (new String(buffer).equals(msg)) {
						System.out.print(c);
					}
					output1.write(msg.getBytes());
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}, "t1").start();

		new Thread(() -> {
			byte[] buffer = new byte[9];
			try {
				for (char c : aC) {
					System.out.print(c);
					output2.write(msg.getBytes());
					input2.read(buffer);
					if (new String(buffer).equals(msg)) {
						continue;
					}
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}, "t2").start();

	}

}

輸出結果:A1B2C3D4E5F6G7。該方法效率很低。

Solution6:sync_wait_notify

①兩個線程交替輸出,但是無法確保哪個線程先輸出,可能會先輸出字母


//要使用wait和notify,要對一個對象加鎖,才能在其中進行notify和wait的操作
public class Maureen_sync_wait_notify_00 {

	public static void main(String[] args) {
		final Object o = new Object();

		char[] aI = "1234567".toCharArray();
		char[] aC = "ABCDEFG".toCharArray();

		new Thread(() -> {
			synchronized (o) {// 不能使用this,因爲代碼塊中是一個匿名內部類,如果用this,那麼和下面代碼塊的this不是同一個對象,不能構成同步
				for (char c : aI) {
					System.out.print(c);
					try {
						o.notify(); // 叫醒等待隊列裏的任意一個,notifyAll是叫醒所有線程
						o.wait(); // 讓出鎖 運行中的線程進入等待隊列中,與此同時將鎖釋放
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
				o.notify(); // 必須,否則無法停止程序。因爲無論哪個線程先運行完,總有個線程處於wait狀態,只有叫醒該線程程序纔會運行完
			}
		}, "t1").start();

		new Thread(() -> {
			synchronized (o) {// 拿不到鎖的時候就在等待隊列中
				for (char c : aC) {
					System.out.print(c);
					try {
						o.notify();
						o.wait();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
				o.notify();
			}
		}, "t2").start();

	}

}

輸出結果:1A2B3C4D5E6F7G

執行流程如下:

持有鎖之後就執行輸出。

②使用CAS限制t2線程先運行

//sync_wait_notify程序無法限制哪個線程先運行,因此在這個程序裏限制哪個線程先運行
public class Maureen_sync_wait_notify_01 {
	private static volatile boolean t2Started = false;

	public static void main(String[] args) {
		final Object o = new Object();

		char[] aI = "1234567".toCharArray();
		char[] aC = "ABCDEFG".toCharArray();

		new Thread(() -> {
			synchronized (o) {
				while (!t2Started) { //限制t2先運行,如果t2沒有先運行,t1就先等待
					try {
						o.wait();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}

				for (char c : aI) {
					System.out.print(c);
					try {
						o.notify();
						o.wait();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
				o.notify();
			}
		},"t1").start();
		
		new Thread(()->{
			synchronized(o) {
				for(char c: aC) {
					System.out.print(c);
					t2Started = true;
					try {
						o.notify();
						o.wait();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
				o.notify();
			}
		},"t2").start();

	}

}

輸出結果:A1B2C3D4E5F6G7

③使用CountDownLatch限制t2先運行

import java.util.concurrent.CountDownLatch;

//使用CountDownLatch限制線程運行順序,以下是限制線程2先運行
public class Maureen_sync_wait_notify_02 {

	private static CountDownLatch latch = new CountDownLatch(1);

	public static void main(String[] args) {
		final Object o = new Object();

		char[] aI = "1234567".toCharArray();
		char[] aC = "ABCDEFG".toCharArray();

		new Thread(() -> {
			try {
				latch.await();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			synchronized (o) {
				for (char c : aI) {
					System.out.print(c);
					try {
						o.notify();
						o.wait();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
				o.notify();
			}
		}, "t1").start();

		new Thread(() -> {
			synchronized (o) {
				for (char c : aC) {
					System.out.print(c);
					latch.countDown();
					try {
						o.notify();
						o.wait();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
				o.notify();
			}
		}, "t2").start();

	}

}

/**
 * 線程調用start()函數,並不意味着線程立即佔用CPU運行,而是進入CPU的等待隊列中,即進入Ready狀態。根據操作系統的調度,決定選擇哪個線程運行
 */

輸出結果:A1B2C3D4E5F6G7

線程狀態遷移圖:

發佈了154 篇原創文章 · 獲贊 48 · 訪問量 15萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章