线程和线程池-------------------超详细讲解!!!!!!(快速入门线程与多线程)

-----------------本文主要讲解线程与线程池的相关知识点---------------

喜欢的小伙伴可以点点赞!创作不易!!!

1.线程

1.线程的了解

线程(英语:thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。在Unix System V及SunOS中也被称为轻量进程(lightweight processes),但轻量进程更多指内核线程(kernel thread),而把用户线程(user thread)称为线程。

从上面的解释中,大家伙肯定会懵逼,这是个啥?什么是实际运作单位?因为专业术语的原因会导致一部分人刚开始会难以理解线程是个什么东西。下面我会通过图文结合来慢慢让大家来理解的,不用着急,慢慢看下去!
在这里插入图片描述
在上面的图中有公司和工作和员工三个元素他们分别代表的意思是:

元素代表含义
公司程序
工作程序的功能
员工线程

我们每个员工做为一个线程,目的是为了去完成公司所布置的任务,因为工作量的关系,这个任务可能只需要一个人就能完成也可能需要多个员工来完成。我们就可以不是那么严谨的将两种现象归纳为:

  1. 单线程
  2. 多线程

也就是说,一个程序中可能只有一个线程来执行任务,也有可能有多个线程来执行任务,这个取决于这个程序的功能的大小。(当然现在市面上的所有程序基本都是多线程)
那么线程在一个程序中担任的角色也就是上述图中的员工的角色,这么与上面的文字对比,大家对线程的了解就会比较容易。
代码:
下面就是线程类,其实就是一个实现了Runnable的类。具体功能的实现逻辑,需要大家自己去看源码了,这里我就不过多的去解释源码了,只讲解功能,使用的话也需要大家自己稍微的了解一下!!

public
class Thread implements Runnable {
........
}

2.线程优先级

优先级(priority)是一种约定,优先级高的先做,优先级低的后做。优先级是计算机分时操作系统在处理多个作业程序时,决定各个作业程序接受系统资源的优先等级的参数。

我们还是老规矩,上图来进一步理解。
在这里插入图片描述

每个元素代表的含义:

元素代表含义
公司程序
工作程序的功能
员工线程
部长线程
经理线程

一个公司,肯定不只一个人为老板干活,那么肯定会根据每个员工的能力来进行职位的划分,我们可以理解员工就是最普通的一类人,部长比员工的能力会强一些,以此类推。既然有这个划分,那么肯定会有划分标准,那我们对标程序,一个线程也肯定有这个划分,那么线程中的这个划分标准是什么呢?

划分标准------> “线程优先级”

上面了解了线程优先级的基本概念,那么我们还需要知道在线程中优先级的作用是干嘛的。

公司中的任务的完成流程:
老板------>经理-------->部长-------->员工1
所以说线程的有优先级越高,那它就越容易去执行任务!!
(这里我写的是越容易去执行任务,而不是肯定先去执行任务,也就是说,在计算机中线程的优先级可以理解为线程抢占CPU时间片的概率,并不能保证优先级高的线程一定会先执行。)

还有最后一点,在计算机中的线程优先级中的等级限制:

线程的优先级是1-10之间的正整数,线程优先级最高为10,最低为1,默认为5。
1- MIN_PRIORITY
10-MAX_PRIORITY
5-NORM_PRIORITY

3.线程调度策略

Java语言规范和Java虚拟机规范是Java的重要文档,可惜的是他们都没有说明Java线程的调度问题。或许从Java的角度看,线程并不是Java最基本的内容。毕竟Thread类也仅仅是Java一个特定的类而已。
终于在Java SE 8 API规范的Thread类说明中算是找到了线程调度的有关描述:每个线程有一个优先级(从1级到10级),较高优先级的线程比低优先级线程先执行。程序员可以通过Thread.setPriority(int)设置线程的优先级,默认的优先级是NORM_PRIORITY。Java SE 还声明JVM可以任何方式实现线程的优先级,甚至忽略它的存在。这可以看做Java SE提供给程序员的关于线程调度的策略,恐怕就这些了。

上面这段话也是我从一个大佬的文章中摘选出来的,在这里作为线程调度策略的一个解释吧。
那么还是老规矩,画图解释:
在这里插入图片描述

在一个公司中,一个制度的形成肯定会有一些漏洞,那么,我们为了弥补一些已知的一些漏洞,我们会创建一个“任务分配处部门”,这个部门在一定程度上与优先级有类似的作用,但是却区别于优先级,可以理解为进一步完善使用优先级这个制度中的出现的一些小问题,举个例子:

公司中发布一个任务,首先会给老板看完,然后老板给经理去看,经理看完再给部长,这么下去才是一个正常的一个工作流程,但是如果经理在发布的这段时间里面请假了,我们还能按这个流程来走吗?所以为了应对这种问题,我们就引出了“线程调度策略”这个概念,目的就是为了解决这些问题的出现。

那么在程序中会出现那些线程调度问题呢?
下面我稍微的介绍一些:

线程调度器选择优先级最高的线程运行。但是,如果发生以下情况,就会终止线程的运行:
线程体中调用了yield()方法,让出了对CPU的占用权。
线程体中调用了sleep()方法,使线程进入睡眠状态。
线程由于I/O操作而受阻塞。
另一个更高优先级的线程出现。
在支持时间片的系统中,该线程的时间片用完。

好了,线程调度也差不多讲完了。

4.线程的生命周期

线程的生命周期包含5个阶段,包括:新建、就绪、运行、阻塞、销毁。
新建: 就是刚使用new方法,new出来的线程;
就绪: 就是调用的线程的start()方法后,这时候线程处于等待CPU分配资源阶段,谁先抢的CPU资源,谁开始执行;
运行: 当就绪的线程被调度并获得CPU资源时,便进入运行状态,run方法定义了线程的操作和功能;
阻塞: 在运行状态的时候,可能因为某些原因导致运行状态的线程变成了阻塞状态,比如sleep()、wait()之后线程就处于了阻塞状态,这个时候需要其他机制将处于阻塞状态的线程唤醒,比如调用notify或者notifyAll()方法。唤醒的线程不会立刻执行run方法,它们要再次等待CPU分配资源进入运行状态;
销毁: 如果线程正常执行完毕后或线程被提前强制性的终止或出现异常导致结束,那么线程就要被销毁,释放资源;

线程的生命周期图:对应的就是一个员工或者领导的生命周期,也就是一辈子的一些状态。

生命周期图:
在这里插入图片描述
这个就是一个线程的生命周期,这张图也是我从一个大佬那借鉴的,可以很好的描述一个线程的一辈子!!!

2.线程池

1.线程池的了解

线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务。线程池线程都是后台线程。每个线程都使用默认的堆栈大小,以默认的优先级运行,并处于多线程单元中。如果某个线程在托管代码中空闲(如正在等待某个事件),则线程池将插入另一个辅助线程来使所有处理器保持繁忙。如果所有线程池线程都始终保持繁忙,但队列中包含挂起的工作,则线程池将在一段时间后创建另一个辅助线程但线程的数目永远不会超过最大值。超过最大值的线程可以排队,但他们要等到其他线程完成后才启动。

之前我们了解了线程的相关知识,那么我们再来了解一下线程池的一些基本信息,还是老规矩,画图:
在这里插入图片描述

每个元素代表的含义:

元素代表含义
程序程序
工作程序的功能
线程池多个线程组成的池子
核心线程需要线程池维护的线程
其他线程不需要线程池维护的线程
线程队列一个Runnable接口的队列

对于线程池,举个例子来理解:

有一个公司,他有一个任务自己无法完成,因为自己公司没有这方面的人才所以需要别人来完成。然后他就给外包公司来做,这个外包公司其实也没几个人,但是他可以招聘人才来做,做完然后让这些人滚蛋。

在这里面这些任务或者工作就是程序的一些功能,这个外包公司就是线程池。
线程池在程序中的功能就是类似于这样的。具体怎么运作等会我会做详细的讲解。

上面讲解了这么多,线程池其实也可以下一个小小的定义:就是为了管理多个线程执行而创造出来的一个类

代码:
下面是一个线程池的构造方法,从当中不难看出一个线程池中的基本组成属性。而这些属性都是我们需要了解的。

public ThreadPoolExecutor(int corePoolSize,//核心线程数
                              int maximumPoolSize,//最大线程数
                              long keepAliveTime,//线程生命周期
                              TimeUnit unit,//时间类型
                              BlockingQueue<Runnable> workQueue,//线程队列
                              ThreadFactory threadFactory,//线程工厂
                              RejectedExecutionHandler handler//拒绝策略
                              ) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

1)corePoolSize(线程池的基本大小):当提交一个任务到线程池时,如果当前poolSize<corePoolSize时,线程池会创建一个线程来执行任务,即使其他空闲的基本线程能够执行新任务也会创建线程,等到需要执行的任务数大于线程池基本大小时就不再创建。如果调用了线程池的prestartAllCoreThreads()方法,线程池会提前创建并启动所有基本线程。

2)maximumPoolSize(线程池最大数量):线程池允许创建的最大线程数。如果队列满了,并且已创建的线程数小于最大线程数,则线程池会再创建新的线程执行任务。值得注意的是,如果使用了无界的任务队列这个参数就没什么效果。

3)keepAliveTime(线程活动保持时间):线程池的工作线程空闲后,保持存活的时间。所以,如果任务很多,并且每个任务执行的时间比较短,可以调大时间,提高线程的利用率。

4)TimeUnit(线程活动保持时间的单位):可选的单位有天(DAYS)、小时(HOURS)、分钟(MINUTES)、毫秒(MILLISECONDS)、微秒(MICROSECONDS,千分之一毫秒)和纳秒(NANOSECONDS,千分之一微秒)。

5)runnableTaskQueue(任务队列):用于保存等待执行的任务的阻塞队列。可以选择以下几个阻塞队列。

ArrayBlockingQueue: 是一个基于数组结构的有界阻塞队列,此队列按FIFO(先进先出)原则对元素进行排序。
LinkedBlockingQueue:一个基于链表结构的阻塞队列,此队列按FIFO排序元素,吞吐量通常要高于ArrayBlockingQueue。静态工厂方法Executors.newFixedThreadPool()使用了这个队列。
SynchronousQueue:一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于Linked-BlockingQueue,静态工厂方法Executors.newCachedThreadPool使用了这个队列。
PriorityBlockingQueue:一个具有优先级的无限阻塞队列。

6)ThreadFactory:用于设置创建线程的工厂,可以通过线程工厂给每个创建出来的线程设置更有意义的名字。使用开源框架guava提供的ThreadFactoryBuilder可以快速给线程池里的线
程设置有意义的名字,代码如下。
new ThreadFactoryBuilder().setNameFormat(“XX-task-%d”).build();

7)RejectedExecutionHandler(饱和策略):当队列和线程池都满了,说明线程池处于饱和状态,那么必须采取一种策略处理提交的新任务。这个策略默认情况下是AbortPolicy,表示无法处理新任务时抛出异常。在JDK 1.5中Java线程池框架提供了以下4种策略。

2.提交优先级

核心线程 > 工作队列 > 非核心线程

3.执行优先级

核心线程 > 非核心线程 > 工作队列

4.JDK自带线程池

在JAVA中的JDK中就有线程池的工具类Executors,这个工具类中一共有三种创建线程池的方式。
分别是:

  1. Executors.newSingleThreadExecutor(); 速度慢
  2. Executors.newCachedThreadPool();速度快,但是cpu占用高
  3. Executors.newFixedThreadPool(10);速度相对较快,但是可能会出现内存泄露

这三种方式的源码:

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

 public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

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

其实上面几种创建线程池的方式不是使用与所有的场景的,就我所了解的像阿里、百度等这种大厂中的程序员都不用这种方式,因为这种方式是有一些问题的,需要优化的,这里我就不过多的讲解优化这一块了!!!想了解更多的线程池有关的知识,可以自行去看一些大佬的博客!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值