1. 程式人生 > 其它 >多執行緒--面試題整理

多執行緒--面試題整理

簡述執行緒,程式、程序的基本概念

執行緒:與程序相似,但執行緒是比程序更小的執行單位。一個程序在其執行的過程中可以產生多個執行緒。與程序不同的是同類的多個執行緒共享同一塊記憶體空間和一組系統資源,所以系統在產生一個執行緒,或是在各個執行緒之間作切換工作時,負擔要比程序小得多,也正因為如此,執行緒也被稱為輕量級程序。
程式:是含有指令和資料檔案,被儲存在磁碟或其他的資料儲存裝置中,也就是說程式是靜態程式碼。
程序:是程式的一次執行過程,是系統執行程式的基本單位,因此程序是動態的。換句話說,當程式在執行時,將會被作業系統載入記憶體中。 執行緒是程序劃分成的更小的執行單位。執行緒和程序最大的不同在於基本上各程序是獨立的,而各執行緒則不一定,
因為同一程序中的執行緒極有可能會相互影響。

執行緒有哪些基本狀態

Java 執行緒在執行的生命週期中可能處於下面 6 種不同狀態
新建(NEW)可執行(RUNNABLE)阻塞(BLOCKED)等待(WAITING)超時等待(TIMED_WAITING)終止(TERMINATED)
可執行狀態是個複合狀態,又可以分為READY:就緒狀態。RUNNING:表示該執行緒正在執行。

如何理解記憶體洩漏問題?有哪些情況會導致記憶體洩露?如何解決

記憶體洩漏:對於應用程式來講,當物件不再被使用的時候,但是java的垃圾回收不能回收他們,就產生了記憶體洩漏。

上圖中包含了未引用物件和引用物件。未引用物件將會被垃圾回收器回收,而引用物件卻不會。未引用 物件很顯然是無用的物件。然而,無用的物件並不都是未引用物件,有一些無用物件也有可能是引用物件,這部分物件正是記憶體洩露的來源。
比如說:物件A引用了物件B,A的生命週期比B的生命週期長,當物件B執行完,垃圾回收器是不會回收物件B的,這就可能造成記憶體不足的問題。因為A還引用著B,還可能引用其他比A生命週期短的物件。
怎樣阻止記憶體洩露


1.使用List、Map等集合時,在使用完成後賦值為null
2.使用大物件時,在用完後賦值為null
3.目前已知的jdk1.6的substring()方法會導致記憶體洩露
4.避免一些死迴圈等重複建立或對集合新增元素,撐爆記憶體
5.簡潔資料結構、少用靜態集合等
6.及時的關閉開啟的檔案,socket控制代碼等
7.多關注事件監聽(listeners)和回撥(callbacks),比如註冊了一個listener,當它不再被使用的時候,忘了登出該listener,可能就會產生記憶體洩露。

執行緒池的原理,為什麼要建立執行緒池?建立執行緒池的方式;

執行緒池的優點:
1、執行緒是稀缺資源,使用執行緒池可以減少建立和銷燬執行緒的次數,每個工作執行緒都可以重複使用。
2、可以根據系統的承受能力,調整執行緒池中工作執行緒的數量,防止因為消耗過多記憶體導致伺服器崩潰。
執行緒池的實現原理:



執行緒池的建立:

/**
* corePoolSize:執行緒池核心執行緒數量 
* maximumPoolSize:執行緒池最大執行緒數量 
* keepAliverTime:當活躍執行緒數大於核心執行緒數時,空閒的多餘執行緒最大存活時間 
* unit:存活時間的單位 
* workQueue:存放任務的佇列 
* handler:超出執行緒範圍和佇列容量的任務的處理程式
* @return
*/
public ThreadPoolExecutor(int corePoolSize, 
                        int maximumPoolSize,
                        long keepAliveTime,
                        TimeUnit unit, 
                        BlockingQueue<Runnable> workQueue, 
                        RejectedExecutionHandler handler)

執行緒池的原始碼解讀

1、ThreadPoolExecutor的execute()方法

public void execute(Runnable command) {
		if (command == null)
			throw new NullPointerException();
		//如果執行緒數大於等於基本執行緒數或者執行緒建立失敗,將任務加入佇列
		if (poolSize >= corePoolSize || !addIfUnderCorePoolSize(command)){
			//執行緒池處於執行狀態並且加入佇列成功
			if (runState == RUNNING && workQueue.offer(command)) {
				if (runState != RUNNING || poolSize == 0)
					ensureQueuedTaskHandled(command);
			}
			//執行緒池不處於執行狀態或者加入佇列失敗,則建立執行緒(建立的是非核心執行緒)
			else if (!addIfUnderMaximumPoolSize(command)){
				//建立執行緒失敗,則採取阻塞處理的方式
				reject(command); // is shutdown or saturated
			}
		}

	}

2、建立執行緒的方法:addIfUnderCorePoolSize(command)

private boolean addIfUnderCorePoolSize(Runnable firstTask) {
		Thread t = null;
		final ReentrantLock mainLock = this.mainLock;
		mainLock.lock();
		try {
			if (poolSize < corePoolSize && runState == RUNNING)
				t = addThread(firstTask);
		}finally {
			mainLock.unlock();
		}
		if (t == null)
			return false;
		t.start();
		return true;
	}

我們重點來看第7行,這裡將執行緒封裝成工作執行緒worker,並放入工作執行緒組裡:

private Thread addThread(Runnable firstTask) {
		Worker w = new Worker(firstTask);
		Thread t = threadFactory.newThread(w);
		if (t != null) {
			w.thread = t;
			workers.add(w);
			int nt = ++poolSize;
			if (nt > largestPoolSize)
				largestPoolSize = nt;
		}
		return t;
	}

worker類的方法run方法,worker在執行完任務後,還會通過getTask方法迴圈獲取工作隊裡裡的任務來執行:

    public void run() {
        try {
            Runnable task = firstTask;
            firstTask = null;
            while (task != null || (task = getTask()) != null) {
                runTask(task);
                task = null;
            }
        } finally {
            workerDone(this);
        }
    }

由於執行緒池的飽和策略內容過多,我將重新記錄一篇來講Java提供的4中策略:
1、AbortPolicy:直接丟擲異常
2、CallerRunsPolicy:只用呼叫所在的執行緒執行任務
3、DiscardOldestPolicy:丟棄佇列裡最近的一個任務,並執行當前任務。
4、DiscardPolicy:不處理,丟棄掉。