自定义线程

这个非常重要,以上学习都是为了认知线程,而自定义线程才是真正项目可用的方式,在N多烈士总结的经验。

IDEA如果使用了阿里的开发规范插件,也可以看到,自定义线程是阿里开发规范要求的。
在这里插入图片描述

先来个demo

package com.wht.demo.thread3;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import static java.lang.Thread.sleep;

/**
 * @author JDIT
 */
public class Test {

  public static void main(String[] args) {
    ThreadPoolExecutor pool =
        new

        ThreadPoolExecutor(2, 100, 5000, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(10),
            new

                ThreadFactory() {

                  @Override
                  public Thread newThread(Runnable r) {
                    System.out.println(">>>>>>>>>>>>>>>>开始创建线程");
                    Thread th = new Thread(r);
                    return th;
                  }


                }, new ThreadPoolExecutor.CallerRunsPolicy()) {

          @Override
          protected void beforeExecute(Thread t, Runnable r) {
            System.out.println(">>>>>>>>>>>准备执行:" + ((ThreadTask) r).getTaskName());
          }


          @Override
          protected void afterExecute(Runnable r, Throwable t) {
            System.out.println(">>>>>>>>>>>>执行完毕:" + ((ThreadTask) r).getTaskName());
          }


          @Override
          protected void terminated() {
            System.out.println("线程池退出");
          }


        };

    int i=0;
    for (; ; ) {
      ++i;
      try {
        sleep(3000);
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
      System.out.println();
      System.out.println();
      System.out.println("执行完成线程数"+pool.getCompletedTaskCount());
      System.out.println("活动线程数"+pool.getActiveCount());
      System.out.println("队列中等待的线程数"+pool.getQueue().size());
      System.out.println("线程总数"+pool.getTaskCount());
      pool.execute(new ThreadTask("Task" + i));
    }

  }


}


package com.wht.demo.thread3;

import java.util.Random;

import static java.lang.Thread.sleep;

/**
 * @author JDIT
 */
public class ThreadTask implements Runnable {

  private String taskName;

  public ThreadTask(String name) {
    this.setTaskName(name);
  }


  @Override
  public void run() {
    //输出执行线程的名称
    try {
      sleep(new Random().nextInt(100000));
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    System.out.println("TaskName" + this.getTaskName() + "---ThreadName:" + Thread.currentThread().getName());
  }


  public String getTaskName() {
    return taskName;
  }


  public void setTaskName(String taskName) {
    this.taskName = taskName;
  }


}

比较完美,你可以知道较多细节,和控制较多资源,而非用JDK现成的Executors去创建,那样你的资源可能会死掉。

ThreadPoolExecutor构造函数

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

构造函数的参数含义如下:

  • corePoolSize:指定了线程池中的线程数量,它的数量决定了添加的任务是开辟新的线程去执行,还是放到workQueue任务队列中去;

  • maximumPoolSize:指定了线程池中的最大线程数量,这个参数会根据你使用的workQueue任务队列的类型,决定线程池会开辟的最大线程数量;

  • keepAliveTime:当线程池中空闲线程数量超过corePoolSize时,多余的线程会在多长时间内被销毁;

  • unit:keepAliveTime的单位 TimeUnit中的常量

  • workQueue:任务队列,被添加到线程池中,但尚未被执行的任务;它一般分为直接提交队列、有界任务队列、无界任务队列、优先任务队列几种;

  • threadFactory:线程工厂,用于创建线程,一般用默认即可;

  • handler:拒绝策略;当任务太多来不及处理时,如何拒绝任务;

workQueue任务队列

1、直接提交队列:

设置为SynchronousQueue队列,SynchronousQueue是一个特殊的BlockingQueue,目的确保存取的唯一性。它没有容量,每执行一个插入操作就会阻塞,需要再执行一个删除操作才会被唤醒,反之每一个删除操作也都要等待对应的插入操作。

  ThreadPoolExecutor pool = new

        ThreadPoolExecutor(2, 100, 5000, TimeUnit.MILLISECONDS, new SynchronousQueue<Runnable>(),
            new

                ThreadFactory() {

                  @Override
                  public Thread newThread(Runnable r) {
                    System.out.println(">>>>>>>>>>>>>>>>开始创建线程");
                    Thread th = new Thread(r);
                    return th;
                  }


                }, new ThreadPoolExecutor.CallerRunsPolicy()) {

          @Override
          protected void beforeExecute(Thread t, Runnable r) {
            System.out.println(">>>>>>>>>>>准备执行:" + ((ThreadTask) r).getTaskName());
          }


          @Override
          protected void afterExecute(Runnable r, Throwable t) {
            System.out.println(">>>>>>>>>>>>执行完毕:" + ((ThreadTask) r).getTaskName());
          }


          @Override
          protected void terminated() {
            System.out.println("线程池退出");
          }


        };

执行完成线程数1
活动线程数5
队列中等待的线程数0
线程总数6

可以看到,当任务队列为SynchronousQueue,创建的线程数大于maximumPoolSize时,它并没有报错,而是直接死在这里了,后续的任务没有再加载进来。
直到所有活动线程都执行完又继续进行执行。
根本原因是因为策略:new ThreadPoolExecutor.CallerRunsPolicy()

使用SynchronousQueue队列,提交的任务不会被保存,总是会马上提交执行。如果用于执行任务的线程数量小于maximumPoolSize,则尝试创建新的进程,如果达到maximumPoolSize设置的最大值,则根据你设置的handler执行拒绝策略。因此这种方式你提交的任务不会被缓存起来,而是会被马上执行,在这种情况下,你需要对你程序的并发量有个准确的评估,才能设置合适的maximumPoolSize数量,否则很容易就会执行拒绝策略;

2、有界的任务队列:

有界的任务队列可以使用ArrayBlockingQueue实现,如下所示

pool = new ThreadPoolExecutor(1, 100, 1000, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(10),Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());

使用ArrayBlockingQueue有界任务队列,若有新的任务需要执行时,线程池会创建新的线程,直到创建的线程数量达到corePoolSize时,则会将新的任务加入到等待队列中。若等待队列已满,即超过ArrayBlockingQueue初始化的容量,则继续创建线程,直到线程数量达到maximumPoolSize设置的最大线程数量,若大于maximumPoolSize,则执行拒绝策略。在这种情况下,线程数量的上限与有界任务队列的状态有直接关系,如果有界队列初始容量较大或者没有达到超负荷的状态,线程数将一直维持在corePoolSize以下,反之当任务队列已满时,则会以maximumPoolSize为最大线程数上限。

3、无界的任务队列:

无任务队列可以使用LinkedBlockingQueue实现,如下所示

pool = new ThreadPoolExecutor(1, 100, 1000, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(),Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());

使用无界任务队列,线程池的任务队列可以无限制的添加新的任务,而线程池创建的最大线程数量就是你corePoolSize设置的数量,也就是说在这种情况下maximumPoolSize这个参数是无效的,哪怕你的任务队列中缓存了很多未执行的任务,当线程池的线程数达到corePoolSize后,就不会再增加了;若后续有新的任务加入,则直接进入队列等待.
注意:你任务提交与处理之间的协调与控制,不然会出现队列中的任务由于无法及时处理导致一直增长,直到最后资源耗尽的问题。

4、优先任务队列:

优先任务队列通过PriorityBlockingQueue实现,下面我们通过一个例子演示下

package com.wht.demo.thread3;

import sun.management.counter.Units;

import java.util.concurrent.*;

import static java.lang.Thread.sleep;

/**
 * @author JDIT
 */
public class Test {

  public static void main(String[] args) {
    ThreadPoolExecutor pool = new ThreadPoolExecutor(1, 200, 1000, TimeUnit.MILLISECONDS, new PriorityBlockingQueue<Runnable>(),Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
    int i = 0;
    for (; ; ) {
      ++i;
      try {
        sleep(1000);
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
      System.out.println();
      System.out.println();
      System.out.println("执行完成线程数" + pool.getCompletedTaskCount());
      System.out.println("活动线程数" + pool.getActiveCount());
      System.out.println("队列中等待的线程数" + pool.getQueue().size());
      System.out.println("线程总数" + pool.getTaskCount());
      pool.execute(new ThreadTask("Task" + i,i));
    }

  }


}


package com.wht.demo.thread3;

import java.util.Random;

import static java.lang.Thread.sleep;

/**
 * @author JDIT
 */
public class ThreadTask implements Runnable, Comparable<ThreadTask> {

  private int priority;
  private String taskName;

  public ThreadTask(String name, int priority) {
    this.setTaskName(name);
    this.priority = priority;
  }

  @Override
  public int compareTo(ThreadTask o) {
    return this.priority > o.priority ? -1 : 1;
  }

  public int getPriority() {
    return priority;
  }

  public void setPriority(int priority) {
    this.priority = priority;
  }

  @Override
  public void run() {
    System.out.println("priority:"+this.priority+",ThreadName:"+Thread.currentThread().getName());
    //输出执行线程的名称
    try {
      sleep(new Random().nextInt(10000));
    } catch (InterruptedException e) {
      e.printStackTrace();
    }

  }


  public String getTaskName() {
    return taskName;
  }


  public void setTaskName(String taskName) {
    this.taskName = taskName;
  }


}

大家可以看到除了第一个任务直接创建线程执行外,其他的任务都被放入了优先任务队列,按优先级进行了重新排列执行,且线程池的线程数一直为corePoolSize,也就是只有一个。

通过运行的代码我们可以看出PriorityBlockingQueue它其实是一个特殊的无界队列,它其中无论添加了多少个任务,线程池创建的线程数也不会超过corePoolSize的数量,只不过其他队列一般是按照先进先出的规则处理任务,而PriorityBlockingQueue队列可以自定义规则根据任务的优先级顺序先后执行。

拒绝策略

一般我们创建线程池时,为防止资源被耗尽,任务队列都会选择创建有界任务队列,但种模式下如果出现任务队列已满且线程池创建的线程数达到你设置的最大线程数时,这时就需要你指定ThreadPoolExecutor的RejectedExecutionHandler参数即合理的拒绝策略,来处理线程池"超载"的情况。ThreadPoolExecutor自带的拒绝策略如下:

1、AbortPolicy策略:

该策略会直接抛出异常,阻止系统正常工作;

2、CallerRunsPolicy策略:

如果线程池的线程数量达到上限,该策略会把任务队列中的任务放在调用者线程当中运行;

3、DiscardOledestPolicy策略:

该策略会丢弃任务队列中最老的一个任务,也就是当前任务队列中最先被添加进去的,马上要被执行的那个任务,并尝试再次提交;

4、DiscardPolicy策略:

该策略会默默丢弃无法处理的任务,不予任何处理。当然使用此策略,业务场景中需允许任务的丢失;

以上内置的策略均实现了RejectedExecutionHandler接口,当然你也可以自己扩展RejectedExecutionHandler接口,定义自己的拒绝策略.

ThreadFactory自定义线程创建

线程池中线程就是通过ThreadPoolExecutor中的ThreadFactory,线程工厂创建的。那么通过自定义ThreadFactory,可以按需要对线程池中创建的线程进行一些特殊的设置,如命名、优先级等,下面代码我们通过ThreadFactory对线程池中创建的线程进行记录与命名

public class ThreadPool {
    private static ExecutorService pool;
    public static void main( String[] args )
    {
        //自定义线程工厂
        pool = new ThreadPoolExecutor(2, 4, 1000, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(5),
                new ThreadFactory() {
            public Thread newThread(Runnable r) {
                System.out.println("线程"+r.hashCode()+"创建");
                //线程命名
                Thread th = new Thread(r,"threadPool"+r.hashCode());
                return th;
            }
        }, new ThreadPoolExecutor.CallerRunsPolicy());
          
        for(int i=0;i<10;i++) {
            pool.execute(new ThreadTask());
        }    
    }
}

public class ThreadTask implements Runnable{    
    public void run() {
        //输出执行线程的名称
        System.out.println("ThreadName:"+Thread.currentThread().getName());
    }
}

ThreadPoolExecutor扩展

ThreadPoolExecutor扩展主要是围绕beforeExecute()、afterExecute()和terminated()三个接口实现的,

1、beforeExecute:线程池中任务运行前执行

2、afterExecute:线程池中任务运行完毕后执行

3、terminated:线程池退出后执行

通过这三个接口我们可以监控每个任务的开始和结束时间,或者其他一些功能。下面我们可以通过代码实现一下

public class ThreadPool {
    private static ExecutorService pool;
    public static void main( String[] args ) throws InterruptedException
    {
        //实现自定义接口
        pool = new ThreadPoolExecutor(2, 4, 1000, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(5),
                new ThreadFactory() {
            public Thread newThread(Runnable r) {
                System.out.println("线程"+r.hashCode()+"创建");
                //线程命名
                Thread th = new Thread(r,"threadPool"+r.hashCode());
                return th;
            }
        }, new ThreadPoolExecutor.CallerRunsPolicy()) {
    
            protected void beforeExecute(Thread t,Runnable r) {
                System.out.println("准备执行:"+ ((ThreadTask)r).getTaskName());
            }
            
            protected void afterExecute(Runnable r,Throwable t) {
                System.out.println("执行完毕:"+((ThreadTask)r).getTaskName());
            }
            
            protected void terminated() {
                System.out.println("线程池退出");
            }
        };
          
        for(int i=0;i<10;i++) {
            pool.execute(new ThreadTask("Task"+i));
        }    
        pool.shutdown();
    }
}

public class ThreadTask implements Runnable{    
    private String taskName;
    public String getTaskName() {
        return taskName;
    }
    public void setTaskName(String taskName) {
        this.taskName = taskName;
    }
    public ThreadTask(String name) {
        this.setTaskName(name);
    }
    public void run() {
        //输出执行线程的名称
        System.out.println("TaskName"+this.getTaskName()+"---ThreadName:"+Thread.currentThread().getName());
    }
}

可以看到通过对beforeExecute()、afterExecute()和terminated()的实现,我们对线程池中线程的运行状态进行了监控,在其执行前后输出了相关打印信息。另外使用shutdown方法可以比较安全的关闭线程池, 当线程池调用该方法后,线程池中不再接受后续添加的任务。但是,此时线程池不会立刻退出,直到添加到线程池中的任务都已经处理完成,才会退出。

线程池线程数量

线程吃线程数量的设置没有一个明确的指标,根据实际情况,只要不是设置的偏大和偏小都问题不大,结合下面这个公式即可
/**
* Nthreads=CPU数量
* Ucpu=目标CPU的使用率,0<=Ucpu<=1
* W/C=任务等待时间与任务计算时间的比率
/
Nthreads = Ncpu
Ucpu*(1+W/C)

发布了156 篇原创文章 · 获赞 11 · 访问量 5360

猜你喜欢

转载自blog.csdn.net/weixin_38280568/article/details/103870717
今日推荐