歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux編程 >> Linux編程 >> 從數據結構和Thread的機制上控制Daemon線程空耗CPU

從數據結構和Thread的機制上控制Daemon線程空耗CPU

日期:2017/3/1 10:14:24   编辑:Linux編程
背景:我們將線程設置成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;
notifyAll(); //@1 notify();
}

}

class Consumer implements Runnable {

private final Dropbox dropbox;
private final boolean even;

public Consumer(boolean even, Dropbox dropbox) {
this.even = even;
this.dropbox = dropbox;
}

public void run() {
Random random = new Random();
while (true) {
dropbox.take(even);
try {
Thread.sleep(random.nextInt(100));
} catch (InterruptedException e) {
}
}
}

}

class Producer implements Runnable {

private Dropbox dropbox;

public Producer(Dropbox dropbox) {
this.dropbox = dropbox;
}

public void run() {
Random random = new Random();
while (true) {
int number = random.nextInt(10);
try {
Thread.sleep(random.nextInt(100));
dropbox.put(number);
} catch (InterruptedException e) {
}
}
}

}


在上面的程序中,Dropbox是倉庫,生產者和消費者互斥地訪問該倉庫的內容。並設置兩個消費者,一個為odd,一個為even,分別消費生產者數量為odd或even產品,而且一次性全部消費完畢。
* 在//@1處使用notify,因為喚醒線程的順序不同,會使得最終所有的線程都處於wait狀態。
Even is waiting ...
Odd is waiting ...
Producer put 4.
Even took 4.
Odd is waiting ...
Producer put 2.
Odd is waiting ...
Producer is waiting ...
Even took 2.
Odd is waiting ...
Even is waiting ...
此時三個線程都處於wait狀態,使用jstack查看程序狀態:
...
"Thread-2" prio=10 tid=0x0810e400 nid=0x6cce in Object.wait() [0x8f7fd000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x9432f640> (a Dropbox)
at java.lang.Object.wait(Object.java:485)
at Dropbox.put(ProducerConsumerExample.java:44)
- locked <0x9432f640> (a Dropbox)
at Producer.run(ProducerConsumerExample.java:94)
at java.lang.Thread.run(Thread.java:619)

Locked ownable synchronizers:
- None

"Thread-1" prio=10 tid=0x0810a800 nid=0x6ccd in Object.wait() [0x8f84e000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x9432f640> (a Dropbox)
at java.lang.Object.wait(Object.java:485)
at Dropbox.take(ProducerConsumerExample.java:29)
- locked <0x9432f640> (a Dropbox)
at Consumer.run(ProducerConsumerExample.java:70)
at java.lang.Thread.run(Thread.java:619)

Locked ownable synchronizers:
- None

"Thread-0" prio=10 tid=0x08109000 nid=0x6ccc in Object.wait() [0x8f89f000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x9432f640> (a Dropbox)
at java.lang.Object.wait(Object.java:485)
at Dropbox.take(ProducerConsumerExample.java:29)
- locked <0x9432f640> (a Dropbox)
at Consumer.run(ProducerConsumerExample.java:70)
at java.lang.Thread.run(Thread.java:619)

Locked ownable synchronizers:
- None
在一個時間片之後,所有線程都處於wait狀態。
*在//@1上使用notifyAll,會同時通知所有的線程。運行的結果是:程序一直處於執行狀態.

Thread 與 對象的monitor lock 相互配合,使得同步互斥操作得以有序地進行。
Copyright © Linux教程網 All Rights Reserved