使用和不使用Executor的多线程之间的区别

时间:2012-03-07 07:25:10

标签: java multithreading sockets executor

我试图了解正常多线程和使用执行程序的多线程(维护线程池)之间的性能差异。

以下是两者的代码示例。

没有Executor代码(使用多线程):

import java.lang.management.ManagementFactory;
import java.lang.management.MemoryPoolMXBean;
import java.lang.management.MemoryUsage;
import java.lang.management.ThreadMXBean;
import java.util.List;

public class Demo1 {
public static void main(String arg[]) {
    Demo1 demo = new Demo1();
    Thread t5  = new Thread(new Runnable() {
       public void run() {
              int count=0;
              // Thread.State;
              // System.out.println("ClientMsgReceiver started-----");
              Demo1.ChildDemo  obj = new Demo1.ChildDemo();
              while(true) {

                // System.out.println("Threadcount is"+Thread);
                // System.out.println("count is"+(count++));
                Thread t=new Thread(obj);
                t.start();
                ThreadMXBean tb = ManagementFactory.getThreadMXBean();
                List<MemoryPoolMXBean> pools = ManagementFactory.getMemoryPoolMXBeans();
                for (MemoryPoolMXBean pool : pools) {
                   MemoryUsage peak = pool.getPeakUsage();
                   System.out.format("Peak %s memory used: %,d%n",
                             pool.getName(), peak.getUsed());
                   System.out.format("Peak %s memory reserved: %,d%n",
                             pool.getName(), peak.getCommitted());
                } 

                System.out.println("Current Thread Count"+ tb.getThreadCount());
                System.out.println("Peak Thread Count"+ tb.getPeakThreadCount());
                System.out.println("Current_Thread_Cpu_Time " 
                         + tb.getCurrentThreadCpuTime());
                System.out.println("Daemon Thread Count" +tb.getDaemonThreadCount());
       }
       // ChatLogin = new ChatLogin();
     }
  });
  t5.start();
}

static class ChildDemo implements Runnable {
   public void run() {
        try {
        //  System.out.println("Thread Started with custom Run method");
            Thread.sleep(100000);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        finally {
            System.out.println("A" +Thread.activeCount());
        }
    }
  }
}

使用执行程序(多线程):

import java.lang.management.ManagementFactory;
import java.lang.management.MemoryPoolMXBean;
import java.lang.management.MemoryUsage;
import java.lang.management.ThreadMXBean;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class Executor_Demo {
public static void main(String arg[]) {
   BlockingQueue<Runnable> queue = new ArrayBlockingQueue<Runnable>(10);
   ThreadPoolExecutor executor   = new ThreadPoolExecutor(
          10, 100, 10, TimeUnit.MICROSECONDS, queue);
   Executor_Demo demo = new Executor_Demo();

   executor.execute(new Runnable() {
       public void run() {
          int count=0;
          // System.out.println("ClientMsgReceiver started-----");
          Executor_Demo.Demo demo2 = new Executor_Demo.Demo();
          BlockingQueue<Runnable> queue1 = new ArrayBlockingQueue<Runnable>(1000);
          ThreadPoolExecutor executor1   = new ThreadPoolExecutor(
                  1000, 10000, 10, TimeUnit.MICROSECONDS, queue1);

          while(true) {
             // System.out.println("Threadcount is"+Thread);
             // System.out.println("count is"+(count++));
             Runnable command= new Demo();
             // executor1.execute(command);
             executor1.submit(command);         
             // Thread t=new Thread(demo2);
             // t.start();
             ThreadMXBean tb = ManagementFactory.getThreadMXBean();
             /* try {
                  executor1.awaitTermination(100, TimeUnit.MICROSECONDS);
                } catch (InterruptedException e) {
                   // TODO Auto-generated catch block
                   e.printStackTrace();
                } */
              List<MemoryPoolMXBean> pools = ManagementFactory.getMemoryPoolMXBeans();
              for (MemoryPoolMXBean pool : pools) {
                 MemoryUsage peak = pool.getPeakUsage();
                 System.out.format("Peak %s memory used: %,d%n",
                          pool.getName(), peak.getUsed());
                 System.out.format("Peak %s memory reserved: %,d%n",
                          pool.getName(), peak.getCommitted());
          }
              System.out.println("daemon threads"+tb.getDaemonThreadCount());
              System.out.println("All threads"+tb.getAllThreadIds());
              System.out.println("current thread CPU time "
                       + tb.getCurrentThreadCpuTime());
              System.out.println("current thread user time "
                       + tb.getCurrentThreadUserTime());
              System.out.println("Total started thread count " 
                       + tb.getTotalStartedThreadCount());
              System.out.println("Current Thread Count"+ tb.getThreadCount());
              System.out.println("Peak Thread Count"+ tb.getPeakThreadCount());
              System.out.println("Current_Thread_Cpu_Time " 
                       + tb.getCurrentThreadCpuTime());
              System.out.println("Daemon Thread Count"
                       + tb.getDaemonThreadCount());
              // executor1.shutdown();  
            }
            //ChatLogin = new ChatLogin();
          }
     });
     executor.shutdown();
}

static class Demo implements Runnable {
    public void run() {
      try {
        // System.out.println("Thread Started with custom Run method");
        Thread.sleep(100000);
      } catch (InterruptedException e) {
          // TODO Auto-generated catch block
          e.printStackTrace();
      }
      finally {
         System.out.println("A" +Thread.activeCount());
      }
   }
  }
}

示例输出Output

当我运行这两个程序时,事实证明执行程序比普通的多线程更昂贵。为什么会这样呢?

考虑到这一点,执行者的用途是什么?我们使用执行程序来管理线程池。

我原本希望执行程序能够提供比普通多线程更好的结果。

基本上我这样做是因为我需要使用多线程的套接字编程来处理数百万个客户端。

任何建议都会有所帮助。

2 个答案:

答案 0 :(得分:2)

要了解某些内容是如何扩展的,我会尽量将监控成本​​保持在最低水平,并将一小部分与一大部分进行比较。

public class Executor_Demo {
    public static void main(String... arg) throws ExecutionException, InterruptedException {
        int nThreads = 5100;
        ExecutorService executor = Executors.newFixedThreadPool(nThreads, new DaemonThreadFactory());

        List<Future<Results>> futures = new ArrayList<Future<Results>>();
        for (int i = 0; i < nThreads; i++) {
            futures.add(executor.submit(new BackgroundCallable()));
        }
        Results result = new Results();
        for (Future<Results> future : futures) {
            result.merge(future.get());
        }
        executor.shutdown();

        result.print(System.out);

    }

    static class Results {
        private long cpuTime;
        private long userTime;

        Results() {
            final ThreadMXBean tb = ManagementFactory.getThreadMXBean();
            cpuTime = tb.getCurrentThreadCpuTime();
            userTime = tb.getCurrentThreadUserTime();
        }


        public void merge(Results results) {
            cpuTime += results.cpuTime;
            userTime += results.userTime;
        }

        public void print(PrintStream out) {
            ThreadMXBean tb = ManagementFactory.getThreadMXBean();

            List<MemoryPoolMXBean> pools = ManagementFactory.getMemoryPoolMXBeans();
            for (int i = 0, poolsSize = pools.size(); i < poolsSize; i++) {
                MemoryPoolMXBean pool = pools.get(i);
                MemoryUsage peak = pool.getPeakUsage();
                out.format("Peak %s memory used:\t%,d%n", pool.getName(), peak.getUsed());
                out.format("Peak %s memory reserved:\t%,d%n", pool.getName(), peak.getCommitted());
            }

            out.println("Total thread CPU time\t" + cpuTime);
            out.println("Total thread user time\t" + userTime);
            out.println("Total started thread count\t" + tb.getTotalStartedThreadCount());
            out.println("Current Thread Count\t" + tb.getThreadCount());
            out.println("Peak Thread Count\t" + tb.getPeakThreadCount());
            out.println("Daemon Thread Count\t" + tb.getDaemonThreadCount());
        }
    }

    static class DaemonThreadFactory implements ThreadFactory {
        @Override
        public Thread newThread(Runnable r) {
            Thread t = new Thread(r);
            t.setDaemon(true);
            return t;
        }
    }

    static class BackgroundCallable implements Callable<Results> {
        @Override
        public Results call() throws Exception {
            Thread.sleep(100);
            return new Results();
        }
    }
}

使用-XX:MaxNewSize=64m进行测试时(这会限制临时内存空间的大小会增加)

100 threads
Peak Code Cache memory used:    386,880
Peak Code Cache memory reserved:    2,555,904
Peak PS Eden Space memory used: 41,280,984
Peak PS Eden Space memory reserved: 50,331,648
Peak PS Survivor Space memory used: 0
Peak PS Survivor Space memory reserved: 8,388,608
Peak PS Old Gen memory used:    0
Peak PS Old Gen memory reserved:    192,675,840
Peak PS Perm Gen memory used:   3,719,616
Peak PS Perm Gen memory reserved:   21,757,952
Total thread CPU time   20000000
Total thread user time  20000000
Total started thread count  105
Current Thread Count    93
Peak Thread Count   105
Daemon Thread Count 92

5100 threads
Peak Code Cache memory used:    425,728
Peak Code Cache memory reserved:    2,555,904
Peak PS Eden Space memory used: 59,244,544
Peak PS Eden Space memory reserved: 59,244,544
Peak PS Survivor Space memory used: 2,949,152
Peak PS Survivor Space memory reserved: 8,388,608
Peak PS Old Gen memory used:    3,076,400
Peak PS Old Gen memory reserved:    192,675,840
Peak PS Perm Gen memory used:   3,787,096
Peak PS Perm Gen memory reserved:   21,757,952
Total thread CPU time   810000000
Total thread user time  150000000
Total started thread count  5105
Current Thread Count    5105
Peak Thread Count   5105
Daemon Thread Count 5104

主要增加的是旧版本使用的增加量~3 MB或每个线程约6 KB。并且每个线程使用的CPU为956 ms或大约0.2 ms。


在第一个示例中,您创建了一个线程,在第二个示例中,您创建了1000个。

您正在执行的输出似乎是大多数工作,并且您在第二种情况下的输出比第一种情况多得多。

您需要确保您的测试和监控远比您想要监控/测量的重量轻得多。

答案 1 :(得分:2)

每个线程占用堆栈内存,从256K到1M。您可以手动设置堆栈大小,但将其设置为低于128K是危险的。因此,如果你有2G内存并且可以花费1/2用于线程,那么你将拥有不超过8K的线程。 如果你没问题,可以使用普通的多线程(每个Runnable都有自己的堆栈)。 如果您不愿意或不能为每个Runnable花费如此多的内存,请使用Executor。将线程池大小设置为处理器数(Runtime.availableProcessors()),或者多次。 出现的主要问题是,你无法在runnable中使用Thread.sleep()或以其他方式阻塞线程(比如等待用户响应),因为这样的阻塞有效地排除了线程的服务。因此,如果使用有限大小的线程池,则会发生所谓的“线程饥饿”,这实际上是一个死锁。如果您的线程池大小不限,那么您将恢复正常的多线程并很快耗尽内存。

解决方法是使用异步操作,即使用回调设置一些请求,然后退出run()方法。然后回调应该用Executor.execute(Runnable)开始执行一些Runnable对象(可能是相同的),或者它可以执行runnable.run()本身的方法。

异步输入输出操作现在出现在Java 7(nio2)中,但我无法使其服务超过数百个网络连接。 对于服务网络连接,可以使用异步网络库(例如Apache Netty)。

组织回调和执行runnable可能需要复杂的同步。为了让生活更轻松,请考虑使用Actor模型(http://en.wikipedia.org/wiki/Actor_model),其中Actor是每次输入消息到达时执行的Runnable。存在许多Java Actor库(例如https://github.com/rfqu/df4j)。