java多线程的杂谈

java的多线程

java的多线程的概念,向来都是很复杂、笼统、抽象的。现实世界只有将知识点抽象事后才能有效的传播,可是传播的过程当中,只有将抽象的知识点具象化,咱们才能习得。因此咱们会将个别内容点进行一个具象化进而解剖。当咱们理解完了以后最终将其抽象成一个个名词:多线程、资源、锁等。java

本文仅从如下的范围内容来谈谈java的多线程。程序员

  1. 何为线程,线程的做用
  2. 资源的控制,锁的介绍
  3. 线程池的做用
  4. 多线程的经常使用工具和方法

1.何为线程

1.1.线程的定义

官方解释:线程是一个单一的顺序控制流程编程

线程分主线程、子线程。由主线程来建立子线程来执行各类任务。多线程

举例说明:以动漫“火影忍者”举例说明,主线程就比如每个忍者,他们构成了最基本的忍者世界,一个忍者能够按照任务的缓急、难易程度同时执行多个任务。同时忍者也能分身(调用自身查克拉)来分担自身的任务,这就比如,忍者世界观中的忍者主体,基本等同于程序中的主线程。主线程没了,分身则主观上不可控,就消失了。异步

图片描述

由上图可见,主线程与子线程的关系和忍者与分身的是很类似,也就是说,主线程能作的事,咱们都能让子线程帮咱们作。忍者本身能作的事也能去靠分身去作。接下来,咱们来看两段代码。工具

//代码一
    public static void main(String[] args) {
        System.out.println("Hello World!");
    }
复制代码
//代码二
    public static void main(String[] args) {
        Thread thread = new Thread(()->{
            System.out.println("Hello World!");
        });
        thread.start();
    }
复制代码

代码一和代码二最终的结果都是只作了一件事,向控制台输出“Hello World!”。可是代码一是由主线程去作的;代码二是由主线程建立的子线程去作的。这里咱们能够看出,主线程和子线程的本质上区分并不大,由于它们都执行相同的逻辑,这一点上并无进行区分。性能

1.2.多线程的做用

程序若是都是按照单个线程的话,那么全部任务的执行均是按照顺序来进行(串行执行)。优化

而多线程的做用是能够安排不一样的线程执行不一样的任务spa

图片描述

上图是一个理论值,咱们在某些任务密集的场景下,多线程的执行效率多数状况下可能高于单线程的执行。线程

为何说是可能,由于这里建立子线程是会消耗性能的,也带有时间消耗,若是设置不合理,单单建立子线程的时间成本就远大于执行任务的时间成本,这一点要结合实际场景进行考虑。

举例说明:火影里的忍者也不会接到一批任务就立刻分身去一个个的作,他们也得结合任务的实际状况来考虑使用分身。

1.3.关于线程的小结

  1. 线程能够执行正确的逻辑代码
  2. 线程的建立也伴随着性能消耗,并非无消耗

2.资源的控制

java中的资源能够理解为,一个实例或基础数据类型的变量的任何操做。实例或变量在这里不能彻底算作资源,由于根据面向对象编程中的封装性,代码中直接将实例暴露出来给非本类的实例进行操做是一个大忌。

图片描述

这里咱们从如下资源和线程的关系进行解剖。

  1. 一个线程能够执行多个资源(串行)
  2. 多个线程能够执行多个资源(并行)
  3. 单个资源能够被一个线程执行(串线)
  4. 单个资源能够被多个线程执行(并行)

从这里看,1和3没什么问题。由于这种机制下咱们确保了一个资源被一个线程执行(等同于一个任务被一个忍者(本体或分身都可)执行)。可是2和4就出现了一个现象,同一个资源被多个线程所操做,若是不加以控制,则会出现指定以外的执行结果或者直接产生死锁。

举例说明:两个忍者都执行了同一个任务,去杀死邻国的头目,咱们假设忍者A过去杀死了头目,忍者B后去的,发现头目死了,那他接下来怎么办?算任务失败仍是算完成了?

固然忍者B最后确定仍是回去复命了,也算他任务成功,这是任务自己的规则和秩序所决定的,可是程序的世界是无秩序,须要程序员经过代码去打造这个无序的世界从而造成秩序

资源自身必定要包含约束性和规则性才能被正确的使用。

2.1.常见的控制方式

java自己提供了资源被多个线程调度的控制方式。

  • synchronized关键字
  • Atomic包
  • ReentrantLock
  • Semaphore
  • CountDownLatch
  • CyclicBarrier
  • Phaser

咱们经过一张图表来概况了解一下。

图片描述

对于资源的控制的方式无非就是一个“” 字。现实当中处处充斥着这样的例子,例如一个城市的市长,按照规定只能有一个,谁上任,那么市长这个资源位就被谁“锁”住了。可是程序世界中的“锁”和现实世界中的“锁”差异很大。

  • 现实世界的锁,是可见,它控制着某同样可见的物体,好比:门、箱子等,而再由这些具备隔绝性质的物体去控制级别更高的资源,例如:门里的东西、箱子里的钱。也就是说现实世界的锁是间接的控制资源。
  • 程序世界里的锁,则是一种更为高级的抽象,它包含的对资源的各类维度的控制,咱们能够将其理解为“规则”,好比:某类资源在同一时刻只能有一个线程进行操做(同步性)、某类资源必须由多个线程同时操做(同步协做)、某类资源最多只能有N个线程进行操做(资源调度许可证)。这些都是“规则”的运用。

2.2.锁的种类

java中有关于锁的内容很是多,咱们这里先用一张图来简要介绍一下,之后再着重篇幅去介绍每一个锁的相关特性。

图片描述

2.3.关于资源的小结

  1. 任何实例对外提供的方法(尽可能避免对变量的直接操做)
  2. 咱们须要在对外提供的方法内用“锁”去控制方法内的被调度的规则。
  3. 实行第二条以前必定要肯定当前的编程环境,是单线程的仍是多线程

3.线程池

用一段话形容线程和资源的关系那就是。某我的(线程)去作(调度)某件有要求和规则(锁策略)的事(资源);根据这件事(资源)的要求和规则(锁策略)去约束作这个事人(线程)的作法

咱们用各类锁策略去保证资源能被正确的使用。这里咱们还缺一个角度,那就是从线程的角度去调度资源。

咱们用一个问题开头来展开对话。

  • 问:咱们能根据资源的数量去建立线程的数量吗?
  • 答:不能,由于建立线程的开销大,受机器的配置的限制。
  • 问:那么能不能建立必定数量的线程,去循环的调度资源。
  • 答:这么作是能够的,可是资源数通常来讲确定是多于线程数,咱们要控制资源的调度顺序,还将来得及调度的资源能够按先来后到原则存放到队列。
  • 问:那资源数少于线程数时候,该怎么样去处理。
  • 答:咱们能够保留必定量的线程,为将来可能调度的资源作预备。

这就是一个线程池的雏形,线程池的雏形具备如下的基本特性

  1. 具备最大的线程数限制
  2. 有若干常备线程(核心线程)
  3. 资源数若多超过了最大线程数的限制则会放入队列中。

咱们来解刨线程池的最全的配置信息:

  1. corePoolSize:核心线程数
  2. maximumPoolSize:最大的线程数
  3. keepAliveTime:无资源调度的线程回收的时间(默认单位:毫秒)
  4. TimeUnit:时间单位
  5. BlockingQueue:多余的资源放入的队列
  6. ThreadFactory:线程的工厂类
  7. RejectedExecutionHandler:线程池调度资源的策略

图片描述

按照咱们常规的设置

BlockingQueue > maximumPoolSize ≥ corePoolSize

  • corePoolSize会随着资源调度数增长至maximumPoolSize
  • 当线程空闲时,会根据keepAliveTime来回收线程数(maximumPoolSize-corePoolSize)
  • BlockingQueue分无界Queue和有界Queue。
  • 当资源调度大于maximumPoolSize时会放入BlockingQueue中
    • 当BlockingQueue是有界队列则存入
    • 当BlockingQueue是无界队列则根据策略调整
  • 当资源调度数大于BlockingQueue的长度,则根据RejectedExecutionHandler的策略来调整资源调度状况。
    • AbortPolicy:默认策略,舍弃最新的资源调度,并抛出异常
    • DiscardPolicy:舍弃最新的资源调度,不会有异常
    • DiscardOldestPolicy:舍弃在队列中队头的资源
    • CallerRunsPolicy:交由主线程去执行(慎用
    • 自定义拒绝策略:实现RejectedExecutionHandler接口,编写特殊业务的拒绝策略。

总结:线程池就是多个线程来调度多个资源时所优化的一种多维度的策略,它的核心就是线程的复用以及资源的缓冲存储。

经常使用的方法和工具

类或关键字 简要介绍 使用的对象
synchronized 做用于方法或代码块中实现线程的同步性 资源
Atomic包 原子性控制变量或实例 资源
ReentrantLock 做用于方法中,实现线程的同步性 资源
Semaphore 做用于方法中,限制方法的线程最大调度数 资源
Phaser 做用于方法中,设置线程必须调度的数量 资源
Object.wait() 做用于同步的方法中,使当前调度的进程等待 资源
Object.notifyAll()或notify() 做用于同步的方法中,唤起当前处于等待状态的调度线程 资源
Runable 线程中调度无返回结果的资源 线程
Callable 线程中调度有返回结果的资源 线程
Future 线程执行有返回结果的资源的接受方 线程
Executor 建立线程池的工厂类(慎用) 线程
ThreadPoolExecutor 线程池的实现类 线程
ExecutorService 线程池的基础类 线程
CompletionService 异步处理带返回结果的线程池 线程
ScheduledExecutorService 处理定时任务的线程池 线程
Fork-Join 分治思想处理批量任务 线程
相关文章
相关标签/搜索