• Tags
  •         
  • www.breakyizhan.com
  •    

    Java中的线程池的背景

    服务器程序(如数据库和Web服务器)重复执行来自多个客户端的请求,这些请求面向处理大量短任务。构建服务器应用程序的方法是每次请求到达时创建一个新线程,并在新创建的线程中为新请求提供服务。虽然这种方法似乎很容易实现,但它具有明显的缺点。为每个请求创建新线程的服务器将花费更多时间并在创建和销毁线程时消耗更多系统资源,而不是处理实际请求。

    由于活动线程消耗系统资源,因此JVM同时创建过多线程会导致系统内存不足。这需要限制正在创建的线程数。

    什么是Java中的线程池ThreadPool?

    线程池重用先前创建的线程来执行当前任务,并提供线程周期开销和资源抖动问题的解决方案。由于线程在请求​​到达时已经存在,因此消除了线程创建引入的延迟,使应用程序更具响应性。

      • Java提供了Executor框架,它以Executor接口,子接口ExecutorService和类ThreadPoolExecutor为中心,实现了这两个接口。通过使用执行程序,只需实现Runnable对象并将它们发送到执行程序即可执行。
      • 它们允许您利用线程,但专注于您希望线程执行的任务,而不是线程机制。
      • 要使用线程池,我们首先创建一个ExecutorService对象,并将一组任务传递给它。ThreadPoolExecutor类允许设置核心和最大池大小。由特定线程运行的runnables按顺序执行。

        Java中的线程池

        线程池初始化,大小= 3个线程。任务队列= 5个可运行对象

    执行者线程池方法

    方法                         说明
    newFixedThreadPool(int)创建固定大小的线程池。
    newCachedThreadPool()创建一个创建新的线程池 
                                      线程根据需要,但将重复使用 
                                      在可用时构造线程
    newSingleThreadExecutor()创建一个线程。 
    

    在固定线程池的情况下,如果执行程序当前正在运行所有线程,则挂起的任务将被放入队列中,并在线程变为空闲时执行。

    Java中的线程池示例

    在下面的教程中,我们将看一下线程池执行器的基本示例 - FixedThreadPool。

    要遵循的步骤

    1.创建要执行的任务(Runnable Object)
    2.使用Executors创建Executor Pool
    3.将任务传递给Executor Pool
    4.关闭执行程序池
    
    // Java program to illustrate
    // ThreadPool
    import java.text.SimpleDateFormat;
    import java.util.Date;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    // Task class to be executed (Step 1)
    class Task implements Runnable  
    {
        private String name;
        
        public Task(String s)
        {
            name = s;
        }
        
        // Prints task name and sleeps for 1s
        // This Whole process is repeated 5 times
        public void run()
        {
            try
            {
                for (int i = 0; i<=5; i++)
                {
                    if (i==0)
                    {
                        Date d = new Date();
                        SimpleDateFormat ft = new SimpleDateFormat("hh:mm:ss");
                        System.out.println("Initialization Time for"
                                + " task name - "+ name +" = " +ft.format(d));  
                        //prints the initialization time for every task
                    }
                    else
                    {
                        Date d = new Date();
                        SimpleDateFormat ft = new SimpleDateFormat("hh:mm:ss");
                        System.out.println("Executing Time for task name - "+
                                name +" = " +ft.format(d));  
                        // prints the execution time for every task
                    }
                    Thread.sleep(1000);
                }
                System.out.println(name+" complete");
            }
            
            catch(InterruptedException e)
            {
                e.printStackTrace();
            }
        }
    }
    public class Test
    {
         // Maximum number of threads in thread pool
        static final int MAX_T = 3;            
        public static void main(String[] args)
        {
            // creates five tasks
            Runnable r1 = new Task("task 1");
            Runnable r2 = new Task("task 2");
            Runnable r3 = new Task("task 3");
            Runnable r4 = new Task("task 4");
            Runnable r5 = new Task("task 5");     
            
            // creates a thread pool with MAX_T no. of
            // threads as the fixed pool size(Step 2)
            ExecutorService pool = Executors.newFixedThreadPool(MAX_T); 
           
            // passes the Task objects to the pool to execute (Step 3)
            pool.execute(r1);
            pool.execute(r2);
            pool.execute(r3);
            pool.execute(r4);
            pool.execute(r5);
            
            // pool shutdown ( Step 4)
            pool.shutdown();   
        }
    }
    

    执行

    Output:
    Initialization Time for task name - task 2 = 02:32:56
    Initialization Time for task name - task 1 = 02:32:56
    Initialization Time for task name - task 3 = 02:32:56
    Executing Time for task name - task 1 = 02:32:57
    Executing Time for task name - task 2 = 02:32:57
    Executing Time for task name - task 3 = 02:32:57
    Executing Time for task name - task 1 = 02:32:58
    Executing Time for task name - task 2 = 02:32:58
    Executing Time for task name - task 3 = 02:32:58
    Executing Time for task name - task 1 = 02:32:59
    Executing Time for task name - task 2 = 02:32:59
    Executing Time for task name - task 3 = 02:32:59
    Executing Time for task name - task 1 = 02:33:00
    Executing Time for task name - task 3 = 02:33:00
    Executing Time for task name - task 2 = 02:33:00
    Executing Time for task name - task 2 = 02:33:01
    Executing Time for task name - task 1 = 02:33:01
    Executing Time for task name - task 3 = 02:33:01
    task 2 complete
    task 1 complete
    task 3 complete
    Initialization Time for task name - task 5 = 02:33:02
    Initialization Time for task name - task 4 = 02:33:02
    Executing Time for task name - task 4 = 02:33:03
    Executing Time for task name - task 5 = 02:33:03
    Executing Time for task name - task 5 = 02:33:04
    Executing Time for task name - task 4 = 02:33:04
    Executing Time for task name - task 4 = 02:33:05
    Executing Time for task name - task 5 = 02:33:05
    Executing Time for task name - task 5 = 02:33:06
    Executing Time for task name - task 4 = 02:33:06
    Executing Time for task name - task 5 = 02:33:07
    Executing Time for task name - task 4 = 02:33:07
    task 5 complete
    task 4 complete

    如在程序的执行中所见,仅当池中的线程变为空闲时才执行任务4或任务5。在此之前,额外的任务被放入队列中。

    Java中的线程池

    线程池执行前三个任务

    Java中的线程池

    线程池执行任务4和5

    使用线程池的风险

          1. 死锁:虽然死锁可能发生在任何多线程程序中,但线程池引入了另一种死锁情况,其中所有正在执行的线程都在等待队列中等待的阻塞线程的结果,因为执行的线程不可用。
          2. 线程泄漏:如果从池中删除线程以执行任务但在任务完成时未返回线程,则会发生线程泄漏。例如,如果线程抛出异常并且池类没有捕获此异常,则线程将简单地退出,从而将线程池的大小减小一。如果重复多次,则池最终将变为空,并且没有线程可用于执行其他请求。
          3. 资源抖动:如果线程池大小非常大,则在线程之间的上下文切换中浪费时间。如上所述,拥有比最佳数量更多的线程可能导致饥饿问题导致资源颠簸。

    重点

          1. 不要将同时等待其他任务结果的任务排队。这可能导致如上所述的死锁情况。
          2. 使用线程进行长期操作时要小心。它可能导致线程永远等待,最终导致资源泄漏。
          3. 线程池必须在结束时明确结束。如果没有这样做,那么程序继续执行并且永远不会结束。在池上调用shutdown()以结束执行程序。如果在关机后尝试向执行程序发送另一个任务,则会抛出RejectedExecutionException。
          4. 需要了解有效调整线程池的任务。如果任务形成鲜明对比,那么为不同类型的任务使用不同的线程池是有意义的,以便正确地调整它们。

    调整线程池

        • 线程池的最佳大小取决于可用处理器的数量和任务的性质。在N处理器系统上,只有一个计算类型进程的队列,最大线程池大小为N或N + 1将达到最大效率。但是任务可能等待I / O,在这种情况下我们会考虑比率等待时间(W)和服务时间(S)的请求; 导致最大池大小为N *(1+ W / S)以获得最大效率。

    线程池是组织服务器应用程序的有用工具。它在概念上非常简单,但在实现和使用时需要注意几个问题,例如死锁,资源颠簸。使用执行程序服务可以更容易地实现。

     
    转载请保留页面地址:https://www.breakyizhan.com/java/4965.html