1. 程式人生 > >Java面試09|多線程

Java面試09|多線程

異步任務 task 獲得 rem don 所在 情況 模式 程序

15個頂級Java多線程面試題及回答 http://ifeve.com/15-java-faq/

1、關於線程的狀態及相互轉換

技術分享

(1)join()方法:能夠使線程順序執行。可以將一個任務分為多個子任務,調用join()方法等待這些子任務完成運算,然後匯總結果。需要註意這並不能保證各個子任務線程的結束順序。

(2)線程只能從就緒狀態(runnable)這一條唯一路徑到運行狀態(running)

(3)Thread類,定義了一些列的線程操作函數。例如,sleep()休眠, interrupt()中斷, getName()獲取線程名稱等

(4)在啟動線程時調用start()方法,重復調用會拋出線程狀態異常。調用run()就相當於調用了普通方法

(5)在線程調用wait()方法進入阻塞狀態,再次被喚醒時繼續執行wait()方法後面的代碼

(6)wait()會釋放鎖,而yield()和sleep()不會釋放鎖

(7)註意interrupt()方法被調用後狀態由等待blocked進入鎖定blocked,這在AQS中的公平鎖中需要處理這種情況。

2、線程中斷的幾個方法

(1)public void interrupt() 中斷目標線程

(2)public boolean isInterrupted() 返回中斷狀態

(3)public static boolean interrupted() 靜態方法,清除中斷狀態並返回之前的值

阻塞庫方法,Thread.sleep()和Object.wait()等,都會檢查線程何時中斷,並且發現中斷時提前返回,它們在響應中斷時執行的操作包括:清除中斷狀態、拋出InterruptedException,表示阻塞操作由於中斷而提前結束。

可以總結下:

(1)處於blocked狀態的線程:清除中斷狀態、拋出InterruptedException,表示阻塞操作由於中斷而提前結束

(2)處於runnable或者running狀態時,線程的中斷標記會被設置為true

(3)處於dead狀態時不會產生任何操作

上面的題目一定要看,我在這裏篩選及補充了幾個題目:

3、假如有Thread1、Thread2、Thread3、Thread4四條線程分別統計C、D、E、F四個盤的大小,所有線程都統計完畢交給Thread5線程去做匯總,應當如何實現?

把相互獨立的計算任務包含在一個工作單元內,無需為每個單元啟動新的線程。這樣處理多線程代碼通常效率更高。因為不用去為每個計算單元單獨啟動Thread線程。執行代碼的線程是重用的。

(1)任務

Callable代表了一段可以調用並返回結果的代碼

Future接口用來表示異步任務,是還沒有完成的任務給出的未來結果。主要方法有get()、cancel()和isDone()

FutureTask是Future接口的常用實現類,它也實現了Runnable接口,所以和Runnable和Callable一樣,可以由執行者高度。

(2)執行者 通過Executors類的工廠方法獲取眾多執行者之一

有個關於FutureTask的好例子,如下:

public class FutureTaskExample {  
     public static void main(String[] args) {  
        MyCallable callable1 = new MyCallable(1000);                       // 要執行的任務  
        MyCallable callable2 = new MyCallable(2000);  
  
        FutureTask<String> futureTask1 = new FutureTask<String>(callable1);// 將Callable寫的任務封裝到一個由執行者調度的FutureTask對象  
        FutureTask<String> futureTask2 = new FutureTask<String>(callable2);  
   
        ExecutorService executor = Executors.newFixedThreadPool(2);        // 創建線程池並返回ExecutorService實例  
        executor.execute(futureTask1);  // 執行任務  
        executor.execute(futureTask2);    
           
        while (true) {  
            try {  
                if(futureTask1.isDone() && futureTask2.isDone()){//  兩個任務都完成  
                    System.out.println("Done");  
                    executor.shutdown();                          // 關閉線程池和服務   
                    return;  
                }  
                   
                if(!futureTask1.isDone()){ // 任務1沒有完成,會等待,直到任務完成  
                    System.out.println("FutureTask1 output="+futureTask1.get());  
                }  
                   
                System.out.println("Waiting for FutureTask2 to complete");  
                String s = futureTask2.get(200L, TimeUnit.MILLISECONDS);  
                if(s !=null){  
                    System.out.println("FutureTask2 output="+s);  
                }  
            } catch (InterruptedException | ExecutionException e) {  
                e.printStackTrace();  
            }catch(TimeoutException e){  
                //do nothing  
            }  
        }  
    }  
}  

  

示例如下:

public class Sums {

	// 使用Callable比Runnable更優勢的地方在於Callable可以有確切的返回值。
	static class Sum implements Callable<Long> {
		private final long from;
		private final long to;

		Sum(long from, long to) {
			this.from = from;
			this.to = to;
		}

		@Override
		public Long call() {
			long acc = 0;
			for (long i = from; i <= to; i++) {
				acc = acc + i;
			}
			return acc;
		}
	}

	public static void main(String[] args) throws Exception {
		ExecutorService executor = Executors.newFixedThreadPool(2);
		
		// Executes the given tasks, returning a list of Futures holding their status and results when all complete
		List<Future<Long>> results = executor.invokeAll( 
														   asList(
																new Sum(0, 10), 
																new Sum(100, 1_000), 
																new Sum(10_000, 1_000_000)
															));
		// 另外要註意executor服務必須被關閉。如果它沒有被關閉,主方法執行完後JVM就不會退出,因為仍然有激活線程存在
		executor.shutdown();

		for (Future<Long> result : results) {
			// Waits if necessary for the computation to complete, and then retrieves its result.
			System.out.println(result.get());
		}
	}
}

書寫如上程序時需要註意三點:

(1)Callable是接口,我們需要重寫的方法為call()

(2)線程池用完一定要shutdown()

4、分析線程池的實現原理和任務的調度過程

關於線程池的實現原理可以參考如下:

(1)一定要看 http://www.jianshu.com/p/87bff5cc8d8c

(1)http://blog.csdn.net/mazhimazh/article/details/19243889

(2)http://blog.csdn.net/mazhimazh/article/details/19283171

(3)參考《Java特種兵》295頁內容

線程池實現原理就是線程池與工作隊列的組合,在Executor任務執行框架中就體現了這種模式。

一個簡單的小例子。

package ThreadPool;

import java.util.LinkedList;
import java.util.List;

/**
 * 線程池類,線程管理器:創建線程,執行任務,銷毀線程,獲取線程基本信息
 */
public final class ThreadPool {
	// 線程池中默認線程的個數為5
	private static int worker_num = 5;
	// 工作線程
	private WorkThread[] workThrads;
	// 未處理的任務
	private static volatile int finished_task = 0;
	// 任務隊列,作為一個緩沖,List線程不安全
	private List<Runnable> taskQueue = new LinkedList<Runnable>();
	private static ThreadPool threadPool;

	// 創建具有默認線程個數的線程池
	private ThreadPool() {
		this(5);
	}

	// 創建線程池,worker_num為線程池中工作線程的個數
	private ThreadPool(int worker_num) {
		ThreadPool.worker_num = worker_num;
		workThrads = new WorkThread[worker_num];
		for (int i = 0; i < worker_num; i++) {
			workThrads[i] = new WorkThread();
			workThrads[i].start();// 開啟線程池中的線程
		}
	}

	// 單態模式,獲得一個默認線程個數的線程池
	public static ThreadPool getThreadPool() {
		return getThreadPool(ThreadPool.worker_num);
	}

	// 單態模式,獲得一個指定線程個數的線程池,worker_num(>0)為線程池中工作線程的個數
	// worker_num<=0創建默認的工作線程個數
	public static ThreadPool getThreadPool(int worker_num1) {
		if (worker_num1 <= 0)
			worker_num1 = ThreadPool.worker_num;
		if (threadPool == null)
			threadPool = new ThreadPool(worker_num1);
		return threadPool;
	}

	// 執行任務,其實只是把任務加入任務隊列,什麽時候執行有線程池管理器覺定
	public void execute(Runnable task) {
		synchronized (taskQueue) {
			taskQueue.add(task);
			taskQueue.notify();
		}
	}

	// 批量執行任務,其實只是把任務加入任務隊列,什麽時候執行有線程池管理器覺定
	public void execute(Runnable[] task) {
		synchronized (taskQueue) {
			for (Runnable t : task)
				taskQueue.add(t);
			taskQueue.notify();
		}
	}

	// 批量執行任務,其實只是把任務加入任務隊列,什麽時候執行有線程池管理器覺定
	public void execute(List<Runnable> task) {
		synchronized (taskQueue) {
			for (Runnable t : task)
				taskQueue.add(t);
			taskQueue.notify();
		}
	}

	// 銷毀線程池,該方法保證在所有任務都完成的情況下才銷毀所有線程,否則等待任務完成才銷毀
	public void destroy() {
		while (!taskQueue.isEmpty()) {// 如果還有任務沒執行完成,就先睡會吧
			try {
				Thread.sleep(10);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		// 工作線程停止工作,且置為null
		for (int i = 0; i < worker_num; i++) {
			workThrads[i].stopWorker();
			workThrads[i] = null;
		}
		threadPool=null;
		taskQueue.clear();// 清空任務隊列
	}

	// 返回工作線程的個數
	public int getWorkThreadNumber() {
		return worker_num;
	}

	// 返回已完成任務的個數,這裏的已完成是只出了任務隊列的任務個數,可能該任務並沒有實際執行完成
	public int getFinishedTasknumber() {
		return finished_task;
	}

	// 返回任務隊列的長度,即還沒處理的任務個數
	public int getWaitTasknumber() {
		return taskQueue.size();
	}

	// 覆蓋toString方法,返回線程池信息:工作線程個數和已完成任務個數
	@Override
	public String toString() {
		return "WorkThread number:" + worker_num + "  finished task number:"
				+ finished_task + "  wait task number:" + getWaitTasknumber();
	}

	/**
	 * 內部類,工作線程
	 */
	private class WorkThread extends Thread {
		// 該工作線程是否有效,用於結束該工作線程
		private boolean isRunning = true;

		/*
		 * 關鍵所在啊,如果任務隊列不空,則取出任務執行,若任務隊列空,則等待
		 */
		@Override
		public void run() {
			Runnable r = null;
			while (isRunning) {// 註意,若線程無效則自然結束run方法,該線程就沒用了
				synchronized (taskQueue) { // 在這裏提供了同步
					while (isRunning && taskQueue.isEmpty()) {// 隊列為空
						try {
							taskQueue.wait(20);
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
					}
					if (!taskQueue.isEmpty())
						r = taskQueue.remove(0);// 取出任務
				}
				if (r != null) {
					r.run();// 執行任務
				}
				finished_task++;
				r = null;
			}
		}// end run

		// 停止工作,讓該線程自然執行完run方法,自然結束
		public void stopWorker() {
			isRunning = false;
		}
	}
}

//測試線程池  
public class TestThreadPool {
	public static void main(String[] args) {
		// 創建3個線程的線程池
		ThreadPool t = ThreadPool.getThreadPool(3);
		t.execute(new Runnable[] { new Task(), new Task(), new Task() });
		t.execute(new Runnable[] { new Task(), new Task(), new Task() });
		System.out.println(t);
		t.destroy();// 所有線程都執行完成才destory
		System.out.println(t);
	}

	// 任務類
	static class Task implements Runnable {
		private static volatile int i = 1;
		@Override
		public void run() {// 執行任務
			System.out.println("任務 " + (i++) + " 完成");
		}
	}
}

Java線程的調度分為協同式線程調度和搶占式線程調度。

ScheduleThreadPoolExecutor是Java提供的多線程調度器,它可以接收任務,並把它們安排給線程池裏的線程。可以參考如下內容:

《Java特種兵》 306頁

利用了多線程加上任務資源共享的方式來實現服務器端大量任務的調度。

Java面試09|多線程