多线程

多线程
线程
进程与线程
进程包含了线程。线程的上下文切换成本比进程的低。
不同进程使用不同的内存空间,同一个进程下的所有线程共享内存空间。并行与并发
并行:同一时间内多个任务同时进行。
并发:同一时间内多个任务交替进行。线程同步
- 目的:保护共享资源 ,防止多个线程同时修改数据导致的竞态条件
- 核心思想:通过限制对共享资源的并发访问,确保线程安全
- 常见实现方式:synchronized、Lock、Semaphore、CountDownLatch、CyclicBarrier、原子操作
线程通信
- 目的:协调线程间的执行顺序 ,让线程根据特定条件暂停或唤醒
- 核心思想 :通过传递信号或数据,让线程在特定条件下等待或继续执行
- 常见实现方式:wait/notify、Condition、BlockingQueue
创建线程的方式
- 继承Thread类
- 实现Runnable接口
- 实现run()方法,无返回值。
- 不能抛出异常(检查型异常)。
- 实现Callable接口
- 实现call()方法,有返回值。
- 配合FutureTask使用。
- 可以抛出异常。
- 使用线程池
线程状态
- 新建
- 就绪
- 运行
- 阻塞:等待资源、等待信号、等待IO操作完成。
- 等待:线程调用wait()方法。
- 睡眠:线程调用sleep()方法。
- 终止
notify和notifyAll的区别
- notify:随机唤醒一个正在等待的线程。
- notifyAll:唤醒所有正在等待的线程。
wait和sleep的区别
- wait
- Object的成员方法。
- 需要先获得目标对象的锁。
- 执行后会释放锁。
- sleep
- Thread的静态方法。
- 不需要获得锁。
- 不释放锁。
- wait
如果停止一个正在运行的线程?
interrupt()方法- 如果线程处于阻塞状态,则抛出InterruptedException异常。
- 如果线程处于运行状态,则设置中断标志。
synchronized关键字的底层原理
- 由Monitor实现,实现的锁属于重量级锁。
- 在同步块前后插入了
monitorenter
和monitorexit
- Monitor结构
- WaitSet:关联了调用了wait()方法的线程。
- EntryList:关联了执行到synchronized代码块的线程(没有持有锁的线程)
- Owner:当前持有锁的线程。
- 锁升级:对象头中的MarkWord部分
- 无锁
- 偏向锁
- 轻量级锁
- 重量级锁:使用Monitor实现,性能较低。
JMM(Java内存模型)
JMM将内存分为主内存和工作内存。线程与线程之间是隔离的,交互需要通过主内存。CAS
CAS(比较再交换),体现的是乐观锁的思想,在无锁状态下保证线程操作数据的原子性。volatile
- 禁止JIT对volatile修饰的变量进行优化,让一个线程对共享变量的修改对另一个线程可见
- 禁止指令重排
AQS
AQS(抽象队列同步器)是多线程中的队列同步器,是一种锁机制,作为一个基础框架使用,ReentrantLock通过AQS实现。
内部维护了一个先进先出的双向队列,存储的是排队的线程。
对state属性进行CAS操作,保证线程操作数据的原子性。ReentrantLock
主要使用CAS和AQS实现,默认为非公平锁。
特点:可中断、可以设置超时、可以设置公平锁、可重入、支持多个条件变量。synchronized和Lock的区别
- synchronized
- 关键字,源码jvm中,使用C++实现
- 自动释放
- 悲观锁、互斥、同步、可重入
- Lock
- 接口,源码在jdk中,使用Java实现
- 需要手动释放
- 悲观锁、互斥、同步、可重入
- 提供公平锁、可打断、可超时、多条件变量
- synchronized
死锁的产生条件
- 互斥、不剥夺、请求和保持、循环等待
- 死锁诊断:jps、jstack
怎么保证多线程的执行安全?
- 原子性:synchronized、Lock
- 内存可见性:volatile、synchronized、Lock
- 有序性:volatile
线程池
线程池的作用
线程池可以减少频繁的创建线程和销毁线程带来的性能损耗。线程池的核心参数
- corePoolSize:核心线程数。
- maximumPoolSize:最大线程数。
- keepAliveTime:线程空闲时间。
- unit:时间单位。
- workQueue:工作队列
- threadFactory:线程工厂。
- handler:拒绝策略。
线程池的执行原理
- 如果核心线程数未满,则创建核心线程。
- 如果任务队列未满,则添加到任务队列中。
- 如果总线程数小于maximumPoolSize,则创建非核心线程。
- 如果总线程数大于或等于maximumPoolSize,则执行拒绝策略。
- AbortPolicy:直接抛出异常。(默认策略)
- DiscardPolicy:直接丢弃。
- DiscardOldestPolicy:丢弃队列中最旧的任务。
- CallerRunsPolicy:由调用者线程处理该任务。
线程池的阻塞队列
- ArrayBlockingQueue:基于数组的先进先出队列。
- 强制有界
- 提前初始化
- 使用一个锁
- LinkedBlockingQueue:基于链表的先进先出队列。
- 无界,可以有界。
- 插入时初始化
- 首尾共两个锁
- SynchronousQueue:
- ArrayBlockingQueue:基于数组的先进先出队列。
如何确定核心线程数?
- 高并发、任务执行时间短
- 核心线程数:N+1。减少线程上下文切换
- IO密集型
- 核心线程数:2N+1
- CPU密集型
- 核心线程数:N+1
- 高并发、任务执行时间短
线程池的种类
Executors类的静态函数。
不建议使用,因为等待队列长度默认为Integer.MAX_VALUE
,可能会堆积大量请求,导致内存溢出。- newFixedThreadPool:固定大小线程池。
- newSingleThreadExecutor:单线程线程池。
- newCachedThreadPool:可变大小线程池。
- newScheduledThreadPool:定时线程池。
线程池的使用场景
- 批量导入
- 数据汇总
- 多线程获取不同数据
- 异步任务
- 统计数据
- 保存日志
CountDownLatch
- 设置目标数量,主线程调用await()方法,等待其他线程。
- 其他线程执行完,调用countDown()方法。
- 数量为0时,主线程继续执行。
- 不可重复使用。
CyclicBarrier
- 设置目标数量。
- 其他线程执行到某个阶段,调用await()方法,等待其他线程。
- 数量到达目标数量时,等待的线程继续执行。
- 可以重复使用。
semaphore信号量
- acquire()
- release()
ThreadLocal
- 线程内部存储类,实现资源对象的线程隔离,避免线程争用引发的安全问题。
- 每个线程维持一个ThreadLocalMap对象,以ThreadLocal对象为key,保存数据。
- 内存泄露问题:ThreadLocalMap中的key是对ThreadLocal的弱引用。而value是强引用,若未调用remove(),不会被GC回收。
锁的类型
- 乐观锁和悲观锁
- 公平锁和非公平锁
- 互斥锁和共享锁
- 可重入锁和非可重入锁
- 自旋锁、分段锁、死锁