线程与线程池详解

1 线程的执行机制

  • 线程分为用户线程 和 内核线程

    • 内核线程就是系统级别的线程,与cpu逻辑处理器数量对应的

    • 用户线程就是使用java代码创建的Thread对象

  • 用户线程必须与内核线程关联(映射),才能执行任务

  • 当用户线程多于内核线程时,内核线程就需要不停的上下文切换,使得多个用户线程都能得以执行

  • 上下文会影响性能,消耗资源。

  • 大量的创建用户线程,消耗用户线程,也会影响性能,消耗资源。

  • 所以我们希望,创建合适数量的线程,不要频繁的创建新线程和销毁线程,希望创建的线程可以反复利用

  • 我们就可以使用池化技术:线程池。

2 线程池的执行机制

  • 线程池是jdk1.5中 JUC提供的一个工具

  • 线程池的作用可以统一的创建,分配,管理,销毁线程。实现线程的复用。

3 应用线程池

3.1 拥有线程池对象

  • 有两种方式,可以拥有线程池对象

    1. 手动创建线程池对象,需要传递7个参数

      JUC提供了3个可以创建线程池的类,我们最常用的就是ThreadPoolExecutor

    2. 通过JUC提供的工具类,快速获得线程池对象 (别人帮我们创建)

      JUC提供了一个Executors工具类,该工具类提供了5个静态方法,可以快速获得对象

  • 线程池对象的子父类结构

3.2 手动创建线程池(7个参数)

//juc提供了4个重载方法
//分别需要5个参数 , 6个参数5+a , 6个参数5+b , 7个参数
new ThreadPoolExecutor( ... );

int corePoolSize : 核心线程数 , 池内线程数的下限,未达下限,优先创建新线程。达到下限,任务装入队列

int maximumPoolSize : 最大线程数。队列满,创建更多线程,直到达到上限。拒绝任务

long keepAliveTime : 线程空闲存活时间(数字)

TimeUnit unit : 配合keepAliveTime , 指定存活时间(单位)

BlockingQueue<Runnable> workQueue : 任务等待队列(阻塞队列)
									ArrayBlockingQueue ,底层数组,适合指定长度
									LinkedBlockingQueue,底层链表,适合无限长度
									DelayQueue , 适合延迟(定时)任务
									PriorityBlockingQueue  自认队列(元素自然有序,大小)
									SynchronousQueue 同步队列,只能存一个元素

ThreadFactory threadFactory : 线程工厂,在线程池内部用来创建线程的。可以自定义

RejectedExecutionHandler handler : 拒绝策略,当任务队列满,线程达到上下且都忙碌,新任务被拒绝
									juc提供了多种拒绝策略,也可以自定义
									AbortPolicy  拒绝任务,并抛出异常
									DiscardPolicy 拒绝任务,没有任何提示
									DiscardOldestPolicy 拒绝最终加入等待队列的任务,
								                        新任务加入队列
								    CallerRunsPolicy 拒绝任务,将任务交还给发起任务的线程

3.3 使用线程池(常用方法)

pool.execute(Runnable); //执行Runnable任务
pool.submit(Runnable); //获得Future对象,可以知道线程是否执行完毕,但无法获得返回值
pool.submit(Callable); //获得Future对象,可以知道线程是否执行完毕,也可以获得返回值
future.isDone();//快速获得boolean结果,用来判断任务是否执行完毕
future.get();//获得任务执行的结果,如果还没有执行完毕,当前线程会处于等待状态
pool.submit(Runnable); //获得Future对象,可以知道线程是否执行完毕,但无法获得返回值
pool.submit(Callable); //获得Future对象,可以知道线程是否执行完毕,也可以获得返回值
future.isDone();//快速获得boolean结果,用来判断任务是否执行完毕
future.get();//获得任务执行的结果,如果还没有执行完毕,当前线程会处于等待状态

3.4 自定义线程工厂

  • 当需要自定义线程的名字,线程的优先级,精灵线程状态时,需要自定义线程工厂。

  • 如何自定义线程工厂

    1. 自定义工厂类,实现ThreadFactory接口,重写方法newThread()

    2. 在创建线程池对象时,传递上述线程工厂对象

public class Test5 {
    public static void main(String[] args) {
        System.out.println("------------------------");
        ThreadPoolExecutor pool = new ThreadPoolExecutor(
                5,15,
                5, TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(5),
                new MyThreadFactory());

        pool.execute(()->{
            System.out.println("---->"+Thread.currentThread().getName());
        });
    }
}

class MyThreadFactory implements ThreadFactory{

    //int count = 1 ;
    AtomicInteger count = new AtomicInteger(1);
    @Override
    public Thread newThread(Runnable r) {
        Thread t = new Thread(r);
        t.setName("pool-a-thread-"+count.getAndIncrement());
        t.setPriority(8);
        //t.setDaemon(true);
        return t;
    }
}

3.5 自定义拒绝策略

  • 实现,当前线程池能力不足时,交给另一个线程池来执行。

    1. 自定义拒绝策略类,

    2. 创建线程池对象时,传递自定义拒绝策略对象

3.6 自动获得线程池

  • 提供了5个方法可以快速获得线程池对象

//适合任务量大,功能简单的任务,需要确保每一次任务都需要快速反应
ExecutorService pool = Executors.newCachedThreadPool();

//线程池中只有一个线程,确保任务顺序执行
//当内部的线程执行任务出错时,会创建一个新线程。
ExecutorService pool = Executors.newSingleThreadExecutor();

//线程池内装载固定数量的线程
ExecutorService pool = Executors.newFixedThreadPool(5);

//可以执行定时任务的线程池
ScheduledExecutorService pool = Executors.newScheduledThreadPool(1);


//工作窃取线程池
//1. 本质是ForkJoinPool
//2. 提供了2个重载构造器,一个传参指定内部线程的数量,第二个不传参默认是逻辑处理器的数量
//3. 工作窃取线程池的机制
//	 所有的线程都是精灵线程
//	 之前的线程池,无论有多少个线程,都只有一个任务队列
//	 当一瞬间有多个线程同时完成任务时,就会同时去队列中获得新任务,此时就会发生阻塞(性能低)
//	 工作窃取线程池内部,每一个线程都配有一个任务队列,当完成任务时,去自己的队列中获取新任务
//	 当自己的队列中的任务完成后,就去其他线程任务队列中"窃取"任务执行
//	 每一个队列都是双端队列,当前线程从a端获取任务,其他线程从b端窃取任务
ExecutorService pool = Executors.newWorkStealingPool();

//延迟指定时间执行
//
pool.schedule(()->{
    System.out.println("-----------------------------");
},5, TimeUnit.SECONDS);


//延迟指定时间,开始执行
//执行后,每隔指定时间,重复执行
//每次执行的时间 会与 间隔时间叠加
 pool.scheduleAtFixedRate(
     ()->{
         for(int i = 1 ;i<=2;i++){
             System.out.println(i);
             try {
                 Thread.sleep(1000);
             } catch (InterruptedException e) {
                 throw new RuntimeException(e);
             }
         }

     },5,2,TimeUnit.SECONDS);



//延迟指定时间,开始执行
//执行后,每隔指定时间,重复执行
//每次执行的时间 不会与 间隔时间叠加
 pool.scheduleWithFixedDelay(
     ()->{
         for(int i = 1 ;i<=2;i++){
             System.out.println(i);
             try {
                 Thread.sleep(1000);
             } catch (InterruptedException e) {
                 throw new RuntimeException(e);
             }
         }

     },5,2,TimeUnit.SECONDS);

4 线程池生命周期

线程的生命周期

创建 -> 就绪 -> 运行 -> 销毁

|

暂停

线程池生命周期

  • 运行状态 RUNNING

    状态切换:随着线程池对象的创建,就立刻进入运行状态

    状态效果:运行时的线程池,可以接收任务,处理任务,缓存任务

  • 关闭状态 SHUTDOWN

    状态切换:在运行状态时,调用pool.shutdown()方法

    状态效果:线程池不再接收新任务,但会执行完先有的任务及队列中的任务

  • 停止状态 STOP

    状态切换:运行时直接调用pool.shutdownNow()方法

    或处于shutdown状态的线程池完成了所有手头任务,自动进入stop状态

    状态效果:线程池不再接收新任务,清除队列中的任务,中断正在执行的任务

  • 整理状态 TIDYING

    状态切换:没有方法能直接切换到整理状态

    处于stop状态的线程池,完成了所有任务处理工作,自动调用terminated()

    状态效果:逻辑上就是整理,释放资源,类似于finally中做的事

  • 终结状态 TERMINATED

    状态切换:没有方法能直接切换到终结状态

    处于整理状态的线程池,执行完terminated方法,自动进入终结状态

    状态效果:完全关闭线程池

线程池5种状态及线程数量的源码表示

  • 使用一个变量值表示线程池的状态及线程数量

  • 将一个int类型的值,按照其bit结构处理 , 拥有4 * 8 = 32 个bit

  • 使用高3位的bit,表示状态

  • 使用后面29位的bit,表示数量

//Integer.SIZE是int类型bit的数量 , 32个
//Integer.SIZE - 3 = 29个
//COUNT_BITS 用来表示线程数量的 bit的位数=29 . 使用29个bit来记录线程的数量
private static final int COUNT_BITS = Integer.SIZE - 3;
//表示线程池种线程的最大容量,理论上应该是29个bit位都是1
//1<<29位
//0000 0000 0000 0000 0000 0000 0000 0001 << 29
//0010 0000 0000 0000 0000 0000 0000 0000
//1<<29 - 1
//0001 1111 1111 1111 1111 1111 1111 1111
private static final int CAPACITY   = (1 << COUNT_BITS) - 1;

//1000 0000 0000 0000 0000 0000 0000 0001(原码)
//1111 1111 1111 1111 1111 1111 1111 1110(反码)
//1111 1111 1111 1111 1111 1111 1111 1111(补码)
//-1<<29
//1110 0000 0000 0000 0000 0000 0000 0000
//在众多状态中,只有运行状态是负数
private static final int RUNNING    = -1 << COUNT_BITS;
//0000 0000 0000 0000 0000 0000 0000 0000
private static final int SHUTDOWN   =  0 << COUNT_BITS;
//0010 0000 0000 0000 0000 0000 0000 0000
private static final int STOP       =  1 << COUNT_BITS;
//0100 0000 0000 0000 0000 0000 0000 0000
private static final int TIDYING    =  2 << COUNT_BITS;
//1100 0000 0000 0000 0000 0000 0000 0000
private static final int TERMINATED =  3 << COUNT_BITS;

//根据一个int数值,获得其状态(高3位,后29位都是0)
//YYYX XXXX XXXX XXXX XXXX XXXX XXXX XXXX
//1110 0000 0000 0000 0000 0000 0000 0000
private static int runStateOf(int c)     { return c & ~CAPACITY; }
//根据一个int数值,计算并获得线程的数量(高3位位0,只要后29位)
//YYYX XXXX XXXX XXXX XXXX XXXX XXXX XXXX
//0001 1111 1111 1111 1111 1111 1111 1111
private static int workerCountOf(int c)  { return c & CAPACITY; }

  • 35
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值