从数据结构和Thread的机制上控制Daemon线程空耗CPU
从数据结构和Thread的机制上控制Daemon线程空耗CPU
背景:我们将线程设置成Daemon的时候,一般在run()方法会设置成一个while(true) forever的场景,而如果不去控制的话,空耗的while会占用大量的CPU时间片,导致CPU负荷过重。解决措施:
1)在每层循环结束时,添加sleep(millisecond),然线程休眠一段时间。(注意,在这种情况下,不会释放对于具有独占特性的对象的同步锁)
2)在使用具有的同步锁的对象的Thread中,应该sleep和yield联合使用,降低空耗,同时还要将对于对象的锁的控制权让出一会。
3)使用具有java.util.concurrent.*中的类。这里引用Hadoop中TaskTracker.java 的一段代码:
这里的LinkedBlockingQueue.take方法会将Thread的状态变为Wait,从而缓解了一直处于Runnable的情况。take方法会跟踪队列,直到可以获取其中的值为止。
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");
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;