java线程池详解

关于线程和线程池的学习,我们可以从以下几个方面入手:

第一,什么是线程,线程和进程的区别是什么

第二,线程中的基本概念,线程的生命周期

第三,单线程和多线程

第四,为什么要使用线程池

第五,线程池的原理解析

第六,常见的几种线程池的特点以及各自的应用场景

 

第一,什么是线程,线程和进程的区别是什么?

线程,程序执行流的最小执行单位,是行程中的实际运作单位,经常容易和进程这个概念混淆。那么,线程和进程究竟有什么区别呢?首先,进程是一个动态的过程,是一个活动的实体。简单来说,一个应用程序的运行就可以被看做是一个进程,而线程,是运行中的实际的任务执行者。可以说,进程中包含了多个可以同时运行的线程。
 

第二,线程中的基本概念,线程的生命周期

线程的生命周期,线程的生命周期可以利用以下的图解来更好的理解:

java线程池详解_第1张图片

第一步,是用new Thread()的方法新建一个线程,在线程创建完成之后,线程就进入了就绪(Runnable)状态,此时创建出来的线程进入抢占CPU资源的状态,当线程抢到了CPU的执行权之后,线程就进入了运行状态(Running),当该线程的任务执行完成之后或者是非常态的调用的stop()方法之后,线程就进入了死亡状态。而我们在图解中可以看出,线程还具有一个则色的过程,这是怎么回事呢?当面对以下几种情况的时候,容易造成线程阻塞,第一种,当线程主动调用了sleep()方法时,线程会进入则阻塞状态,除此之外,当线程中主动调用了阻塞时的IO方法时,这个方法有一个返回参数,当参数返回之前,线程也会进入阻塞状态,还有一种情况,当线程进入正在等待某个通知时,会进入阻塞状态。那么,为什么会有阻塞状态出现呢?我们都知道,CPU的资源是十分宝贵的,所以,当线程正在进行某种不确定时长的任务时,Java就会收回CPU的执行权,从而合理应用CPU的资源。我们根据图可以看出,线程在阻塞过程结束之后,会重新进入就绪状态,重新抢夺CPU资源。这时候,我们可能会产生一个疑问,如何跳出阻塞过程呢?又以上几种可能造成线程阻塞的情况来看,都是存在一个时间限制的,当sleep()方法的睡眠时长过去后,线程就自动跳出了阻塞状态,第二种则是在返回了一个参数之后,在获取到了等待的通知时,就自动跳出了线程的阻塞过程
 

第三,单线程和多线程

单线程,顾名思义即是只有一条线程在执行任务,这种情况在我们日常的工作学习中很少遇到,所以我们只是简单做一下了解

多线程,创建多条线程同时执行任务,这种方式在我们的日常生活中比较常见。但是,在多线程的使用过程中,还有许多需要我们了解的概念。比如,在理解上并行和并发的区别,以及在实际应用的过程中多线程的安全问题,对此,我们需要进行详细的了解。

并行和并发:在我们看来,都是可以同时执行多种任务,那么,到底他们二者有什么区别呢?

并发,从宏观方面来说,并发就是同时进行多种时间,实际上,这几种时间,并不是同时进行的,而是交替进行的,而由于CPU的运算速度非常的快,会造成我们的一种错觉,就是在同一时间内进行了多种事情

而并行,则是真正意义上的同时进行多种事情。这种只可以在多核CPU的基础下完成。

还有就是多线程的安全问题?为什么会造成多线程的安全问题呢?我们可以想象一下,如果多个线程同时执行一个任务,name意味着他们共享同一种资源,由于线程CPU的资源不一定可以被谁抢占到,这是,第一条线程先抢占到CPU资源,他刚刚进行了第一次操作,而此时第二条线程抢占到了CPU的资源,name,共享资源还来不及发生变化,就同时有两条数据使用了同一条资源,具体请参考多线程买票问题。这个问题我们应该如何解决那?

  有造成问题的原因我们可以看出,这个问题主要的矛盾在于,CPU的使用权抢占和资源的共享发生了冲突,解决时,我们只需要让一条线程战歌了CPU的资源时,阻止第二条线程同时抢占CPU的执行权,在代码中,我们只需要在方法中使用同步代码块即可。在这里,同步代码块不多进行赘述,可以自行了解。

 

第四,为什么要使用线程池

1. 创建/销毁线程伴随着系统开销,过于频繁的创建/销毁线程,会很大程度上影响处理效率,例如:

记创建线程消耗时间 T1,执行任务消耗时间 T2,销毁线程消耗时间 T3

如果 T1+T3>T2,那么是不是说开启一个线程来执行这个任务太不划算了!

正好,线程池缓存线程,可用已有的闲置线程来执行新任务,避免了 T1+T3 带来的系统开销

 

2. 线程并发数量过多,抢占系统资源从而导致阻塞

我们知道线程能共享系统资源,如果同时执行的线程过多,就有可能导致系统资源不足而产生阻塞的情况

运用线程池能有效的控制线程最大并发数,避免以上的问题

 

3. 对线程进行一些简单的管理

比如:延时执行、定时循环执行的策略等

运用线程池都能进行很好的实现

 

第五,线程池的原理解析和使用

public ThreadPoolExecutor(int corePoolSize,  
                              int maximumPoolSize,  
                              long keepAliveTime,  
                              TimeUnit unit,  
                              BlockingQueue workQueue,  
                              ThreadFactory threadFactory,  
                              RejectedExecutionHandler handler)

 

这是其中最重要的一个构造方法,这个方法决定了创建出来的线程池的各种属性,下面依靠一张图来更好的理解线程池和这几个参数:

java线程池详解_第2张图片

corePoolSize:线程池核心线程数量

maximumPoolSize:线程池最大线程数量

keepAliverTime:当活跃线程数大于核心线程数时,空闲的多余线程最大存活时间

unit:存活时间的单位

workQueue:存放任务的队列

handler:超出线程范围和队列容量的任务的处理程序

threadFactory - 执行程序创建新线程时使用的工厂。

 

核心线程:

线程池新建线程的时候,如果当前线程总数小于 corePoolSize,则新建的是核心线程,如果超过 corePoolSize,则新建的是非核心线程

核心线程默认情况下会一直存活在线程池中,即使这个核心线程啥也不干(闲置状态)。

如果指定 ThreadPoolExecutor 的 allowCoreThreadTimeOut 这个属性为 true,那么核心线程如果不干活(闲置状态)的话,超过一定时间(时长下面参数决定),就会被销毁掉

很好理解吧,正常情况下你不干活我也养你,因为我总有用到你的时候,但有时候特殊情况(比如我自己都养不起了),那你不干活我就要把你干掉了

 

  • TimeUnit unit

keepAliveTime 的单位,TimeUnit 是一个枚举类型,其包括:

NANOSECONDS :1微毫秒 = 1微秒 / 1000

MICROSECONDS :1微秒 = 1毫秒 / 1000

MILLISECONDS :1毫秒 = 1秒 /1000

SECONDS :

MINUTES :

HOURS :小时

DAYS : 

 

  • BlockingQueue workQueue

该线程池中的任务队列:维护着等待执行的Runnable对象

当所有的核心线程都在干活时,新添加的任务会被添加到这个队列中等待处理,如果队列满了,则新建非核心线程执行任务

常用的 workQueue 类型:

SynchronousQueue:这个队列接收到任务的时候,会直接提交给线程处理,而不保留它,如果所有线程都在工作怎么办?那就新建一个线程来处理这个任务!所以为了保证不出现<线程数达到了 maximumPoolSize 而不能新建线程>的错误,使用这个类型队列的时候,maximumPoolSize 一般指定成 Integer.MAX_VALUE,即无限大

LinkedBlockingQueue:这个队列接收到任务的时候,如果当前线程数小于核心线程数,则新建线程(核心线程)处理任务;如果当前线程数等于核心线程数,则进入队列等待。由于这个队列没有最大值限制,即所有超过核心线程数的任务都将被添加到队列中,这也就导致了 maximumPoolSize 的设定失效,因为总线程数永远不会超过 corePoolSize

ArrayBlockingQueue:可以限定队列的长度,接收到任务的时候,如果没有达到 corePoolSize 的值,则新建线程(核心线程)执行任务,如果达到了,则入队等候,如果队列已满,则新建线程(非核心线程)执行任务,又如果总线程数到了 maximumPoolSize,并且队列也满了,则发生错误

DelayQueue:队列内元素必须实现 Delayed 接口,这就意味着你传进去的任务必须先实现 Delayed 接口。这个队列接收到任务时,首先先入队,只有达到了指定的延时时间,才会执行任务

 

向ThreadPoolExecutor添加任务

那说了这么多,你可能有疑惑,我知道new一个ThreadPoolExecutor,大概知道各个参数是干嘛的,可是我new完了,怎么向线程池提交一个要执行的任务啊?

通过ThreadPoolExecutor.execute(Runnable command)方法即可向线程池内添加一个任务

 

ThreadPoolExecutor的策略

上面介绍参数的时候其实已经说到了ThreadPoolExecutor执行的策略,这里给总结一下,当一个任务被添加进线程池时:

  1. 线程数量未达到corePoolSize,则新建一个线程(核心线程)执行任务
  2. 线程数量达到了corePools,则将任务移入队列等待
  3. 队列已满,新建线程(非核心线程)执行任务
  4. 队列已满,总线程数又达到了maximumPoolSize,就会由上面那位星期天(RejectedExecutionHandler)抛出异常

 

线程池的执行流程又是怎样的呢?

java线程池详解_第3张图片

有图我们可以看出,任务进来时,首先执行判断,判断核心线程是否处于空闲状态,如果不是,核心线程就先就执行任务,如果核心线程已满,则判断任务队列是否有地方存放该任务,若果有,就将任务保存在任务队列中,等待执行,如果满了,在判断最大可容纳的线程数,如果没有超出这个数量,就开创非核心线程执行任务,如果超出了,就调用handler实现拒绝策略。

handler的拒绝策略:

有四种:第一种AbortPolicy:不执行新任务,直接抛出异常,提示线程池已满

             第二种DisCardPolicy:不执行新任务,也不抛出异常

             第三种DisCardOldSetPolicy:将消息队列中的第一个任务替换为当前新进来的任务执行

             第四种CallerRunsPolicy:直接调用execute来执行当前任务
 

第六,常见的几种线程池的特点以及各自的应用场景:

CachedThreadPool:(推荐使用)可缓存的线程池,该线程池中没有核心线程,非核心线程的数量为Integer.max_value,就是无限大,当有需要时创建线程来执行任务,没有需要时回收线程,适用于耗时少,任务量大的情况。

SecudleThreadPool:周期性执行任务的线程池,按照某种特定的计划执行线程中的任务,有核心线程,但也有非核心线程,非核心线程的大小也为无限大。适用于执行周期性的任务。

SingleThreadPool:只有一条线程来执行任务,适用于有顺序的任务的应用场景。

FixedThreadPool:定长的线程池,有核心线程,核心线程的即为最大的线程数量,没有非核心线程
 

1,SingleThreadPool 使用案例

package com.test.threadpool;

public class RunThread extends Thread {

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"正在执行.....");
    }
}
package com.test.threadpool;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class singleThreadExecutorTest {

    public static void main(String[] args){
        ExecutorService pool  = Executors.newSingleThreadExecutor();

        Thread t1=new RunThread();
        Thread t2=new RunThread();
        Thread t3=new RunThread();
        Thread t4=new RunThread();
        Thread t5=new RunThread();

        t1.setName("t1");
        t2.setName("t2");
        t3.setName("t3");
        t4.setName("t4");
        t5.setName("t5");

        pool.execute(t1);
        pool.execute(t2);
        pool.execute(t3);
        pool.execute(t4);
        pool.execute(t5);

        pool.shutdown();

    }

}

java线程池详解_第4张图片

2,newFixedThreadPool 使用案例

package com.test.threadpool;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class singleThreadExecutorTest {

    public static void main(String[] args){
        ExecutorService pool  = Executors.newFixedThreadPool(2);

        Thread t1=new RunThread();
        Thread t2=new RunThread();
        Thread t3=new RunThread();
        Thread t4=new RunThread();
        Thread t5=new RunThread();

        t1.setName("t1");
        t2.setName("t2");
        t3.setName("t3");
        t4.setName("t4");
        t5.setName("t5");

        pool.execute(t1);
        pool.execute(t2);
        pool.execute(t3);
        pool.execute(t4);
        pool.execute(t5);

        pool.shutdown();

    }
}

java线程池详解_第5张图片

3,newCachedThreadPool实例

package com.test.threadpool;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class singleThreadExecutorTest {

    public static void main(String[] args){
        ExecutorService pool  = Executors.newCachedThreadPool();

        Thread t1=new RunThread();
        Thread t2=new RunThread();
        Thread t3=new RunThread();
        Thread t4=new RunThread();
        Thread t5=new RunThread();

        t1.setName("t1");
        t2.setName("t2");
        t3.setName("t3");
        t4.setName("t4");
        t5.setName("t5");

        pool.execute(t1);
        pool.execute(t2);
        pool.execute(t3);
        pool.execute(t4);
        pool.execute(t5);

        pool.shutdown();

    }
}

java线程池详解_第6张图片

 

4,ScheduledThreadPoolExecutor实例

public class scheduledThreadExecutorTest{
     

    public static void main(String[] args) {
        // TODO Auto-generated method stub
 
       ScheduledThreadPoolExecutor exec =new ScheduledThreadPoolExecutor(1);
       exec.scheduleAtFixedRate(new Runnable(){//每隔一段时间就触发异常

        @Override
        public void run() {
            // TODO Auto-generated method stub
            //throw new RuntimeException();
            System.out.println("===================");
            
        }}, 1000, 5000, TimeUnit.MILLISECONDS);  
       
       exec.scheduleAtFixedRate(new Runnable(){//每隔一段时间打印系统时间,证明两者是互不影响的

        @Override
        public void run() {
            // TODO Auto-generated method stub
            System.out.println(System.nanoTime());
            
        }}, 1000, 2000, TimeUnit.MILLISECONDS);
        

    }

}

结果:

===================
23119318857491
23121319071841
23123319007891
===================
23125318176937
23127318190359
===================
23129318176148
23131318344312
23133318465896
===================
23135319645812

 

参考:

https://www.jianshu.com/p/210eab345423

 

 

你可能感兴趣的:(java)