从数据结构和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 的一段代码:
 
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 相互配合,使得同步互斥操作得以有序地进行。

相关内容