多线程

99 阅读6分钟
线程的创建方式
  • implements runable
  • implements callable
  • extends thread
  • jdk线程池、spring线程池 ...
线程的状态
  • 新建 new
  • 就绪 start()
  • 运行 获得到时间片执行run()
  • 阻塞
    • blocking 等待获取到排他锁
    • Waiting 无限期等待
进入方法退出方法
没有设置 Timeout 参数的 Object.wait() 方法Object.notify() / Object.notifyAll()
没有设置 Timeout 参数的 Thread.join() 方法被调用的线程执行完毕
LockSupport.park() 方法-
    • Timed Waiting 限期等待
进入方法退出方法
Thread.sleep() 方法时间结束
设置了 Timeout 参数的 Object.wait() 方法时间结束 / Object.notify() / Object.notifyAll()
设置了 Timeout 参数的 Thread.join() 方法时间结束 / 被调用的线程执行完毕
LockSupport.parkNanos() 方法-
LockSupport.parkUntil() 方法-
  • 死亡 可以是线程结束任务之后自己结束,或者产生了异常而结束。
多线程安全问题的产生
  • 可见性【缓存一致性】

    • cpu缓存引起
  • 有序性【指令重排序】

    • 编译器优化的重排序。编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序。
    • 指令级并行的重排序。现代处理器采用了指令级并行技术(Instruction-Level Parallelism, ILP)来将多条指令重叠执行。如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。
    • 内存系统的重排序。由于处理器使用缓存和读 / 写缓冲区,这使得加载和存储操作看上去可能是在乱序执行
  • 原子性【非原子操作】

    • 分时复用线程切换)引起,由于CPU分时复用(线程切换)的存在,线程1执行了第一条指令后,就切换到线程2执行,假如线程2执行了这三条指令后,再切换回线程1执行后续两条指令。一组指令的执行会被打断,转而去执行与上一组指令相关变量的指令。
如何解决原子性问题?
  • atomic 的类利用cas实现原子操作
  • 通过synchronized、reentrantLock等加锁,保证代码段只有一个线程执行,保证操作的原子性
如何解决可见性?
  • volitile 关键字声明共享变量,通过禁止指令重排序和强制刷新cpu缓存来实现可见性、有序性
  • 通过synchronized、reentrantLock等加锁,在锁释放的时候会将修改的值刷新到主内存
如何解决有序性?
  • volitile 关键字声明共享变量,通过禁止指令重排序(内存屏障)和强制刷新cpu缓存(lock前缀)来实现可见性、有序性
  • 通过synchronized、reentrantLock等加锁,保证代码段只有一个线程执行,保证操作的有序性
volatile 有序性和可见性详解?
  • volatile 变量的写操作 之前 插入写屏障,保证写之前的所有指令都执行完毕;读操作之后插入读屏障,保证读操作之后的所有指令都不会被重新排序到读操作之前,保证有序性。
  • volatile 关键字会禁止线程将变量存储在线程本地缓存(cpu缓存中)中,而是直接从主内存中读取和写入,保证可见性。
Happens-Before 8大规则?
  • 单一线程原则(Single Thread rule),在一个线程内,在程序前面的操作先行发生于后面的操作。
  • 管程锁定规则(Monitor Lock Rule),一个 unlock 操作先行发生于后面对同一个锁的 lock 操作。
  • volatile变量规则(Volatile Variable Rule),对一个 volatile 变量的写操作先行发生于后面对这个变量的读操作。
  • 线程启动规则(Thread Start Rule),Thread 对象的 start() 方法调用先行发生于此线程的每一个动作(run中的操作)。
  • 线程加入规则(Thread Join Rule),Thread 对象的结束先行发生于 join() 方法返回。
  • 线程中断规则(Thread Interruption Rule),对线程 interrupt() 方法的调用先行发生于被中断线程的代码检测到中断事件的发生,可以通过 interrupted() 方法检测到是否有中断发生。
  • 对象终结规则(Finalizer Rule),一个对象的初始化完成(构造函数执行结束)先行发生于它的 finalize() 方法的开始。
  • 传递性(Transitivity),如果操作 A 先行发生于操作 B,操作 B 先行发生于操作 C,那么操作 A 先行发生于操作 C。
线程安全的实现方案?
  • 互斥同步 synchronized 和 ReentrantLock。
  • 非阻塞同步 cas
  • 无同步方案
    • 栈封闭 方法中没有成员变量 都是局部变量
    • 线程本地存储 threadLocal
    • 可重入代码(Reentrant Code) 可重入代码有一些共同的特征,例如不依赖存储在堆上的数据和公用的系统资源、用到的状态量都由参数中传入、不调用非可重入的方法等。
线程池的核心参数?
  • coresize 核心线程数
  • maxsize 最大线程数
  • worker queue 工作队列
  • 拒绝策略
  • keepalive time 空闲时间大小
  • keepalive timeunit 空闲时间单位
  • threadfactory 线程工厂
线程池提交任务流程?
  • 当前线程数小于核心线程,创建核心线程数执行任务
  • 当前线程数大于核心线程,工作队列没满则将任务加入队列,满了的话,当前线程数小于最大线程数,则创建非核心线程执行任务,大于最大线程数则执行拒绝策略。
线程池的状态?
  • running 【初始状态,在此状态下能够接收新任务,以及对已经添加的任务进行处理;】
  • shutdown【通过调用shutdown方法,线程池转成SHUTDOWN状态。此时不再接收新任务,但是能处理已经添加的任务】
  • stop【通过调用shutdownNow方法,线程池转成STOP状态。此时不再接收新任务,不处理已经添加的任务,并且会中断正在处理的任务;】
  • tidying【当线程池中所有的任务已经终止了,任务数量为0并且阻塞队列为空的时候,会进入到TIDYING状态。此时会调用一个钩子方法terminated,它是一个空的实现,可以供调用者覆写;】
  • teminated【线程池彻底终止的状态。当线程池处于TIDYING状态时,执行完terminated方法后,就会进入到该状态。】
jdk线程池的拒绝策略?
  • CallRunsPolicy 由主线程执行
  • AbortPolicy 抛出拒绝任务异常
  • DiscardPolicy 拒绝任务,不抛出异常
To C端的接口,线程池的队列选择?核心线程和最大线程?
  • 使用同步队列SynchronousQueue
  • 使用线程池并发处理任务最大线程数和核心线程数,根据Runtime.getRunTime().availableProcessors()获取core数
  • 决绝策略使用callerRunsPolicy
执行线程池的shutdown和shutdownnow的区别?
  • shutdown 不再接受新的任务,正在执行的任务和队列中的任务正常执行
  • shutdownnow 不在接受新的任务,正在执行任务打断,队列中的任务丢弃
有哪些方式可以控制线程的执行顺序?
  • thread.join()
  • reentrantLock + conditon(await+signal)
  • countDownLanch
  • cyclicBarrier
  • wait() \notify\notifyAll 配合synconized关键字
  • thread.park() + thread.unpark()