不要玄之又玄:多线程相关知识点的通俗理解

线程和进程的区别

  • 进程是一个程序执行一次创建的,是系统运行程序的基本单位。
  • 线程是⼀个⽐进程更⼩的执⾏单位,一个进程在运行期间可以产生多个线程。多个线程可以共享进程的堆和⽅法区资源,每个线程有⾃⼰的程序计数器、虚拟机栈和本地⽅法栈。
  • 线程是进程划分成的更⼩的运⾏单位。
  • 线程和进程最⼤的不同在于基本上各进程是独⽴的,⽽各线程则可以有共享的资源也有自己独有的资源。
  • 线程执⾏开销⼩,但不利于资源的管理和保护;⽽进程正相反

多线程的几个对象

  • 任务对象
    • Runable
      • 核心方法run方法
      • 需要通过executor的execute方法执行
    • Callable:
      • 核心方法 call方法
      • 需要通过executor的submit方法执行
  • 执行对象
    • executor:顶层接口,包含execute方法
    • ExecutorService:继承executor接口,包含submit和shutdown方法
  • 结果
    • Future:线程返回结果的顶层接口,包含get方法
    • FutureTask是Future的实现类

线程池

  • ThreadPoolExecutor

    • 核心参数
      • 核心线程数
      • 最大线程数
      • 等待队列最大长度
    • Executors工具类实现的线程池
      • FixedThreadPool:固定线程数,使用无界等待队列,大量任务堆积等待队列中会出现OOM
      • SingleThreadExecutor:线程池只有一个线程,使用无界等待队列,大量任务堆积等待队列中会出现OOM
      • CachedThreadPool:使用的同步队列,不存储等待任务,只要任务进来,就创建线程执行任务,且允许创建的线程数量为 Integer.MAX_VALUE,会出现OOM
      • ScheduledThreadPool:使用的无界的延迟阻塞队列,内容使用的最小堆排列,同样大量任务堆积等待队列中会出现OOM
    • 线程池的拒绝策略
      • 抛出异常之后拒绝任务
      • 将任务回退给调用者,使用调用者的线程来执行任务
      • 直接丢弃掉
      • 丢弃等待队列中的最早进来的未处理的任务请求
    • 线程池中任务进来的判断流程
      • 在这里插入图片描述
      • 可以看出是先判断等待队列是否可以放入,才判断是否是小于最大线程数,然后创建线程
  • 线程池创建核心参数设置

    • 计算密集型
      • 可以将核心线程数设置为 N(CPU 核心数)+1
      • 最大线程数不适合过大,根据实际情况调整
    • IO密集型
      • 设置核心最大进程数为2N
      • 设置最大线程数为4N
  • 线程池的参数调整过程

    • 首先根据上面类型设置理论数
    • 根据实际情况进行压测
    • 根据压测结果进行调整
    • 循环步骤2和3,直到得到最佳效果
  • 线程池尽量不要放耗时任务

    • 线程池本身的目的是为了提高任务执行效率,避免因频繁创建和销毁线程而带来的性能开销。如果将耗时任务提交到线程池中执行,可能会导致线程池中的线程被长时间占用,无法及时响应其他任务,甚至会导致线程池崩溃或者程序假死。

线程间的同步的方式

  • 互斥:
    • 只有拿到互斥对象的线程才能访问资源
    • synchronized 和 ReentrantLock 都是一次只允许一个线程访问某个资源
  • 信号量:
    • 允许多个线程访问资源,当时线程数必须在信号量允许的范围内
    • Semaphore(信号量)可以用来控制同时访问特定资源的线程数量
  • 事件:通过事件通知的方式实现线程同步。

AQS的关系

  • AQS 是抽象队列同步器(AbstractQueuedSynchronizer),是在java.util.concurrent.locks包下
  • AbstractQueuedSynchronizer这个类是一个抽象类,主要是被子类继承可以很方便实现构造出同步器(ReentrantLock 互斥同步和Semaphore 共享同步)
  • 原理:当一个线程请求资源时,通过CAS操作,判断是否允许线程持有资源,如果CAS操作失败,则将当前线程封装为一个node节点,加入一个双向等待队列的尾部(遵循FIFO)。

CAS和AQS的关系

  • CAS(Compare-And-Swap)是一种无锁算法,通过值比较来保证操作的原子性。
  • AQS是一个用来构建线程同步的框架,例如,ReentrantLock和Semaphore等同步器都是基于AQS实现的
  • AQS利用CAS来保证其内部状态(state)的原子性更新。

ThreadLocal

ThreadLocal基本方法

  • 使用ThreadLocal目的:保证线程所修改的属性不被其他线程修改
  • ThreadLocal的方法get的实质是获取当前线程内部的ThreadLocalMap的get方法,key是ThreadLocal,通过hashcode方法计算出ThreadLocal对应的索引下标,然后获取ThreadLocalMap中对应的内容
  • ThreadLocal的方法set的实质是对当前线程内部的ThreadLocalMap进行set,首先判断线程的ThreadLocalMap是否存在,不存在则创建一个并复制到线程内部。set过程中会创建一个ThreadLocal的弱引用作为key进行设置。

ThreadLocal中的软引用引发的问题

  • ThreadLocalMap是Map结构,自然也会发生Hash冲突,HashMap通过拉链发解决的。ThreadLocalMap则是通过开放地址法解决冲突,在计算出的Hash下标的数组中发现已经存在entry,则会向后移动下标,继续找可以存放数据的地方。
  • ThreadLocalMap中每一个key、value都是通过entry存放的,但是由于entry中的key是ThreadLocal的弱引用,所以当ThreadLocal的强引用被赋值null,在下次GC的过程中就会导致ThreadLocal对象被回收,导致entry中key为null,entry也就会被认为是过期数据。
  • 为什么key要使用ThreadLocal的弱引用,而不是直接使用强引用?如果在entry内部ThreadLocal是强引用,在ThreadLocal threadLocal = null;时,相当于ThreadLocal对应的对象已经不能再使用了,但是ThreadLocalMap中取值需要通过ThreadLocal作为key才能get到内容,相当于Map中的数据永远无法获取到,会导致内存泄漏。
  • 而使用ThreadLocal的弱引用,在ThreadLocal强引用被删除,而entry中的key被gc之后就会变为null,通过这个特征就可以在代码中进行回收了。
  • 如何回收,在ThreadLocalMap的set,get方法中都有主动清楚存在entry但是key为null得过期数据的逻辑。
  • 如果一直不调用get,set方法,存过期的entry在就会造成内存泄漏。所以在不适用ThreadLocal时,通过remove主动删除过期数据。

几个锁的对应和理解

  • 公平锁和非公平锁:等待锁的过程中是否有插队的
  • 悲观锁和乐观锁:操作之前是否要上锁
  • 共享锁和互斥锁:一个资源是否可以被多个线程持有

ReentrantLock 的概念

  • ReentrantLock是java.util.concurrent.locks包下的,通过AQS实现的悲观锁
  • 原理:ReentrantLock内部有一个抽象类Sync,这个类继承AbstractQueuedSynchronizer(AQS),同时在ReentrantLock内部对Sync有两种实现,公平锁和非公平锁。生成ReentrantLock对象的过程其实就是构建Sync实例对象的过程。同时上锁也是通过Sync实例对象的lock方法实现的。(这块建议看下源码,很好理解)
  • ReentrantLock 的特性是可重入,这块跟是否是公平锁没关系,在获取锁的时候,会判断持有当前资源的线程是不是当前线程,如果是的话,可以直接获取锁
  • 非公平锁,一个线程进来直接会尝试一下CAS,不成功则进入等待队列。成功则会在等待队列前占有资源(直接越过了排队过程,对等待队列中的其他线程是不公平的)
  • 公平锁,在实例化ReentrantLock 对象的时候,通过传入布尔值,来创建公平锁。线程进来,如果有等待的线程,则先进等待队列排队,依次获取资源。

CountDownLatch 倒计时器

Semaphore 信号量锁

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

贝多芬也爱敲代码

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值