背景:我們將線程設置成Daemon的時候,一般在run()方法會設置成一個while(true) forever的場景,而如果不去控制的話,空耗的while會占用大量的CPU時間片,導致CPU負荷過重。
解決措施:
1)在每層循環結束時,添加sleep(millisecond),然線程休眠一段時間。(注意,在這種情況下,不會釋放對於具有獨占特性的對象的同步鎖)
2)在使用具有的同步鎖的對象的Thread中,應該sleep和yield聯合使用,降低空耗,同時還要將對於對象的鎖的控制權讓出一會。
3)使用具有java.util.concurrent.*中的類。這裡引用Hadoop中TaskTracker.java 的一段代碼:
private BlockingQueue<TaskTrackerAction> tasksToCleanup =
new LinkedBlockingQueue<TaskTrackerAction>();
private Thread taskCleanupThread =
new Thread(new Runnable() {
public void run() {
while (true) {
try {
TaskTrackerAction action = tasksToCleanup.take();
if (action instanceof KillJobAction) {
purgeJob((KillJobAction) action);
} else if (action instanceof KillTaskAction) {
processKillTaskAction((KillTaskAction) action);
} else {
LOG.error("Non-delete action given to cleanup thread: "
+ action);
}
} catch (Throwable except) {
LOG.warn(StringUtils.stringifyException(except));
}
}
}
}, "taskCleanup");
這裡的LinkedBlockingQueue.take方法會將Thread的狀態變為Wait,從而緩解了一直處於Runnable的情況。take方法會跟蹤隊列,直到可以獲取其中的值為止。
4)使用Object的wait、notify、notifyAll 三劍客來降低Daemon內循環對於CPU的空耗。
注意點:
(1)wait 必須在synchronized 方法或者synchronized代碼塊中使用,並且它總出現在while(condition)的循環當中。
(2)public synchronized方法,代表了對象同步方法,一個對象的所有synchronized方法在一個時刻只能被一個線程使用其中的一個synchronized方法。
(3)每個對象都維護了一個monitor,它負責管理多線程訪問共享對象時,exclusively access.並且維護等待該對象monitor的thread列表。
(4)使用public static synchronized方法或者在static{}代碼塊中使用synchronized代碼塊,代表了類的同步方法,多個線程之間,只能獲得類的monitor才可以排它地訪問。
(5)在不使用synchronized的方法中,不需要擁有對象鎖或者類鎖(monitor),就可以直接訪問。因此如果存在線程之間共享變量的讀寫問題,會出現運行結果不一致的情況。
(6)wait方法使用的前提是當前線程擁有對象monitor,wait會釋放對象的monitor,這樣其它的線程就會有望得到該monitor.
notify和notifyAll的區別,這個問題確實爭論了很久,下面結合jdk的doc文檔,和在
sun stackOverflow上討論,給出個人的理解:
a)notify是通知等待該對象monitor的一個線程,其它線程仍處於await狀態;
b)notifyAll會在同一時間通知(awaken)所有等待對象monitor的線程,讓線程由blocked狀態變為awake狀態,然後,喚醒的線程會按照OS調度的順序去競爭monitor.這樣只要程序寫得合理,在一個線程使用完monitor之後釋放,則所有的線程都有可能競爭到monitor。
為了更好地說明這個道理,使用一個生產者和消費者的例子來說明notify/notifyAll的區別。
import java.util.Random;
public class ProducerConsumerExample {
private static boolean Even = true;
private static boolean Odd = false;
public static void main(String[] args) {
Dropbox dropbox = new Dropbox();
(new Thread(new Consumer(Even, dropbox))).start();
(new Thread(new Consumer(Odd, dropbox))).start();
(new Thread(new Producer(dropbox))).start();
}
}
class Dropbox {
private int number;
private boolean empty = true;
private boolean evenNumber = false;
public synchronized int take(final boolean even) {
while (empty || evenNumber != even) {
try {
System.out
.format("%s is waiting ... %n", even ? "Even" : "Odd");
wait();
} catch (InterruptedException e) {
}
}
System.out.format("%s took %d.%n", even ? "Even" : "Odd", number);
empty = true;
notifyAll(); //@1 notify();
return number;
}
public synchronized void put(int number) {
while (!empty) {
try {
System.out.println("Producer is waiting ...");
wait();
} catch (InterruptedException e) {
}
}
this.number = number;
evenNumber = number % 2 == 0;
System.out.format("Producer put %d.%n", number);
empty = false;