高性能并发编程之线程一

本文介绍了线程的概念,详细讲解了如何创建线程、线程的六种状态,探讨了线程中止的正确方法,包括interrupt机制和线程协作。还讨论了线程通信的方式,如wait/notify、park/unpark,并介绍了ThreadLocal在实现线程封闭中的作用,最后阐述了线程池的应用和实现原理。
摘要由CSDN通过智能技术生成

高性能并发编程之线程一

什么是线程?

线程:是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务

1、如何创建线程

  • 继承Thread类来创建线程
  • 实现Runnable接口创建线程
  • lambda表达式来创建线程
  • 使用Callable和Future来创建线程
  • 使用线程池(Executor)来创建线程

2、线程状态: 线程6个状态(java.lang.Thread.State)

  • New:尚未启动的线程的线程状态
  • Runnable:可运行线程的线程状态,等待CPU调度
  • Blocked:线程阻塞等待监视器锁定的状态
  • Waiting:等待线程的状态
  • Timed Waiting: 具有指定等待时间的等待线程的线程状态
  • Terminaed:终止线程的 线程状态。线程正常完成执行或出现异常。

3、线程中止

  • 不正确的线程中止-Stop,Stop:中止线程,清除监控器锁的信息,但是可能导致线程安全问题 ,JDK不建议用。Destroy:JDK未实现该方法。
  • 正确的线程中止-interrupt:如果目标线程在调用Object class的wait()、wait(long)或wait(long,int) 方法、join()、join(long,int)或sleep(long,int)方法时被阻塞, 那么interrupt会生效, 该线程的中断状态被清除,抛出InterruptException异常。如果目标线程是被I/O或者NIO中的Channel所阻塞,同样I/O操作会被中断或者返回特殊异常值。达到线程中止的目的,如果都不满足,则会设置此线程的中断状态。
  • 正确的线程中止-标志位:通过标识符来判断当前线程要不要继续执行
  • interrupt 之后会先通知线程终止,此时会继续执行线程run 方法,状态为RUNNABLE,执行完毕后,线程状态才会变为TERMINATED

4、线程通信

通信方式:涉及到线程之间的通信方式分为四类

  • 文件共享:一个线程写数据到某个文件中(例如test.txt文件),另一个线程读取test.txt文件中的数据
  • 网络共享
  • 共享变量:设置一个公共变量,2个线程可以同时对该变量读取
  • JDK提供的线程协调API:suspend/resume 、wait/notify、park/unpark

5、线程协作 - JDK API

JDK中对于需要多线程协作完成某一任务的场景,提供了对应API支持。
多线程协作的典型场景是:生产者 - 消费者模型。

  • 被弃用的suspend和resume: 弃用原因,很容易造成死锁
    a、suspend等待时不会释放锁,唤醒时拿不到锁会一直等待挂起,
    b、suspend比resume后执行也会导致死锁

  • wait/notify :只能由同一对象锁的持有者线程调用,也就是写在同步块里面,否则会抛出IllegalMonitorStateException异常
    a、wait方法导致当前线程等待,加入该对象的等待集合中,并且放弃当前持有的对象锁。
    b、notify/notifyAll方法唤醒一个或所有正在等待这个对象锁的线程
    PS:虽然wait会自动解锁,但是对顺序有要求的,如果在notify被调用之后,才开始wait方法的调用,线程会永远处于waitting状态。

  • park/unpark
    a、线程调用park则等待“许可”,unpark方法指定线程提供“许可”;
    b、不要求park和unpark方法的调用顺序;
    c、多次调用unpark之后,再调用park,线程会直接运行。但不会叠加,也就是说,连续多次调用park方法,第一次会拿到“许可”直接运行,后续调用会进入等待;
    d、同步代码不会释放锁,会导致死锁。

  • 伪唤醒:用if语句来判断线程是否进入等待是错误的,官方建议用while来判断,防止伪唤醒导致程序逻辑问题或异常,代码示例如下:

//wait
synchronize(obj){
while(<条件判断>){
	obj.wait();
	//执行后续操作
	...
}
//park
synchronize(obj){
while(<条件判断>){
	LockSupport.park();
	//执行后续操作
	...
}

6、线程封闭之ThreadLocal

  • 线程封闭:
    a、多线程访问共享可变数据时,涉及到线程间数据同步的问题,并不是所有时候,都要用到共享数据,所以线程封闭概念就提出来了;
    b、数据都被封闭在各自的线程之中,就不需要同步,这种通过将数据封闭在线程中而避免使用同步的技术成为线程封闭
    c、线程封闭具体的体现有:ThreadLocal、局部变量。
  • ThreadLocal: 它是一个线程级别变量,每个线程都有一个ThreadLocal就是每个线程都拥有了自己独立的一个变量,竞争条件被彻底消除了,在并发模式下是绝对安全的变量。
    用法:ThreadLocal var = newThreadLocal();会自动在每一个线程上创建一个T的副本,副本之间彼此独立,互不影响,可以用ThreadLocal存储一些参数,以便在线程中多个方法中使用,用来代替方法传参的做法。
  • 栈封闭:局部变量的固有属性之一就是封闭在线程中,他们位于执行线程的栈中,其他线程无法访问这个栈。

7、线程池应用即实现原理

线程是不是越多越好

为什么要用线程池

a、线程在java中是一个对象,更是操作系统的资源,线程创建、销毁需要时间,如果创建时间+销毁时间>执行任务时间就不合算;
b、java对象占用堆内存,操作系统占用系统内存,根据JVM规范,一个线程默认最大栈大小1M,这个栈空间是需要从系统内存中分配的,线程过多会消耗很多内存;
c、操作系统需要频繁切换线程上下文(大家都想被运行),影响性能。
ps:线程池的推出,就是为了方便的控制线程数量。

  • 线程池原理概念
    a、线程池管理器:用于创建并管理线程池,包括创建线程池,销毁线程池,添加任务;
    b、工作线程:线程池中的线程,在没有任务时是处于等待状态,可以循环的执行任务;
    c、任务接口:每个任务必须实现接口,以供工作线程调度任务的执行,它主要规定了任务的入口,任务执行完后的收尾工作,任务的执行状态等;
    d、任务队列:用于存放没有处理的任务。提供一种缓冲机制。
    示例图如下:多个任务被提交后由线程池的任务仓库进行管理,并把任务分配给不同的线程运行
    在这里插入图片描述
  • 线程池API - 接口定义和实现类

1)接口定义:

类型名称描述
接口Executor最上层的接口,定义了执行任务的方法execute
接口ExecutorService集成了Executor接口,拓展了Callable、future、关闭方法
接口ScheduleExecutorService继承了ExecutorService,增加了定时任务相关方法
实现类ThreadPoolExecitor基础标准的线城实现
实现类ScheduledThreadPoolExecutor继承了ThreadPoolExecutor,实现ScheduledExecutorService中相关定时任务的方法

2)方法定义:
a、ExecutorService
在这里插入图片描述
b、ScheduledExecutorService
在这里插入图片描述
c、Executors工具类:

  • newFixedThreadPool(int nThreads):创建一个固定大小、任务队列容量无界的线程池。核心线程数=最大线程数;
  • newCachedThreadPool():创建的是一个大小无界的缓冲线程池。他的任务队列是一个同步队列。任务加入到池中,如果池中有空闲线程,则用空闲线程执行,如果无则创建新线程执行。池中的线程空闲超过60秒,将被销毁释放。线程数随任务的多少变化。适用于执行耗时较小的异步任务。池的核心线程数=0,最大线程数=Integer.MAX_VALUE
  • newSingleThreadExecutor()只有一个线程执行无界任务队列的单一线程池。该线程池确保任务按加入的顺序一个一个一次执行。当唯一的线程因任务异常中止时,将创建一个新的线程来继续执行后续的任务。与newFixedThreadPool(1)的区别在于,单一线程池的大小在newSingleThreadExecutor方法中硬编码,不能改变的。
  • newScheduleThreadPool(int corePoolSize):能定时执行任务的线程池。
    该池的核心线程数由参数指定,最大线程数=Integer.MAX_VALUE

d、线程池原理 - 任务execute的过程

  • 是否达到核心线程数量? 没达到,创建一个工作线程来执行任务。
  • 工作队里是否已满?没满,则将新提交的任务存储在工作队列里。
  • 是否达到线程池最大数量?没达到,则创建一个新的工作线程来执行任务。
  • 最后,执行拒绝策略来处理这个任务
  • 先进队列,在创建新线程
    在这里插入图片描述

f、线程数量:如何确定合适的数量

  • 线程计算型任务:cpu数量的1-2倍
  • IO型任务:相对比计算型任务,需多一些线程,要根据具体的IO阻塞时长进行考量决定。如tomact中默认的最大线程数为200.也可考虑根据需要在一个最小数量和最大数量间自动增减线程数。

后续待更新补充,文章内容皆为概念性理论知识点,若有不准确之处还请指出,共同学习

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值