寫在前面:
這篇文章是我最近看15個頂級Java多線程面試題及回答這篇帖子,根據文中所列問題在網上找的答案彙總。或許某些解答不盡如人意,歡迎你們來補充和指正。另外感謝這篇帖子的翻譯者趙峯以及全部在網絡上分享問題答案的朋友們~~html
1. 有T一、T二、T3三個線程,如何怎樣保證T2在T1執行完後執行,T3在T2執行完後執行?java
使用join方法。程序員
join方法的功能是使異步執行的線程變成同步執行。即調用線程實例的start方法後,該方法會當即返回,若是調用start方法後,須要使用一個由這個線程計算獲得的值,就必須使用join方法。若是不使用join方法,就不能保證當執行到start方法後面的某條語句時,這個線程必定會執行完。而使用join方法後,直到這個線程退出,程序纔會往下執行。web
2.Java中的Lock接口,比起synchronized,優點在哪裏?面試
若是須要實現一個高效的緩存,它容許多個用戶讀,但只容許一個用戶寫,以此來保持它的完整性,如何實現?算法
Lock接口最大的優點是爲讀和寫分別提供了鎖。編程
讀寫鎖ReadWriteLock擁有更增強大的功能,它可細分爲讀鎖和解鎖。緩存
讀鎖能夠容許多個進行讀操做的線程同時進入,但不容許寫進程進入;寫鎖只容許一個寫進程進入,在這期間任何進程都不能再進入。(徹底符合題目中容許多個用戶讀和一個用戶寫的條件)安全
要注意的是每一個讀寫鎖都有掛鎖和解鎖,最好將每一對掛鎖和解鎖操做都用try、finally來套入中間的代碼,這樣就會防止因異常的發生而形成死鎖得狀況。性能優化
下面是一個示例程序:
import java.util.Random; import java.util.concurrent.locks.*; public class ReadWriteLockTest { public static void main(String[] args) { final TheData myData=new TheData(); //這是各線程的共享數據 for(int i=0;i<3;i++){ //開啓3個讀線程 new Thread(new Runnable(){ @Override public void run() { while(true){ myData.get(); } } }).start(); } for(int i=0;i<3;i++){ //開啓3個寫線程 new Thread(new Runnable(){ @Override public void run() { while(true){ myData.put(new Random().nextInt(10000)); } } }).start(); } } } class TheData{ private Object data=null; private ReadWriteLock rwl=new ReentrantReadWriteLock(); public void get(){ rwl.readLock().lock(); //讀鎖開啓,讀線程都可進入 try { //用try finally來防止因異常而形成的死鎖 System.out.println(Thread.currentThread().getName()+"is ready to read"); Thread.sleep(new Random().nextInt(100)); System.out.println(Thread.currentThread().getName()+"have read date"+data); } catch (InterruptedException e) { e.printStackTrace(); } finally{ rwl.readLock().unlock(); //讀鎖解鎖 } } public void put(Object data){ rwl.writeLock().lock(); //寫鎖開啓,這時只有一個寫線程進入 try { System.out.println(Thread.currentThread().getName()+"is ready to write"); Thread.sleep(new Random().nextInt(100)); this.data=data; System.out.println(Thread.currentThread().getName()+"have write date"+data); } catch (InterruptedException e) { e.printStackTrace(); } finally{ rwl.writeLock().unlock(); //寫鎖解鎖 } } }
3. java中wait和sleep方法有何不一樣?
最大的不一樣是在等待時wait會釋放鎖,而sleep一直持有鎖。Wait一般被用於線程間交互,sleep一般被用於暫停執行。
其它不一樣有:
4.如何用Java實現阻塞隊列?
首先,咱們要明確阻塞隊列的定義:
阻塞隊列(BlockingQueue)是一個支持兩個附加操做的隊列。這兩個附加的操做是:在隊列爲空時,獲取元素的線程會等待隊列變爲非空。當隊列滿時,存儲元素的線程會等待隊列可用。 阻塞隊列經常使用於生產者和消費者的場景,生產者是往隊列裏添加元素的線程,消費者是從隊列裏拿元素的線程。阻塞隊列就是生產者存放元素的容器,而消費者也只從容器裏拿元素。
注:有關生產者——消費者問題,可查閱維基百科網址:
和百度百科網址:
http://baike.baidu.com/view/10800629.htm
阻塞隊列的一個簡單實現:
public class BlockingQueue { private List queue = new LinkedList(); private int limit = 10; public BlockingQueue(int limit){ this.limit = limit; } public synchronized void enqueue(Object item)throws InterruptedException { while(this.queue.size() == this.limit) { wait(); } if(this.queue.size() == 0) { notifyAll(); } this.queue.add(item); } public synchronized Object dequeue() throws InterruptedException{ while(this.queue.size() == 0){ wait(); } if(this.queue.size() == this.limit){ notifyAll(); } return this.queue.remove(0); } }
在enqueue和dequeue方法內部,只有隊列的大小等於上限(limit)或者下限(0)時,才調用notifyAll方法。若是隊列的大小既不等於上限,也不等於下限,任何線程調用enqueue或者dequeue方法時,都不會阻塞,都可以正常的往隊列中添加或者移除元素。
5.編寫Java代碼,解決生產者——消費者問題。
生產者——消費者問題是研究多線程程序時繞不開的經典問題之一,它描述是有一塊緩衝區做爲倉庫,生產者能夠將產品放入倉庫,消費者則能夠從倉庫中取走產品。
使用問題4中阻塞隊列實現代碼來解決。但此不是惟一解決方案。
解決生產者/消費者問題的方法可分爲兩類:
第一種方式有較高的效率,而且易於實現,代碼的可控制性較好,屬於經常使用的模式。第二種管道緩衝區不易控制,被傳輸數據對象不易於封裝等,實用性不強。所以建議使用第一種方式來實現。
同步的核心問題在於:如何保證同一資源被多個線程併發訪問時的完整性?
經常使用的同步方法是採用信號或加鎖機制,保證資源在任意時刻至多被一個線程訪問。Java語言在多線程編程上實現了徹底對象化,提供了對同步機制的良好支持。
在Java中一共有四種方法支持同步,其中前三個是同步方法,一個是管道方法。管道方法不建議使用,阻塞隊列方法在問題4已有描述,現只提供前兩種實現方法。
生產者類:
public class Producer extends Thread { // 每次生產的產品數量 private int num; // 所在放置的倉庫 private Storage storage; // 構造函數,設置倉庫 public Producer(Storage storage) { this.storage = storage; } // 線程run函數 public void run() { produce(num); } // 調用倉庫Storage的生產函數 public void produce(int num) { storage.produce(num); } public int getNum() { return num; } public void setNum(int num) { this.num = num; } public Storage getStorage() { return storage; } public void setStorage(Storage storage) { this.storage = storage; } }
消費者類:
public class Consumer extends Thread { // 每次消費的產品數量 private int num; // 所在放置的倉庫 private Storage storage; // 構造函數,設置倉庫 public Consumer(Storage storage) { this.storage = storage; } // 線程run函數 public void run() { consume(num); } // 調用倉庫Storage的生產函數 public void consume(int num) { storage.consume(num); } // get/set方法 public int getNum() { return num; } public void setNum(int num) { this.num = num; } public Storage getStorage() { return storage; } public void setStorage(Storage storage) { this.storage = storage; } }
倉庫類:(wait()/notify()方法)
public class Storage { // 倉庫最大存儲量 private final int MAX_SIZE = 100; // 倉庫存儲的載體 private LinkedList<Object> list = new LinkedList<Object>(); // 生產num個產品 public void produce(int num) { // 同步代碼段 synchronized (list) { // 若是倉庫剩餘容量不足 while (list.size() + num > MAX_SIZE) { System.out.print("【要生產的產品數量】:" + num); System.out.println(" 【庫存量】:" + list.size() + " 暫時不能執行生產任務!"); try { list.wait();// 因爲條件不知足,生產阻塞 } catch (InterruptedException e) { e.printStackTrace(); } } // 生產條件知足狀況下,生產num個產品 for (int i = 1; i <= num; ++i) { list.add(new Object()); } System.out.print("【已經生產產品數】:" + num); System.out.println(" 【現倉儲量爲】:" + list.size()); list.notifyAll(); } } // 消費num個產品 public void consume(int num) { // 同步代碼段 synchronized (list) { // 若是倉庫存儲量不足 while (list.size() < num) { System.out.print("【要消費的產品數量】:" + num); System.out.println(" 【庫存量】:" + list.size() + " 暫時不能執行生產任務!"); try { // 因爲條件不知足,消費阻塞 list.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } // 消費條件知足狀況下,消費num個產品 for (int i = 1; i <= num; ++i) { list.remove(); } System.out.print("【已經消費產品數】:" + num); System.out.println(" 【現倉儲)量爲】:" + list.size()); list.notifyAll(); } } // get/set方法 public LinkedList<Object> getList() { return list; } public void setList(LinkedList<Object> list) { this.list = list; } public int getMAX_SIZE() { return MAX_SIZE; } }
倉庫類:(await()/signal()方法)
public class Storage { // 倉庫最大存儲量 // 倉庫最大存儲量 private final int MAX_SIZE = 100; // 倉庫存儲的載體 private LinkedList<Object> list = new LinkedList<Object>(); // 鎖 private final Lock lock = new ReentrantLock(); // 倉庫滿的條件變量 private final Condition full = lock.newCondition(); // 倉庫空的條件變量 private final Condition empty = lock.newCondition(); // 生產num個產品 public void produce(int num) { // 得到鎖 lock.lock(); // 若是倉庫剩餘容量不足 while (list.size() + num > MAX_SIZE) { System.out.print("【要生產的產品數量】:" + num); System.out.println(" 【庫存量】:" + list.size() + " 暫時不能執行生產任務!"); try { // 因爲條件不知足,生產阻塞 full.await(); } catch (InterruptedException e) { e.printStackTrace(); } } // 生產條件知足狀況下,生產num個產品 for (int i = 1; i <= num; ++i) { list.add(new Object()); } System.out.print("【已經生產產品數】:" + num); System.out.println(" 【現倉儲量爲】:" + list.size()); // 喚醒其餘全部線程 full.signalAll(); empty.signalAll(); // 釋放鎖 lock.unlock(); } // 消費num個產品 public void consume(int num) { // 得到鎖 lock.lock(); // 若是倉庫存儲量不足 while (list.size() < num) { System.out.print("【要消費的產品數量】:" + num); System.out.println(" 【庫存量】:" + list.size() + " 暫時不能執行生產任務!"); try { // 因爲條件不知足,消費阻塞 empty.await(); } catch (InterruptedException e) { e.printStackTrace(); } } // 消費條件知足狀況下,消費num個產品 for (int i = 1; i <= num; ++i) { list.remove(); } System.out.print("【已經消費產品數】:" + num); System.out.println(" 【現倉儲)量爲】:" + list.size()); // 喚醒其餘全部線程 full.signalAll(); empty.signalAll(); // 釋放鎖 lock.unlock(); } // set/get方法 public int getMAX_SIZE() { return MAX_SIZE; } public LinkedList<Object> getList() { return list; } public void setList(LinkedList<Object> list) { this.list = list; } }
6. 如何解決一個用Java編寫的會致使死鎖的程序?
Java線程死鎖問題每每和一個被稱之爲哲學家就餐的問題相關聯。
注:有關哲學家就餐的問題,可查閱維基百科網址:
http://zh.wikipedia.org/wiki/%E5%93%B2%E5%AD%A6%E5%AE%B6%E5%B0%B1%E9%A4%90%E9%97%AE%E9%A2%98
和百度百科網址:
致使死鎖的根源在於不適當地運用「synchronized」關鍵詞來管理線程對特定對象的訪問。
「synchronized」關鍵詞的做用是,確保在某個時刻只有一個線程被容許執行特定的代碼塊,所以,被容許執行的線程首先必須擁有對變量或對象的排他性的訪問權。當線程訪問對象 時,線程會給對象加鎖,而這個鎖致使其它也想訪問同一對象的線程被阻塞,直至第一個線程釋放它加在對象上的鎖。因爲這個緣由,在使用「synchronized」關鍵詞時,很容易出現兩個線程互相等待對方作出某個動做的情形。
死鎖程序例子
public class Deadlocker implements Runnable { public int flag = 1; static Object o1 = new Object(), o2 = new Object(); public void run() { System.out.println("flag=" + flag); if (flag == 1) { synchronized (o1) { try { Thread.sleep(500); } catch (Exception e) { e.printStackTrace(); } synchronized (o2) { System.out.println("1"); } } } if (flag == 0) { synchronized (o2) { try { Thread.sleep(500); } catch (Exception e) { e.printStackTrace(); } synchronized (o1) { System.out.println("0"); } } } } public static void main(String[] args) { Deadlocker td1 = new Deadlocker(); Deadlocker td2 = new Deadlocker(); td1.flag = 1; td2.flag = 0; Thread t1 = new Thread(td1); Thread t2 = new Thread(td2); t1.start(); t2.start(); } }
說明:
當類的對象flag=1時(T1),先鎖定O1,睡眠500毫秒,而後鎖定O2; 而T1在睡眠的時候另外一個flag=0的對象(T2)線程啓動,先鎖定O2,睡眠500毫秒,等待T1釋放O1; T1睡眠結束後須要鎖定O2才能繼續執行,而此時O2已被T2鎖定; T2睡眠結束後須要鎖定O1才能繼續執行,而此時O1已被T1鎖定; T1、T2相互等待,都須要對方鎖定的資源才能繼續執行,從而死鎖。
避免死鎖的一個通用的經驗法則是:當幾個線程都要訪問共享資源A、B、C時,保證使每一個線程都按照一樣的順序去訪問它們,好比都先訪問A,再訪問B和C。
如把 Thread t2 = new Thread(td2); 改爲 Thread t2 = new Thread(td1);
還有一種方法是對對象進行synchronized,加大鎖定的粒度,如上面的例子中使得進程鎖定當前對象,而不是逐步鎖定當前對象的兩個子對象o1和o2。這樣就在t1鎖定o1以後, 即便發生休眠,當前對象仍然被t1鎖定,t2不能打斷t1去鎖定o2,等t1休眠後再鎖定o2,獲取資源,執行成功。而後釋放當前對象t2,接着t1繼續運行。
代碼以下:
public class Deadlocker implements Runnable { public int flag = 1; static Object o1 = new Object(), o2 = new Object(); public synchronized void run() { System.out.println("flag=" + flag); if (flag == 1) { try { Thread.sleep(500); } catch (Exception e) { e.printStackTrace(); } System.out.println("1"); } if (flag == 0) { try { Thread.sleep(500); } catch (Exception e) { e.printStackTrace(); } System.out.println("0"); } } public static void main(String[] args) { Deadlocker td1 = new Deadlocker(); Deadlocker td2 = new Deadlocker(); td1.flag = 1; td2.flag = 0; Thread t1 = new Thread(td1); Thread t2 = new Thread(td2); t1.start(); t2.start(); } }
代碼修改爲public synchronized void run(){..},去掉子對象鎖定。對於一個成員方法加synchronized關鍵字,其實是以這個成員方法所在的對象自己做爲對象鎖。此例中,即對td1,td2這兩個Deadlocker 對象進行加鎖。
第三種解決死鎖的方法是使用實現Lock接口的重入鎖類(ReentrantLock),代碼以下:
public class Deadlocker implements Runnable { public int flag = 1; static Object o1 = new Object(), o2 = new Object(); private final Lock lock = new ReentrantLock(); public boolean checkLock() { return lock.tryLock(); } public void run() { if (checkLock()) { try { System.out.println("flag=" + flag); if (flag == 1) { try { Thread.sleep(500); } catch (Exception e) { e.printStackTrace(); } System.out.println("1"); } if (flag == 0) { try { Thread.sleep(500); } catch (Exception e) { e.printStackTrace(); } System.out.println("0"); } } finally { lock.unlock(); } } } public static void main(String[] args) { Deadlocker td1 = new Deadlocker(); Deadlocker td2 = new Deadlocker(); td1.flag = 1; td2.flag = 0; Thread t1 = new Thread(td1); Thread t2 = new Thread(td2); t1.start(); t2.start(); } }
說明:
代碼行lock.tryLock()是測試對象操做是否已在執行中,若是已在執行中則再也不執行此對象操做,當即返回false,達到忽略對象操做的效果。
7. 什麼是原子操做,Java中的原子操做是什麼?
所謂原子操做是指不會被線程調度機制打斷的操做;這種操做一旦開始,就一直運行到結束,中間切換到另外一個線程。
java中的原子操做介紹:
jdk1.5的包爲java.util.concurrent.atomic
這個包裏面提供了一組原子類。其基本特性就是在多線程環境下,當有多個線程同時執行這些類的實例包含的方法時,具備排他性。
即當某個線程進入方法,執行其中的指令時,不會被其餘線程打斷,而別的線程就像鎖同樣,一直等到該方法執行完成,才由JVM從等待隊列中選擇另外一個線程進入,這只是一種邏輯上的理解。其實是藉助硬件的相關指令來實現的,但不會阻塞線程(synchronized 會把別的等待的線程掛,或者說只是在硬件級別上阻塞了)。
其中的類能夠分紅4組
Atomic類的做用
AtomicBoolean , AtomicInteger, AtomicLong, AtomicReference 這四種基本類型用來處理布爾,整數,長整數,對象四種數據。
構造函數(兩個構造函數)
默認的構造函數:初始化的數據分別是false,0,0,null
帶參構造函數:參數爲初始化的數據
set( )和get( )方法:能夠原子地設定和獲取atomic的數據。相似於volatile,保證數據會在主存中設置或讀取
getAndSet( )方法
compareAndSet( ) 和weakCompareAndSet( )方法
對於AtomicInteger、AtomicLong還提供了一些特別的方法。getAndIncrement( )、incrementAndGet( )、getAndDecrement( )、decrementAndGet ( )、addAndGet( )、getAndAdd( )以實現一些加法,減法原子操做。(注意 --i、++i不是原子操做,其中包含有3個操做步驟:第一步,讀取i;第二步,加1或減1;第三步:寫回內存)
例子-使用AtomicReference建立線程安全的堆棧
public class LinkedStack<T> { private AtomicReference<Node<T>> stacks = new AtomicReference<Node<T>>(); public T push(T e) { Node<T> oldNode, newNode; while (true) { //這裏的處理很是的特別,也是必須如此的。 oldNode = stacks.get(); newNode = new Node<T>(e, oldNode); if (stacks.compareAndSet(oldNode, newNode)) { return e; } } } public T pop() { Node<T> oldNode, newNode; while (true) { oldNode = stacks.get(); newNode = oldNode.next; if (stacks.compareAndSet(oldNode, newNode)) { return oldNode.object; } } } private static final class Node<T> { private T object; private Node<T> next; private Node(T object, Node<T> next) { this.object = object; this.next = next; } } }
8. Java中的volatile關鍵字是什麼做用?怎樣使用它?在Java中它跟synchronized方法有什麼不一樣?
volatile在多線程中是用來同步變量的。 線程爲了提升效率,將某成員變量(如A)拷貝了一份(如B),線程中對A的訪問其實訪問的是B。只在某些動做時才進行A和B的同步。所以存在A和B不一致的狀況。
volatile就是用來避免這種狀況的。volatile告訴jvm, 它所修飾的變量不保留拷貝,直接訪問主內存中的(也就是上面說的A) 變量。
一個變量聲明爲volatile,就意味着這個變量是隨時會被其餘線程修改的,所以不能將它cache在線程memory中。如下例子展示了volatile的做用:
public class StoppableTask extends Thread { private volatile boolean pleaseStop; public void run() { while (!pleaseStop) { // do some stuff... } } public void tellMeToStop() { pleaseStop = true; } }
假如pleaseStop沒有被聲明爲volatile,線程執行run的時候檢查的是本身的副本,就不能及時得知其餘線程已經調用tellMeToStop()修改了pleaseStop的值。
Volatile通常狀況下不能代替sychronized,由於volatile不能保證操做的原子性,即便只是i++,實際上也是由多個原子操做組成:
read i; inc; write i,
假如多個線程同時執行i++,volatile只能保證他們操做的i是同一塊內存,但依然可能出現寫入髒數據的狀況。若是配合Java 5增長的atomic wrapper classes,對它們的increase之類的操做就不須要sychronized。
volatile和synchronized的不一樣是最容易解釋清楚的。volatile是變量修飾符,而synchronized則做用於一段代碼或方法;看以下三句get代碼:
int i1; volatile int i2; int i3; int geti1() { return i1; } int geti2() { return i2; } synchronized int geti3() { return i3; }
獲得存儲在當前線程中i1的數值。多個線程有多個i1變量拷貝,並且這些i1之間能夠互不相同。換句話說,另外一個線程可能已經改變了它線程內的 i1值,而這個值能夠和當前線程中的i1值不相同。事實上,Java有個思想叫「主」內存區域,這裏存放了變量目前的「準確值」。每一個線程能夠有它本身的 變量拷貝,而這個變量拷貝值能夠和「主」內存區域裏存放的不一樣。所以實際上存在一種可能:「主」內存區域裏的i1值是1,線程1裏的i1值是2,線程2裏 的i1值是3——這在線程1和線程2都改變了它們各自的i1值,並且這個改變還沒來得及傳遞給「主」內存區域或其餘線程時就會發生。
而 geti2()獲得的是「主」內存區域的i2數值。用volatile修飾後的變量不容許有不一樣於「主」內存區域的變量拷貝。換句話說,一個變量經 volatile修飾後在全部線程中必須是同步的;任何線程中改變了它的值,全部其餘線程當即獲取到了相同的值。理所固然的,volatile修飾的變量存取時比通常變量消耗的資源要多一點,由於線程有它本身的變量拷貝更爲高效。
既然volatile關鍵字已經實現了線程間數據同步,又要 synchronized幹什麼呢?它們之間有兩點不一樣。首先,synchronized得到並釋放監視器——若是兩個線程使用了同一個對象鎖,監視器能強制保證代碼塊同時只被一個線程所執行——這是衆所周知的事實。可是,synchronized也同步內存:事實上,synchronized在「 主」內存區域同步整個線程的內存。所以,執行geti3()方法作了以下幾步:
1.線程請求得到監視this對象的對象鎖(假設未被鎖,不然線程等待直到鎖釋放)
2.線程內存的數據被消除,從「主」內存區域中讀入
3.代碼塊被執行
4,對於變量的任何改變如今能夠安全地寫到「主」內存區域中(不過geti3()方法不會改變變量值)
5.線程釋放監視this對象的對象鎖
所以volatile只是在線程內存和「主」內存間同步某個變量的值,而synchronized經過鎖定和解鎖某個監視器同步全部變量的值。顯然synchronized要比volatile消耗更多資源。
9. 什麼是競爭條件?如何發現和解決競爭?
兩個線程同步操做同一個對象,使這個對象的最終狀態不明——叫作競爭條件。競爭條件能夠在任何應該由程序員保證原子操做的,而又忘記使用synchronized的地方。
惟一的解決方案就是加鎖。
Java有兩種鎖可供選擇:
10.如何使用thread dump?如何分析Thread dump?
Thread Dump是很是有用的診斷Java應用問題的工具,每個Java虛擬機都有及時生成顯示全部線程在某一點狀態的thread-dump的能力。雖然各個 Java虛擬機打印輸出格式上略微有一些不一樣,可是Thread dumps出來的信息包含線程;線程的運行狀態、標識和調用的堆棧;調用的堆棧包含完整的類名,所執行的方法,若是可能的話還有源代碼的行數。
SUN
JVM 產生ThreadDumpSolaris OS
<ctrl>-’\’ (Control-Backslash) kill -QUIT <PID>
HP-UX/UNIX/Linux
Kill -3 <PID>
Windows
直接對MSDOS窗口的程序按Ctrl-break
有些Java應用服務器是在控制檯上運行,如Weblogic,爲了方便獲取threaddump信息,在weblogic啓動的時候,會將其標準輸出重定向到一個文件, 用"nohup ./startWebLogic .sh > log.out &
"命令,執行"kill -3 <pid>
",Thread dump就會輸出到log.out裏。
Tomcat的Thread Dump會輸出到命令行控制檯或者logs的catalina.out文件裏。爲了反映線程狀態的動態變化,須要接連作三次以上thread dump,每次間隔10-20s。
IBM JVM 產生Thread Dump
在AIX上用IBM的JVM,內存溢出時默認地會產生javacore文件(關於cpu的)和heapdump文件(關於內存的)。 若是沒有,則參照下列方法:
在server啓動前設置下面環境變量(能夠加在啓動腳本中)
export IBM_HEAPDUMP=true export IBM_HEAP_DUMP=true export IBM_HEAPDUMP_OUTOFMEMORY=true export IBM_HEAPDUMPDIR=<directory path>
用set命令檢查參數設置,確保沒有設置DISABLE_JAVADUMP,而後啓動server
執行kill -3 命令能夠生成javacore文件和heapdump文件
拿到java thread dump後,你要作的就是查找"waiting for monitor entry"的thread,若是大量thread都在等待給同一個地址上鎖(由於對於Java,一個對象只有一把鎖),這說明極可能死鎖發生了。好比:
"service-j2ee" prio=5 tid=0x024f1c28 nid=0x125 waiting for monitor entry [62a3e000..62a3f690] [27/Jun/2006:10:03:08] WARNING (26140): CORE3283: stderr: at com.sun.enterprise.resource.IASNonSharedResourcePool.internalGetResource(IASNonS haredResourcePool.java:625) [27/Jun/2006:10:03:08] WARNING (26140): CORE3283: stderr: - waiting to lock <0x965d8110> (a com.sun.enterprise.resource.IASNonSharedResourcePool) [27/Jun/2006:10:03:08] WARNING (26140): CORE3283: stderr: at com.sun.enterprise.resource.IASNonSharedResourcePool.getResource(IASNonSharedRes ourcePool.java:520) ................
爲了肯定問題,經常須要在隔兩分鐘後再次收集一次thread dump,若是獲得的輸出相同,仍然是大量thread都在等待給同一個地址上鎖,那麼確定是死鎖了。
如何找到當前持有鎖的線程是解決問題的關鍵。方法是搜索thread dump,查找"locked <0x965d8110>
", 找到持有鎖的線程。
[27/Jun/2006:10:03:08] WARNING (26140): CORE3283: stderr: "Thread-20" daemon prio=5 tid=0x01394f18 nid=0x109 runnable [6716f000..6716fc28] [27/Jun/2006:10:03:08] WARNING (26140): CORE3283: stderr: at java.net.SocketInputStream.socketRead0(Native Method) [27/Jun/2006:10:03:08] WARNING (26140): CORE3283: stderr: at java.net.SocketInputStream.read(SocketInputStream.java:129) [27/Jun/2006:10:03:08] WARNING (26140): CORE3283: stderr: at oracle.net.ns.Packet.receive(Unknown Source) [27/Jun/2006:10:03:08] WARNING (26140): CORE3283: stderr: at oracle.net.ns.DataPacket.receive(Unknown Source) [27/Jun/2006:10:03:08] WARNING (26140): CORE3283: stderr: at oracle.net.ns.NetInputStream.getNextPacket(Unknown Source) [27/Jun/2006:10:03:08] WARNING (26140): CORE3283: stderr: at oracle.net.ns.NetInputStream.read(Unknown Source) [27/Jun/2006:10:03:08] WARNING (26140): CORE3283: stderr: at oracle.net.ns.NetInputStream.read(Unknown Source) [27/Jun/2006:10:03:08] WARNING (26140): CORE3283: stderr: at oracle.net.ns.NetInputStream.read(Unknown Source) [27/Jun/2006:10:03:08] WARNING (26140): CORE3283: stderr: at oracle.jdbc.ttc7.MAREngine.unmarshalUB1(MAREngine.java:929) [27/Jun/2006:10:03:08] WARNING (26140): CORE3283: stderr: at oracle.jdbc.ttc7.MAREngine.unmarshalSB1(MAREngine.java:893) [27/Jun/2006:10:03:08] WARNING (26140): CORE3283: stderr: at oracle.jdbc.ttc7.Ocommoncall.receive(Ocommoncall.java:106) [27/Jun/2006:10:03:08] WARNING (26140): CORE3283: stderr: at oracle.jdbc.ttc7.TTC7Protocol.logoff(TTC7Protocol.java:396) [27/Jun/2006:10:03:08] WARNING (26140): CORE3283: stderr: - locked <0x954f47a0> (a oracle.jdbc.ttc7.TTC7Protocol) [27/Jun/2006:10:03:08] WARNING (26140): CORE3283: stderr: at oracle.jdbc.driver.OracleConnection.close(OracleConnection.java:1518) [27/Jun/2006:10:03:08] WARNING (26140): CORE3283: stderr: - locked <0x954f4520> (a oracle.jdbc.driver.OracleConnection) [27/Jun/2006:10:03:08] WARNING (26140): CORE3283: stderr: at com.sun.enterprise.resource.JdbcUrlAllocator.destroyResource(JdbcUrlAllocator.java:122) [27/Jun/2006:10:03:08] WARNING (26140): CORE3283: stderr: at com.sun.enterprise.resource.IASNonSharedResourcePool.destroyResource(IASNonSharedResourcePool.java:872) [27/Jun/2006:10:03:08] WARNING (26140): CORE3283: stderr: at com.sun.enterprise.resource.IASNonSharedResourcePool.resizePool(IASNonSharedResourcePool.java:1086) [27/Jun/2006:10:03:08] WARNING (26140): CORE3283: stderr: - locked <0x965d8110> (a com.sun.enterprise.resource.IASNonSharedResourcePool) [27/Jun/2006:10:03:08] WARNING (26140): CORE3283: stderr: at com.sun.enterprise.resource.IASNonSharedResourcePool$Resizer.run(IASNonSharedResourcePool.java:1178) [27/Jun/2006:10:03:08] WARNING (26140): CORE3283: stderr: at java.util.TimerThread.mainLoop(Timer.java:432) [27/Jun/2006:10:03:08] WARNING (26140): CORE3283: stderr: at java.util.TimerThread.run(Timer.java:382)
在這個例子裏,持有鎖的線程在等待Oracle返回結果,卻始終等不到響應,所以發生了死鎖。
若是持有鎖的線程還在等待給另外一個對象上鎖,那麼仍是按上面的辦法順藤摸瓜,直到找到死鎖的根源爲止。 另外,在thread dump裏還會常常看到這樣的線程,它們是等待一個條件而主動放棄鎖的線程。例如:
"Thread-1" daemon prio=5 tid=0x014e97a8 nid=0x80 in Object.wait() [68c6f000..68c6fc28] at java.lang.Object.wait(Native Method) - waiting on <0x95b07178> (a java.util.LinkedList) at com.iplanet.ias.util.collection.BlockingQueue.remove(BlockingQueue.java:258) - locked <0x95b07178> (a java.util.LinkedList) at com.iplanet.ias.util.threadpool.FastThreadPool$ThreadPoolThread.run(FastThreadPool.java:241) at java.lang.Thread.run(Thread.java:534)
有時也會須要分析這類線程,尤爲是線程等待的條件。
其實,Java thread dump並不僅用於分析死鎖,其它Java應用運行時古怪的行爲均可以用thread dump來分析。
在Java SE 5裏,增長了jstack的工具,也能夠獲取thread dump。在Java SE 6裏, 經過jconsole的圖形化工具也能夠方便地查找涉及object monitors 和java.util.concurrent.locks死鎖。
參考文章:http://www.cnblogs.com/zhengyun_ustc/archive/2013/01/06/dumpanalysis.html
11. 爲何調用start()方法時會執行run()方法,而不能直接調用run()方法?
調用start()方法時,將會建立新的線程,而且執行在run()方法裏的代碼。但若是直接調用 run()方法,它不會建立新的線程也不會執行調用線程的代碼。
12. Java中怎樣喚醒一個阻塞的線程?
若是是IO阻塞,建立線程時,加一個數量的閾值,超過該值後則再也不建立。或者爲每一個線程設置標誌變量標誌該線程是否已經束,三就是直接加入線程組去管理。
若是線程由於調用 wait()、sleep()、或者join()方法而致使的阻塞,你能夠中斷線程,而且經過拋出InterruptedException來喚醒它。
13. Java中CycliBarriar和CountdownLatch有什麼區別?
CountdownLatch: 一個線程(或者多個),等待另外N個線程完成某個事情以後才能執行。
CycliBarriar: N個線程相互等待,任何一個線程完成以前,全部的線程都必須等待。
這樣應該就清楚一點了,對於CountDownLatch來講,重點是那個「一個線程」, 是它在等待,而另外那N的線程在把「某個事情」作完以後能夠繼續等待,也能夠終止。
而對於CyclicBarrier來講,重點是那N個線程,他們之間任何一個沒有完成,全部的線程都必須等待。
14. 什麼是不可變對象,它對寫併發應用有什麼幫助?
不可變對象(英語:Immutable object)是一種對象,在被創造以後,它的狀態就不能夠被改變。
因爲它不可更改,併發時不須要其餘額外的同步保證,故相比其餘的鎖同步等方式的併發性能要好。
衍生問題:爲何String是不可變的?
字符串常量池(String pool, String intern pool, String保留池) 是Java堆內存中一個特殊的存儲區域, 當建立一個String對象時,假如此字符串值已經存在於常量池中,則不會建立一個新的對象,而是引用已經存在的對象。
以下面的代碼所示,將會在堆內存中只建立一個實際String對象.
String s1 = "abcd"; String s2 = "abcd";
示意圖以下所示:
倘若字符串對象容許改變,那麼將會致使各類邏輯錯誤,好比改變一個對象會影響到另外一個獨立對象. 嚴格來講,這種常量池的思想,是一種優化手段.
請思考: 倘若代碼以下所示,s1和s2還會指向同一個實際的String對象嗎?
String s1= "ab" + "cd"; String s2= "abc" + "d";
也許這個問題違反新手的直覺, 可是考慮到現代編譯器會進行常規的優化, 因此他們都會指向常量池中的同一個對象. 或者,你能夠用 jd-gui 之類的工具查看一下編譯後的class文件.
Java中String對象的哈希碼被頻繁地使用, 好比在hashMap 等容器中。
字符串不變性保證了hash碼的惟一性,所以能夠放心地進行緩存.這也是一種性能優化手段,意味着沒必要每次都去計算新的哈希碼. 在String類的定義中有以下代碼:
private int hash;//用來緩存HashCode
String被許多的Java類(庫)用來當作參數,例如 網絡鏈接地址URL,文件路徑path,還有反射機制所須要的String參數等, 倘若String不是固定不變的,將會引發各類安全隱患。
假若有以下的代碼:
boolean connect(String s) { if (!isSecure(s)) { throw new SecurityException(); } // 若是在其餘地方能夠修改String,那麼此處就會引發各類預料不到的問題/錯誤 causeProblem(s); }
15. 多線程環境中遇到的常見問題是什麼?如何解決?
多線程和併發程序中常遇到的有Memory-interface、競爭條件、死鎖、活鎖和飢餓。
Memory-interface(暫無資料)[X]
競爭條件見第9題
死鎖見第6題
活鎖和飢餓:
活鎖(英文 livelock)
概念:指事物1可使用資源,但它讓其餘事物先使用資源;事物2可使用資源,但它也讓其餘事物先使用資源,因而二者一直謙讓,都沒法使用資源。活鎖有必定概率解開。而死鎖(deadlock)是沒法解開的。
解決:避免活鎖的簡單方法是採用先來先服務的策略。當多個事務請求封鎖同一數據對象時,封鎖子系統按請求封鎖的前後次序對事務排隊,數據對象上的鎖一旦釋放就批准申請隊列中第一個事務得到鎖。
飢餓
概念:是指若是事務T1封鎖了數據R,事務T2又請求封鎖R,因而T2等待。T3也請求封鎖R,當T1釋放了R上的封鎖後,系統首先批准了T3的請 求,T2仍然等待。而後T4又請求封鎖R,當T3釋放了R上的封鎖以後,系統又批准了T4的請求......T2可能永遠等待,這就是飢餓。
解決: 公平鎖: 每個調用lock()的線程都會進入一個隊列,當解鎖後,只有隊列裏的第一個線程被容許鎖住Farlock實例,全部其它的線程都將處於等待狀態,直到他們處於隊列頭部。
代碼示例 公平鎖類:
public class FairLock { private boolean isLocked = false; private Thread lockingThread = null; private List<QueueObject> waitingThreads = new ArrayList<QueueObject>(); public void lock() throws InterruptedException { QueueObject queueObject = new QueueObject(); boolean isLockedForThisThread = true; synchronized (this) { waitingThreads.add(queueObject); } while (isLockedForThisThread) { synchronized (this) { isLockedForThisThread = isLocked || waitingThreads.get(0) != queueObject; if (!isLockedForThisThread) { isLocked = true; waitingThreads.remove(queueObject); lockingThread = Thread.currentThread(); return; } } try { queueObject.doWait(); } catch (InterruptedException e) { synchronized (this) { waitingThreads.remove(queueObject); } throw e; } } } public synchronized void unlock() { if (this.lockingThread != Thread.currentThread()) { throw new IllegalMonitorStateException("Calling thread has not locked this lock"); } isLocked = false; lockingThread = null; if (waitingThreads.size() > 0) { waitingThreads.get(0).doNotify(); } } }
隊列對象類:
public class QueueObject { private boolean isNotified = false; public synchronized void doWait() throws InterruptedException { while (!isNotified) { this.wait(); } this.isNotified = false; } public synchronized void doNotify() { this.isNotified = true; this.notify(); } public boolean equals(Object o) { return this == o; } }
說明:
首先lock()方法再也不聲明爲synchronized,取而代之的是對必需同步的代碼,在synchronized中進行嵌套。 FairLock新建立一個QueueObject的實例,並對每一個調用lock()的線程進行入隊列。調用unlock()的線程將從隊列頭部獲取QueueObject,並對其調用doNotify(),用以喚醒在該對象上等待的線程。經過這種方式,在同一時間僅有一個等待線程得到喚醒,而不是全部的等待線程。這也是實現了FairLock公平性。
注意,在同一個同步塊中,鎖狀態依然被檢查和設置,以免出現滑漏條件。還有,QueueObject實際是一個semaphore。doWait()和doNotify()方法在QueueObject中保存着信號。這樣作以免一個線程在調用queueObject.doWait()以前被另外一個調用unlock()並隨之調用 queueObject.doNotify()的線程重入,從而致使信號丟失。queueObject.doWait()調用放置在 synchronized(this)塊以外,以免被monitor嵌套鎖死,因此只要沒有線程在lock方法的 synchronized(this)塊中執行,另外的線程均可以被解鎖。
最後,注意到queueObject.doWait()在try – catch塊中是怎樣調用的。在InterruptedException拋出的狀況下,線程得以離開lock(),並需讓它從隊列中移除。
16. 在java中綠色線程和本地線程區別?
綠色線程執行用戶級別的線程,且一次只使用一個OS線程。 本地線程用的是OS線程系統,在每一個JAVA線程中使用一個OS線程。 在執行java時,可經過使用-green或 -native標誌來選擇所用線程是綠色仍是本地。
17. 線程與進程的區別?
線程是指進程內的一個執行單元,也是進程內的可調度實體.
與進程的區別:
地址空間:進程內的一個執行單元;進程至少有一個線程;它們共享進程的地址空間;而進程有本身獨立的地址空間;
資源擁有:進程是資源分配和擁有的單位,同一個進程內的線程共享進程的資源
線程是處理器調度的基本單位,但進程不是.
兩者都可併發執行.
進程和線程都是由操做系統所體會的程序運行的基本單元,系統利用該基本單元實現系統對應用的併發性。進程和線程的區別在於:
簡而言之,一個程序至少有一個進程,一個進程至少有一個線程。線程的劃分尺度小於進程,使得多線程程序的併發性高。
另外,進程在執行過程當中擁有獨立的內存單元,而多個線程共享內存,從而極大地提升了程序的運行效率。 線程在執行過程當中與進程仍是有區別的。每一個獨立的線程有一個程序運行的入口、順序執行序列和程序的出口。可是線程不可以獨立執行,必須依存在應用程序中,由應用程序提供多個線程執行控制。
從邏輯角度來看,多線程的意義在於一個應用程序中,有多個執行部分能夠同時執行。但操做系統並無將多個線程看作多個獨立的應用,來實現進程的調度和管理以及資源分配。這就是進程和線程的重要區別。
進程是具備必定獨立功能的程序關於某個數據集合上的一次運行活動,進程是系統進行資源分配和調度的一個獨立單位.
線程是進程的一個實體,是CPU調度和分派的基本單位,它是比進程更小的能獨立運行的基本單位.線程本身基本上不擁有系統資源,只擁有一點在運行中必不可少的資源(如程序計數器,一組寄存器和棧),可是它可與同屬一個進程的其餘的線程共享進程所擁有的所有資源.
一個線程能夠建立和撤銷另外一個線程;同一個進程中的多個線程之間能夠併發執行.
18. 什麼是多線程中的上下文切換?
操做系統管理不少進程的執行。有些進程是來自各類程序、系統和應用程序的單獨進程,而某些進程來自被分解爲不少進程的應用或程序。當一個進程從內核中移出, 另外一個進程成爲活動的,這些進程之間便發生了上下文切換。操做系統必須記錄重啓進程和啓動新進程使之活動所須要的全部信息。這些信息被稱做上下文,它描述 了進程的現有狀態。當進程成爲活動的,它能夠繼續從被搶佔的位置開始執行。
當線程被搶佔時,就會發生線程之間的上下文切換。若是線程屬於相同的進程,它們共享相同的地址空間,由於線程包含在它們所屬於的進程的地址空間內。這樣,進程須要恢復的多數信息對於線程而言是不須要的。儘管進程和它的線程共享了不少內容,但最爲重要的是其地址空間和資源,有些信息對於線程而言是本地且惟一 的,而線程的其餘方面包含在進程的各個段的內部。
19. 死鎖與活鎖的區別,死鎖與飢餓的區別?
死鎖: 是指兩個或兩個以上的進程在執行過程當中,因爭奪資源而形成的一種互相等待的現象,若無外力做用,它們都將沒法推動下去。此時稱系統處於死鎖狀態或系統產生 了死鎖,這些永遠在互相等待的進程稱爲死鎖進程。 因爲資源佔用是互斥的,當某個進程提出申請資源後,使得有關進程在無外力協助下,永遠分配不到必需的資源而沒法繼續運行,這就產生了一種特殊現象:死鎖。
雖然進程在運行過程當中,可能發生死鎖,但死鎖的發生也必須具有必定的條件,死鎖的發生必須具有如下四個必要條件。
活鎖:指事物1可使用資源,但它讓其餘事物先使用資源;事物2可使用資源,但它也讓其餘事物先使用資源,因而二者一直謙讓,都沒法使用資源。
活鎖有必定概率解開。而死鎖(deadlock)是沒法解開的。
避免活鎖的簡單方法是採用先來先服務的策略。當多個事務請求封鎖同一數據對象時,封鎖子系統按請求封鎖的前後次序對事務排隊,數據對象上的鎖一旦釋放就批准申請隊列中第一個事務得到鎖。
死鎖與飢餓的區別?見第15題
20. Java中用到的線程調度算法是什麼?
計算機一般只有一個CPU,在任意時刻只能執行一條機器指令,每一個線程只有得到CPU的使用權才能執行指令. 所謂多線程的併發運行,實際上是指從宏觀上看,各個線程輪流得到CPU的使用權,分別執行各自的任務.在運行池中,會有多個處於就緒狀態的線程在等待CPU,JAVA虛擬機的一項任務就是負責線程的調度,線程調度是指按照特定機制爲多個線程分配CPU的使用權
java虛擬機採用搶佔式調度模型,是指優先讓可運行池中優先級高的線程佔用CPU,若是可運行池中的線程優先級相同,那麼就隨機選擇一個線程,使其佔用CPU。處於運行狀態的線程會一直運行,直至它不得不放棄CPU。
一個線程會由於如下緣由而放棄CPU。
須要注意的是,線程的調度不是跨平臺的,它不只僅取決於java虛擬機,還依賴於操做系統。在某些操做系統中,只要運行中的線程沒有遇到阻塞,就不會放棄CPU;
在某些操做系統中,即便線程沒有遇到阻塞,也會運行一段時間後放棄CPU,給其它線程運行的機會。 java的線程調度是不分時的,同時啓動多個線程後,不能保證各個線程輪流得到均等的CPU時間片。 若是但願明確地讓一個線程給另一個線程運行的機會,能夠採起如下辦法之一。
調整各個線程的優先級
讓處於運行狀態的線程調用Thread.sleep()方法
讓處於運行狀態的線程調用Thread.yield()方法
讓處於運行狀態的線程調用另外一個線程的join()方法
21.在Java中什麼是線程調度?
見上題
22. 在線程中,怎麼處理不可捕捉異常?
捕捉異常有兩種方法。
示例代碼:
public class TestThread implements Runnable { public void run() { throw new RuntimeException("throwing runtimeException....."); } }
當線程代碼拋出運行級別異常以後,線程會中斷。主線程不受這個影響,不會處理這個,並且根本不能捕捉到這個異常,仍然繼續執行本身的代碼。
方法1)代碼示例:
public class TestMain { public static void main(String[] args) { try { TestThread t = new TestThread(); ExecutorService exec = Executors.newCachedThreadPool(); Future future = exec.submit(t); exec.shutdown(); future.get();//主要是這句話起了做用,調用get()方法,異常重拋出,包裝在ExecutorException } catch (Exception e) {//這裏能夠把線程的異常繼續拋出去 System.out.println("Exception Throw:" + e.getMessage()); } } }
方法2)代碼示例:
public class HandlerThreadFactory implements ThreadFactory { public Thread newThread(Runnable runnable) { Thread t = new Thread(runnable); MyUncaughtExceptionHandler myUncaughtExceptionHandler = new MyUncaughtExceptionHandler(); t.setUncaughtExceptionHandler(myUncaughtExceptionHandler); return t; } } public class MyUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler { public void uncaughtException(Thread t, Throwable e) { System.out.println("write logger here:" + e); } } public class TestMain { public static void main(String[] args) { try { TestThread t = new TestThread(); ExecutorService exec = Executors.newCachedThreadPool(new HandlerThreadFactory()); exec.execute(t); } catch (Exception e) { System.out.println("Exception Throw:" + e.getMessage()); } } }
23. 什麼是線程組,爲何在Java中不推薦使用?
ThreadGroup線程組表示一個線程的集合。此外,線程組也能夠包含其餘線程組。線程組構成一棵樹,在樹中,除了初始線程組外,每一個線程組都有一個父線程組。
容許線程訪問有關本身的線程組的信息,可是不容許它訪問有關其線程組的父線程組或其餘任何線程組的信息。線程組的目的就是對線程進行管理。
線程組爲何不推薦使用
節省頻繁建立和銷燬線程的開銷,提高線程使用效率。
衍生問題:線程組和線程池的區別在哪裏?
一個線程的週期分爲:建立、運行、銷燬三個階段。處理一個任務時,首先建立一個任務線程,而後執行任務,完了,銷燬線程。而線程處於運行狀態的時候,纔是真的在處理咱們交給它的任務,這個階段纔是有效運行時間。因此,咱們但願花在建立和銷燬線程的資源越少越好。若是不銷燬線程,而這個線程又不能被其餘的任務調用,那麼就會出現資源的浪費。爲了提升效率,減小建立和銷燬線程帶來時間和空間上的浪費,出現了線程池技術。這種技術是在開始就建立必定量的線程,批量處理一類任務,等待任務的到來。任務執行完畢後,線程又能夠執行其餘的任務。等再也不須要線程的時候,就銷燬。這樣就省去了頻繁建立和銷燬線程的麻煩。
24. 爲何使用Executor框架比使用應用建立和管理線程好?
大多數併發應用程序是以執行任務(task)爲基本單位進行管理的。一般狀況下,咱們會爲每一個任務單首創建一個線程來執行。
這樣會帶來兩個問題:
一,大量的線程(>100)會消耗系統資源,使線程調度的開銷變大,引發性能降低;
二,對於生命週期短暫的任務,頻繁地建立和消亡線程並非明智的選擇。由於建立和消亡線程的開銷可能會大於使用多線程帶來的性能好處。
一種更加合理的使用多線程的方法是使用線程池(Thread Pool)。 java.util.concurrent 提供了一個靈活的線程池實現:Executor 框架。這個框架能夠用於異步任務執行,並且支持不少不一樣類型的任務執行策略。它還爲任務提交和任務執行之間的解耦提供了標準的方法,爲使用 Runnable 描述任務提供了通用的方式。 Executor的實現還提供了對生命週期的支持和hook 函數,能夠添加如統計收集、應用程序管理機制和監視器等擴展。
在線程池中執行任務線程,能夠重用已存在的線程,免除建立新的線程。這樣能夠在處理多個任務時減小線程建立、消亡的開銷。同時,在任務到達時,工做線程一般已經存在,用於建立線程的等待時間不會延遲任務的執行,所以提升了響應性。經過適當的調整線程池的大小,在獲得足夠多的線程以保持處理器忙碌的同時,還能夠防止過多的線程相互競爭資源,致使應用程序在線程管理上耗費過多的資源。
25. 在Java中Executor和Executors的區別?
Executor是接口,是用來執行 Runnable 任務的;它只定義一個方法- execute(Runnable command);執行 Ruannable 類型的任務。
Executors是類,提供了一系列工廠方法用於建立線程池,返回的線程池都實現了ExecutorService接口。
Executors幾個重要方法:
callable(Runnable task): 將 Runnable 的任務轉化成 Callable 的任務
newSingleThreadExecutor(): 產生一個ExecutorService對象,這個對象只有一個線程可用來執行任務,若任務多於一個,任務將按前後順序執行。
newCachedThreadPool(): 產生一個ExecutorService對象,這個對象帶有一個線程池,線程池的大小會根據須要調整,線程執行完任務後返回線程池,供執行下一次任務使用。
newFixedThreadPool(int poolSize): 產生一個ExecutorService對象,這個對象帶有一個大小爲 poolSize 的線程池,若任務數量大於 poolSize ,任務會被放在一個 queue 裏順序執行。
newSingleThreadScheduledExecutor(): 產生一個ScheduledExecutorService對象,這個對象的線程池大小爲 1 ,若任務多於一個,任務將按前後順序執行。
newScheduledThreadPool(int poolSize): 產生一個ScheduledExecutorService對象,這個對象的線程池大小爲 poolSize ,若任務數量大於 poolSize ,任務會在一個 queue 裏等待執行。
26. 如何在Windows和Linux上查找哪一個線程使用的CPU時間最長?
其實就是找CPU佔有率最高的那個線程
Windows
任務管理器裏面看,以下圖:
Linux
能夠用下面的命令將 cpu 佔用率高的線程找出來:
$ ps H -eo user,pid,ppid,tid,time,%cpu,cmd –sort=%cpu
這個命令首先指定參數’H',顯示線程相關的信息,格式輸出中包含:
user,pid,ppid,tid,time,%cpu,cmd
而後再用%cpu字段進行排序。這樣就能夠找到佔用處理器的線程了。
原文地址:http://www.ituring.com.cn/article/111835