java多线程(主要针对线程池讲解)

181 阅读7分钟

java多线程(主要针对线程池讲解)

1、创建线程的4种方式

第一种: 通过继承Thread类创建线程。(占用继承位,不推荐)

第二种: 通过实现Runnable接口创建线程。 (能更好的做到并发完成同一个任务, 因为访问的是同一个target, 实现了共享数据)

以上两种重写run()方法没有返回值, 所以这两种方式不能获取异步执行的结果。

第三种: 使用Callable接口 和 FutureTask类创建线程。(线程处于并发状态, 默认异步执行)

第四种: 通过线程池创建线程。(在高并发场景中, 频繁创建线程是非常消耗资源的, 通过线程池创建线程可以对已经创建好的线程进行复用)

2、创建线程池的7种方式(6种Executors,1种ThreadPoolExecutor)

1、Executors.newFixedThreadPool:创建一个固定大小的线程池,可控制并发的线程数,超出的线程会在队列中等待;

2、Executors.newCachedThreadPool:创建一个可缓存的线程池,若线程数超过处理所需,缓存一段时间后会回收,若线程数不够,则新建线程;

3、Executors.newSingleThreadExecutor:创建单个线程数的线程池,它可以保证先进先出的执行顺序;

4、Executors.newScheduledThreadPool:创建一个可以执行延迟任务的线程池;

5、Executors.newSingleThreadScheduledExecutor:创建一个单线程的可以执行延迟任务的线程池;

6、Executors.newWorkStealingPool:创建一个抢占式执行的线程池(任务执行顺序不确定)【JDK 1.8 添加】。

7、ThreadPoolExecutor:最原始的创建线程池的方式,它包含了 7 个参数可供设置,后面会详细讲。

3、原始线程池的7大参数

(1)corePoolSize:线程池中的常驻核心线程数。

CPU密集型:
    核心线程数 = CPU核数 + 1
IO密集型:
    核心线程数 = CPU核数 * 2
    核心线程数 = CPU核数 / (1-阻塞系数)

(2)maximumPoolSize:线程池能够容纳同时执行的最大线程数,此值大于等于1。

maximumPoolSize > corePoolSize + workQueue

(3)keepAliveTime:多余的空闲线程存活时间,当空间时间达到keepAliveTime值时,多余的线程会被销毁直到只剩下corePoolSize个线程为止。

keepAliveTime > 线程中任务的执行时间

(4)unit:keepAliveTime的单位。

TimeUnit.SECOND,
TimeUnit.MINUTES,
TimeUnit.HOURS,
TimeUnit.DAYS,
TimeUnit.MICROSECONDS,
TimeUnit.MILLISECONDS,
TimeUnit.NANOSECONDS

(5)workQueue:任务队列,被提交但尚未被执行的任务。

1、直接提交队列:设置为SynchronousQueue队列,SynchronousQueue是一个特殊的BlockingQueue,它没有容量,每执行一个插入操作就会阻塞,需要再执行一个删除操    作才会被唤醒,反之每一个删除操作也都要等待对应的插入操作。
2、有界任务队列:使用ArrayBlockingQueue有界任务队列,若有新的任务需要执行时,线程池会创建新的线程,直到创建的线程数量达到corePoolSize时,则会将新的任务     加入到等待队列中。若等待队列已满,即超过ArrayBlockingQueue初始化的容量,则继续创建线程,直到线程数量达到maximumPoolSize设置的最大线程数量,若大于      maximumPoolSize,则执行拒绝策略。在这种情况下,线程数量的上限与有界任务队列的状态有直接关系,如果有界队列初始容量较大或者没有达到超负荷的状态,线程数将    一直维持在corePoolSize以下,反之当任务队列已满时,则会以maximumPoolSize为最大线程数上限
3、无界任务队列:使用无界任务队列,线程池的任务队列可以无限制的添加新的任务,而线程池创建的最大线程数量就是你corePoolSize设置的数量,也就是说在这种情况下      maximumPoolSize这个参数是无效的,哪怕你的任务队列中缓存了很多未执行的任务,当线程池的线程数达到corePoolSize后,就不会再增加了;若后续有新的任务加入,    则直接进入队列等待,当使用这种任务队列模式时,一定要注意你任务提交与处理之间的协调与控制,不然会出现队列中的任务由于无法及时处理导致一直增长,直到最后资源    耗尽的问题。
4、优先任务队列:除了第一个任务直接创建线程执行外,其他的任务都被放入了优先任务队列,按优先级进行了重新排列执行,且线程池的线程数一直为corePoolSize,也就是    只有一个。通过运行的代码我们可以看出PriorityBlockingQueue它其实是一个特殊的无界队列,它其中无论添加了多少个任务,线程池创建的线程数也不会超过            corePoolSize的数量,只不过其他队列一般是按照先进先出的规则处理任务,而PriorityBlockingQueue队列可以自定义规则根据任务的优先级顺序先后执行。

(6)threadFactory:表示生成线程池中工作线程的线程工厂,用户创建新线程,一般用默认即可。

1、 Executors.defaultThreadFactory()
   返回用于创建新线程的默认线程工厂。此工厂在同一线程组中创建执行器使用的所有新线程。
2、 Executors.privilegedThreadFactory()
   返回一个线程工厂,用于创建与当前线程具有相同权限的新线程。

(7)handler:拒绝策略,表示当线程队列满了并且工作线程大于等于线程池的最大显示数(maxnumPoolSize)时如何来拒绝请求执行的runnable的策略。

1、AbortPolicy(默认):丢弃任务并抛出 RejectedExecutionException 异常。
   这是线程池默认的拒绝策略,在任务不能再提交的时候,抛出异常,及时反馈程序运行状态。如果是比较关键的业务,推荐使用此拒绝策略,这样子在系统不能承载更大的并发    量的时候,能够及时的通过异常发现。
2、CallerRunsPolicy:由调用线程处理该任务。
   使用此策略,可能会使我们无法发现系统的异常状态。建议是一些无关紧要的业务采用此策略。例如,某些视频网站统计视频的播放量就是采用的这种拒绝策略。
3、DiscardPolicy:丢弃任务,但是不抛出异常。可以配合这种模式进行自定义的处理方式。
   此拒绝策略,是一种喜新厌旧的拒绝策略。是否要采用此种拒绝策略,还得根据实际业务是否允许丢弃老任务来认真衡量。
4、DiscardOldestPolicy:丢弃队列最早的未处理任务,然后重新尝试执行任务。
   此拒绝策略由调用线程(提交任务的线程)直接执行被丢弃的任务的。

4、线程池工作流程

1、有任务的时候会先去使用核心的线程数corePoolSize去执行

2、当线程满了(有任务但是线程被使用完)不会立即扩容,而是放到阻塞队列中,当阻塞队列满了之后才会继续创建线程。

3、如果队列满了,会继续尝试创建线程,直至线程数达到最大线程数则会执行拒绝策略。

4、当线程数大于核心线程数事,超过KeepAliveTime(闲置时间),线程会被回收,最终会保持corePoolSize个线程。

5、代码演示

1、不需要返回值(execute)

public class VideoMonitorServer {
    private static ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(8, 30, 10, TimeUnit.SECONDS,
            new ArrayBlockingQueue<Runnable>(16), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
​
    /**
     * 日志
     */
    private static final Logger LOG = getLogger(VideoMonitorServer.class);
​
    public List<Map> getRtspUriV2(String uri, String json) {
         List<Map> list = new ArrayList<>();
         JSONArray cameraInfos = JSONUtil.parseObj(json).getJSONArray("cameraInfos");
         CountDownLatch countDownLatch = new CountDownLatch(cameraInfos.size());
         for (Object o : cameraInfos) {
            //在for循环中开启线程去执行
            threadPoolExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    //这里面不能出现成员变量,会被加锁,否则下个线程任务开启时获取成员变量可能还存在于上个线程中
                    ..............
                }
            });
            list.add(cameraInfo);
        }
        countDownLatch.countDown();   //挂起执行完的线程,等待所有线程执行完毕返回。
        return list;
    }
}

2、需要返回值(sumbit)

public class VideoMonitorServer {
    private static ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(8, 30, 10, TimeUnit.SECONDS,
            new ArrayBlockingQueue<Runnable>(16), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
​
    /**
     * 日志
     */
    private static final Logger LOG = getLogger(VideoMonitorServer.class);
​
    public List<Map> getRtspUriV2(String uri, String json) {
        List<Map> list = new ArrayList<>();
        JSONArray cameraInfos = JSONUtil.parseObj(json).getJSONArray("cameraInfos");
        CountDownLatch countDownLatch = new CountDownLatch(cameraInfos.size());
        for (Object o : cameraInfos) {
             //在for循环中开启线程去执行
            JSONObject o1 = (JSONObject) o;
            JSONObject cameraInfo = o1.getJSONObject("cameraInfo");
            Future<JSONObject> submit = threadPoolExecutor.submit(
                    new Callable<JSONObject>() {
                         @Override
                         public JSONObject call() throws Exception {
                                //这里面不能出现成员变量,会被加锁,否则下个线程任务开启时获取成员变量可能还存在于上个线程中
                                ...........
                             return cameraInfo;
                         }
                    }
            );
            try {
                list.add(submit.get());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
            countDownLatch.countDown();       //挂起执行完的线程,等待所有线程执行完毕返回。
        }
        return list;
    }
}