初识Java多线程

陈污龟 2019-11-10 05:50:06
原文地址:https://segmentfault.com/a/1190000020805371

今天开始来学习一下有关Java多线程的知识,主要有以下知识点:

  • 进程与线程
  • 线程的生命周期
  • 中断线程
  • 线程池

进程与线程

什么是进程?

进程就是在运行过程中的程序,就好像手机运行中的微信,QQ,这些就叫做进程。

什么是线程?

线程就是进程的执行单元,就好像一个音乐软件可以听音乐,下载音乐,这些任务都是由线程来完成的。

进程与线程的关系

一个进程可以拥有多个线程,一个线程必须要有一个父进程。
线程之间共享父进程的共享资源,相互之间协同完成进程所要完成的任务。
一个线程可以创建和撤销另一个线程,同一个进程的多个线程之间可以并发执行。

线程的生命周期

![clipboard.png](https://segmentfault.com/img/bVbzsA5?w=1084&h=716 "clipboard.png")

  • 新建(New)

当线程实例被new出来之后,调用start()方法之前,线程处于新建状态。

  • 可运行(Runnable)

当线程实例调用start()方法之后,线程调度器分配处理器资源之前,线程处于可运行状态
或者线程调度器分配处理器资源给线程之后,线程处于运行中状态,这两种情况都属于可运行状态。

  • 等待(Waitting)

当线程处于运行状态时,线程执行了obj.wait()或Thread.join()方法、LockSupport.park()
以及Thread.sleep()时,线程处于等待状态。

  • 超时等待(Timed Waitting)

当线程处于运行状态时,线程执行了obj.wait(long)、 Thread.join(long)、LockSupport.parkNanos、
LockSupport.parkUntil以及Thread.sleep(long)方法时,线程处于超时等待状态。

  • 阻塞(Blocked)

当线程处于运行状态时,获取锁失败,线程进入等待队列,同时状态变为阻塞。

  • 终止(Terminated)

当线程执行完毕或出现异常提前结束时,线程进入终止状态

注意:在任何给定时刻,一个可运行的线程可能正在运行也可能没有运行(这就是为什么将这个状态称为可运行而不是运行)。

中断线程

interrupt方法可以用来请求终止线程。
当对一个线程调用interrupt方法时,线程的中断状态将被置位为true。这是每一个线程都具有的boolean标志。
每个线程都应该不时地检查这个标志,已判断线程是否被中断。

源码解析

 public void interrupt() {
    if (this != Thread.currentThread())
        checkAccess();//检查权限

    synchronized (blockerLock) {
        //判断线程是否被阻塞
        Interruptible b = blocker;
        if (b != null) {
            interrupt0();
            b.interrupt(this);//如果是阻塞线程,则把中断状态设为false后返回
            return;
        }
    }
    interrupt0();//否则把中断状态设为true
}

如果线程被阻塞,就无法检测中断状态。
当在一个被阻塞的线程(调用sleep或wait)上调用interrupt方法时,阻塞调用将会被Interrupt Exception异常中断。
如果阻塞线程调用了interrupt()方法,那么会抛出异常,设置标志位为false,同时该线程会退出阻塞的。(利用这个特性可以打破死锁)

interrupted和isInterrupted区别:

  • interrupted方法是一个静态方法,它检测当前的线程是否被中断。调用interrupted方法会清除该线程的中断状态。
  • isInterrupted方法是一个实例方法,可用来检测是否有线程被中断。调用这个方法不会改变中断状态。

线程池

概述

当需要执行的任务增多时,单个线程是满足不了需求的,此时就需要创建多个线程来完成需要。
多线程的最大好处就在于提高CPU的利用率和提高执行效率,同时也存在着一些弊端:频繁的创建和销毁线程会产生很多的性能开销。
为了解决这个问题,线程池孕育而生。

  • 复用线程池中的线程,减少创建和销毁线程的性能开销
  • 控制线程的并发数,避免对资源竞争而导致阻塞现象
  • 更好地管理线。

ExecutorService

在Java中,线程池的代码起源之Executor(翻译过来就是执行者)注意:这是一个接口。
Executor有一个ExecutorService子接口。实际上,一般说线程池接口,基本上说的是这个ExecutorService。
ExecutorService接口的默认实现类为ThreadPoolExecutor(翻译过来就是线程池执行者)。

ThreadPoolExecutor

翻译过来就是线程池执行器,它是线程池的真正实现,构造方法提供了一些参数来配置线程池。

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

参数详解

corePoolSize:线程池中核心的线程数。

核心线程默认情况下会一直存活在线程池中,即使这个核心线程啥也不干(闲置状态)。
如果指定ThreadPoolExecutor的allowCoreThreadTimeOut这个属性为true,那么核心线程如果不干活(闲置状态)的话,超过一定时间( keepAliveTime),就会被销毁掉。

maximumPollize:线程池所能容纳的最大线程数。超过限制,新线程会被阻塞。

keepAliveTime:一个非核心线程,如果不干活(闲置状态)的时长,超过这个参数所设定的时长,就会被销毁掉。但是,如果设置了allowCoreThreadTimeOut = true,则会作用于核心线程。

unit:超时等待时间单位。

workQueue:线程池中的任务队列。每次执行execute()会把runnable对象存储在这个队列中。如果队列满了,则新建非核心线程执行任务。

threadFactory:线程工厂,为线程池提供创建新线程的功能。

执行任务

ThreadPoolExecutor poolExecutor;
//初始化一个线程池
poolExecutor = new ThreadPoolExecutor(corePoolSize: 3,
                                      maximumPoolSize: 5,
                                      keepAliveTime: 30,
                                      TimeUnit.SECONDS,
                                      new ArrayBlockingQueue<Runnable>( capacity: 2));
//向线程池中添加任务
poolExecutor.execute(new Runnable() {
      public void run() {
});

首先我们初始化一个线程池后,即可调用execute这个方法,里面传入Runnable即可向线程池添加任务。

问题又来了,既然线程池新添加了任务,那么线程池是如何处理这些批量任务?

  • 如果线程数量未达到corePoolSize,则新建一个核心线程执行任务。
  • 如果线程数量达到了corePoolSize,则将任务移入队列等待执行。
  • 如果队列已满,新建线程(非核心线程)执行任务。
  • 如果队列已满,总线程数又达到了maximumPoolSize,就会由RejectedExecutionHandler抛出异常。

常用的线程池

Executors提供了创建常用线程池的静态方法,接下也会大概讲解一下常用的四种线程池:

  • 定长线程池(FixedThreadPool)
  • 单线程化线程池(SingleThreadExecutor)
  • 定时线程池(ScheduledThreadPool)
  • 缓存线程池(CachedThreadPool)

FixedThreadPool

创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。

public static ExecutorService newFixedThreadPool(int nThreads){
   return new ThreadPoolExecutor(nThreads, nThreads
                                keepAliveTime: OL, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>());
}

特点:

  • 线程数量固定
  • 只有核心线程,并且不会被回收
  • 超过corePoolSize的线程,他们会在队列中等待,直到有一个线程可用。
  • 适用于控制线程的最大并发数

CachedThreadPool

创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。

public static ExecutorService newCachedThreadPool() {
   return new ThneadPoolExecutor(corePoolSize: 0, maximumPoolSize: Integer.MAX VALUE,
                                 keepAliveTime: 60L, TimeUnit. SECONDS,
                                 new SynchronousQueue<Runnable>();
}

特点:

  • 无核心线程
  • 非核心线程数量无限制
  • 对于空闲线程回收灵活//超过60s没有使用则进行回收
  • 适用于大量且耗时少的任务

ScheduledThreadPool

创建一个定长任务线程池,支持定时及周期性任务执行。

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
 }

特点:

  • 线程数量固定
  • 非核心数量无限制
  • 适用于定时或者周期性任务

SingleThreadExecutor

创建一个单线程的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

  public static ExecutorService newSingleThreadExecutor() {
     return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1,
                                                    keepAliveTime: 0L,
                                                    TimeUnit.MILLISECONDS,
                                                    new LinkedBlockingQueue<Runnable>()));
    }

特点:

  • 只有一个核心线程
  • 任务队列无限制
  • 不需要考虑线程同步问题
  • 适用于一些因为并发而导致问题的操作
总结

有关多线程暂且说到这里,先对多线程有个初步的认识,后面会深入研究多线程。

参考:Java线程池

声明:该文章系转载,转载该文章的目的在于更广泛的传递信息,并不代表本网站赞同其观点,文章内容仅供参考。

本站是一个个人学习和交流平台,网站上部分文章为网站管理员和网友从相关媒体转载而来,并不用于任何商业目的,内容为作者个人观点, 并不代表本网站赞同其观点和对其真实性负责。

我们已经尽可能的对作者和来源进行了通告,但是可能由于能力有限或疏忽,导致作者和来源有误,亦可能您并不期望您的作品在我们的网站上发布。我们为这些问题向您致歉,如果您在我站上发现此类问题,请及时联系我们,我们将根据您的要求,立即更正或者删除有关内容。本站拥有对此声明的最终解释权。