全面认识和理解“线程池”

一、什么是线程

(一)进程与线程

在了解线程前,需要知道什么是进程。进程是一个程序执行的过程集合体,是操作系统进行资源分配和调度的一个独立单位,是应用程序运行的载体,包括就绪态、运行态和阻塞态三种状态。

进程一般由(1)程序;(2)数据集合和(3)进程控制块三部分组成。

  • 程序用于描述进程要完成的功能,是控制进程执行的指令集;
  • 数据集合是程序在执行时所需要的数据和工作区;
  • 程序控制块包含进程的描述信息和控制信息是进程存在的唯一标志
    在这里插入图片描述

进程间通过争抢时间片实现对CPU的时分复用,提高了系统的并发程度。那为什么还需要线程呢。因为随着计算机的发展,对系统运行速度要求越来越高。线程比进程更小,基本上不拥有系统资源,故对它的调度所付出的开销比进程就会小得多,能更高效的提高系统内多个程序间并发执行的程度。引入了线程就是便进一步提高系统的并发性。

下面通过进程与线程的关系了解线程。线程是调度的最小单位,进程是分配资源的最小单位。如下图所示,一个进程中可以有1至多个线程,线程之间共享代码、数据、进程空间等,拥有自己独立的寄存器和栈。
在这里插入图片描述
具体来说,进程和线程的区别可以细化为以下几个方面:

  • 定位不一样: 线程是程序执行的最小单位,而进程是操作系统分配资源的最小单位;一个进程由一个或多个线程组成,线程是一个进程中代码的不同执行路线

  • 资源不一样:进程之间相互独立,但同一进程下的各个线程之间共享程序的内存空间(包括代码段,数据集,堆等)及一些进程级的资源(如打开文件和信号等);

  • 开销不一样:线程上下文切换比进程上下文切换要快得多

(二)线程的生命周期

线程的生命周期一共包括5个状态,他们之间的状态转移图如下所示。

  • 新建(new):就是刚使用new方法new Thread(),new出来的线程;

  • 就绪(Runnable):就是调用的线程的start()方法后,这时候线程处于等待CPU分配资源阶段,谁先抢的CPU资源,谁开始执行;

  • 运行(Running):当就绪的线程被调度并获得CPU资源时,便进入运行状态,run方法定义了线程的操作和功能;

  • 阻塞(Blocked):在运行状态的时候,可能因为某些原因导致运行状态的线程变成了阻塞状态,比如sleep()wait()之后线程就处于了阻塞状态,这个时候需要其他机制将处于阻塞状态的线程唤醒,比如调用notify或者notifyAll()方法。唤醒的线程不会立刻执行run方法,它们要再次等待CPU分配资源进入运行状态;

  • 销毁(Terminated):如果线程正常执行完毕后或线程被提前强制性的终止或出现异常导致结束,那么线程就要被销毁,释放资源;

在这里插入图片描述

二、线程怎么用

既然线程可以进一步提高系统的并发程度,那怎么让程序进行多线程运行呢。

下面,介绍创建线程的三种简单方式。

  • 实现Runnable接口
  • 实现Callable接口
  • 继承Thread类

其中,实现Runable接口和Callable接口不是真正意义上的线程,需要通过Thread来调用。它们只能当作一个可以在线程中执行的任务,由Thread来调用执行。

另外,Callable是可以带返回值的,返回值通过FutureTask进行封装。

(一)实现Runable接口

The ‘Runnable’ interface should be implemented by any class whose instances are intended to be executed by a thread. The class must define a method of no arguments called ‘run’.
所有准备被一个线程执行的类都必须继承Runnable接口,Thread类也实现了Runnable接口。该接口提供一个无参的run()方法。
This interface is designed to provide a common protocol for objects that wish to execute code while they are active. For example, ‘Runnable’ is implemented by class ‘Thread’. Being active simply means that a thread has been started and has not yet been stopped.
In addition, ‘Runnable’ provides the means for a class to be active while not subclassing ‘Thread’. A class that implements ‘Runnable’ can run without subclassing ‘Thread’ by instantiating a ‘Thread’ instance and passing itself in as the target. In most cases, the ‘Runnable’ interface should be used if you are only planning to override the ‘run()’ method and no other ‘Thread’ methods. This is important because classes should not be subclassed unless the programmer intends on modifying or enhancing the fundamental behavior of the class.


/**
* @author wanglong
* @time 2021/8/13/
* @ref 继承Runnable接口使用线程
*/
public class MyRunnable implements Runnable{

   @Override
   public void run() {
       System.out.println("这里是子线程");
   }

   public static void main(String[] args) {
       MyRunnable myRunnable = new MyRunnable();
       Thread myThread = new Thread(myRunnable);
       myThread.start();
   }
}

(二)实现Callable接口

Callable,A task that returns a result and may throw an exception. Implementors define a single method with no arguments called ‘call’.
Callable是带有返回值的任务接口,实现它的类都必须实现一个没有参数的call()方法
The ‘Callable’ interface is similar to ‘java.lang.Runnable’, in that both are designed for classes whose instances are potentially executed by another thread. A ‘Runnable’, however, does not return a result and cannot throw a checked exception.
Callable和Runnable非常像,两者都是为实例可能由另一个线程执行而设计的。但是Runnable没有返回值,也不能抛出受检异常。
The ‘Executors’ class contains utility methods to convert from other common forms to ‘Callable’ classes.

package com.example.threaddemo.single;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

/**
 * @author wanglong
 * @time 2021/8/13/
 * @ref
 */
public class MyCallable implements Callable<Integer> {

    @Override
    public Integer call() throws Exception {
        return 123;
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        MyCallable mc = new MyCallable();
        FutureTask<Integer> ft = new FutureTask<>(mc);
        Thread t = new Thread(ft);
        t.start();
        System.out.println(ft.get());
    }
}

需要注意的是,Callable接口本身没有实现Runnable接口,不能直接被Thread调用。Callable需要配合FutureTask一同使用。FutureTask实现了Runable接口和Future接口。Runnable保证了可以被Thread调用异步执行,Future保证了可以获取返回值。

(三)继承Thread类

继承Thread类也需要实现run()方法,因为Thread也实现了Runnable接口。
当调用线程的start()方法启动一个线程时,虚拟机会将该线程放入到就绪队列中等待被调度,当一个线程被调度执行时会执行该线程的run()方法。

/**
 * @author wanglong
 * @time 2021/8/14/
 * @ref
 */
public class MyThread extends Thread{

    @Override
    public void run() {
        System.out.println("myThread: " + Thread.currentThread().getName());
    }

    public static void main(String[] args) {
        System.out.println("main: " + Thread.currentThread().getName());
        MyThread  myThread = new MyThread();
        myThread.start();
    }

}

三、线程池

在一个应用程序中,如果需要多次使用线程,也就意味着需要多次创建并销毁线程,创建并销毁线程的过程势必会消耗内存。而在Java中,内存资源是及其宝贵的,所以,我们就提出了线程池的概念。

(一)创建线程池

Java中已经提供了创建线程池的一个类:Executor。
在这里插入图片描述

而我们创建时,一般使用它的子类:ThreadPoolExecutor.

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

(二)线程池参数含义

各个参数含义如下:

  • corePoolSize:核心线程数量

(线程池中会维护一个最小的线程数量,即使这些线程处理空闲状态,他们也不会被销毁,除非设置了allowCoreThreadTimeOut为true。这里的最小线程数量即是corePoolSize。)

  • maximumPoolSize:线程池允许的最大线程池数量

一个任务被提交到线程池以后,首先会找空闲存活线程,如果有则直接将任务交给这个空闲线程来执行,如果没有则会缓存到工作队列中,如果工作队列满了,才会创建一个新线程,然后从工作队列的头部取出一个任务交由新线程来处理,而将刚提交的任务放入工作队列尾部。线程池不会无限制的去创建新线程,它会有一个最大线程数量的限制,这个数量即由maximunPoolSize指定。

线程池最大线程数的设置可以参照以下规则:CUP密集型 N+1(N为CPU核数);IO密集型 2N(N为CPU核数)

  • keepAliveTime:空闲线程存活时间。

    (一个线程如果处于空闲状态,并且当前的线程数量大于corePoolSize,那么在指定时间后,这个空闲线程会被销毁,这里的指定时间由keepAliveTime来设定。)

  • unit:超时时间的单位,也就是keepAliveTime的计量单位

  • workQueue:工作队列,保存未执行的Runnable 任务

  • threadFactory:创建线程的工厂类

  • handler:拒绝策略。

    当线程已满,工作队列也满了的时候,会被调用。被用来实现各种拒绝策略。jdk中提供了4中拒绝策略:

    • ①CallerRunsPolicy:该策略下,在调用者线程中直接执行被拒绝任务的run方法,除非线程池已经shutdown,则直接抛弃任务。
    • ②AbortPolicy:直接丢弃任务,并抛出RejectedExecutionException异常。
    • ③DiscardPolicy:直接丢弃任务,也不抛出异常。
    • ④DiscardOldestPolicy:该策略下,抛弃进入队列最早的那个任务,然后尝试把这次拒绝的任务放入队列

(三)常见线程池

四种常见的线程池:

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

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

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值