【面试必背】线程篇

线程篇

进程与线程

进程:程序运行起来的状态,操作系统分配资源的基本单位
线程:进程中不同的执行路径,操作系统资源调度的最小单位

并发并行

并发:一个线程在一段时间内分别做N件事
并行:一个线程同时处理N件事
串行:一个线程按顺序执行

线程通信

共享内存

volatile

  1. 保证内存可见性
    共享的变量会放入到主内存中,每个线程都有独立的工作内存保留被线程使用变量的副本,线程对变量的修改没有更新到主内存中,导致其他线程拿不到最新变量
    使用volatile修饰的变量,线程操作变量时会从主内存拷贝到工作内存,修改完成后再写回主内存,通过CPU总线嗅探机制告知其他变量副本已失效,需要从主内存读取
  2. 禁止指令重排
    编译为字节码后,为了效率,并不一定会按顺序执行,加了volatile,会按照顺序执行

消息传递

wait/notify

管道流

生命周期

New新建
Runnable就绪
start()
Running运行
获取到CPU资源
Blocked阻塞
wait、sleep
Terminal销毁
线程执行完或异常终止

创建

继承Thread
实现Runnable
实现Callable
实现Callable,使用FutureTask包装Callable,get()获取返回值
使用线程池,Executor

线程池

作用

减少创建和销毁线程的次数,让线程可以多次使用

ExecutorService参数

corePoolSize核心线程数

CPU密集型
业务以CPU计算为主,CPU消耗大,运算快,线程数设置CPU+1,避免频繁上下文切换
IO密集型
业务以磁盘或IO为主,IO速度比CPU慢很多,为避免线程空等IO的情况,将线程数设置为CPU*2

maximumPoolSize最大线程数
keepAliveTime非核心线程超时时间
timeUnit时间单位
workQueue任务队列
ThreadFacotry创建线程方式
handler拒绝策略

AbortPolicy,默认,丢弃任务并抛异常
DiscardPolicy,静默丢弃任务,不抛异常
DiscardOldestPolicy,丢弃最前面的任务,重新提交拒绝的任务
CallerRunsPolicy,任务被拒绝后,由调用线程执行此任务

执行顺序

线程数量未达到核心线程数,创建线程,核心线程数一般与CPU数一致

达到核心线程,放入队列等待
队列已满,未超过最大线程数,新建线程
队列已满,并超过最大线程数,执行拒绝策略

四种

可缓存线程池
	线程数无限制
	有空闲线程则复用,没有则创建
定长线程池
	控制线程最大并发数
定时线程池
	定时及周期性任务
单线程池

ThreadLocal

是一种线程隔离机制,多线程环境下对共享变量访问的安全性。
在多线程环境下一般解决方案是对共享变量加锁,保证同一时刻只能有一个线程对共享变量更新,基于happends-before锁监视器规则,能够保证数据修改之后对其他线程是可见的,但是加锁会造成性能下降。
ThreadLocal是空间换时间的策略,每个线程都有一个容器存储共享变量的副本,对共享变量的更新在副本中进行,这样既解决了线程的安全问题,又避免了多线程竞争锁的一个开销。
ThreadLocal的一个具体实现原理在Thread类有一个成员变量ThreadLocalMap,用来存储当前线程的共享变量副本,不会影响全局共享变量的值,从而实现数据隔离

死锁

两个或两个以上的线程由于竞争资源或彼此通信导致相互阻塞,若无外力作用,无法继续运行

乐观锁CAS

Compare And Swap,比较并交换,每次取数据都认为不会被修改,不会上锁,在修改数据时,会再去读下数据有没有被更新,被更新了取新的值,AtomicInteger是用CAS实现的
ABA问题,数据原值为A,改为B后,又被改为A了,数据被修改过,加版本号控制

自旋锁

持有锁的线程在短时间内会释放资源,等待竞争锁的线程不需要做用户与内核态的切换进入阻塞挂起状态,可以通过CPU自旋的方式,每次询问锁是否被释放,释放后立即获取锁
并发量大时,会出现忙循环,CPU消耗大

悲观锁

认为数据会被更改,读写都加锁

Synchornized

特性
原子性
	volatile不具备原子性
可见性
有序性
可重入
用法
修饰静态方法
	静态方法+sync
修饰方法
	方法+sync
修饰代码块
	sync(this/object)
实现原理
同步代码块
	反编译后,在代码块前后加上两个指令,monitorenter和monitorexit,一个线程来时发现对象头的锁状态是01,也就是无锁状态,会尝试获取锁对象,锁对象和另外一个对象monitor监视器关联,将monitor锁定器+1,并且将monitor指针写入到一个对象头中,修改锁对象标志位为10,重量级锁的一个标志位,以此完成换锁过程
	锁是可重入的,不需要每次进来后获取这个锁,让锁记录+1即可,加锁完后当其他线程来后检查到monitor监视器锁的计数器不为0,就会在monitor监视状态下等待去竞争这个锁,执行结束后会释放锁,计数器为0时,线程可竞争锁
同步方法
	ACC_SYNCHRONIZED标志位,相当于一个flag,当JVM检测到有这个flag时,先获取monitor,获取成功后才能执行方法体,方法执行完后再释放monitor
1.7锁升级
创建锁,锁处于无锁状态
有线程执行时,升级为偏向锁,根据线程ID判断是否为同一个,是同一个直接获取锁,不是同一个升级为轻量级锁
超过一个线程时,升级为轻量级锁,CPU自旋
线程自旋超过10次,升级为重量级锁
	线程挂起,将控制权交给操作系统,由操作系统负责线程间的调度和线程状态的变更

AQS

juc包中多个组件的底层实现,CountDownLatch/CyclicBarrier/Semaphore都用到了AQS,AQS提供了两种锁机制,排他锁(多个线程中只有一个线程可以获取锁资源)和共享锁(同一时刻允许多个线程同时获取锁资源)
互斥变量的设计,以及多线程同时更新互斥变量线程的安全性
未竞争到锁线程的等待,以及竞争到锁资源,锁的唤醒
锁竞争的公平性和非公平性
int类型的state用来记录锁竞争的状态,0没有锁竞争,大于0有线程正在持有锁资源
线程获取锁资源时会先判断state是否=0,是否无锁状态,如果是,更新为1,表示占用到锁,多个线程同时操作,会出现安全问题,AQS采用CAS机制,保证state互斥变量更新的原子性,未获取到锁的线程,通过unsafe类的park方法进行阻塞,阻塞的线程按照先进先出原则加入到一个双向链表的结构中,当获取锁资源的线程释放锁之后,会从双向链表头部去唤醒下一个等待的线程,再去竞争锁
公平锁,AQS需要判断双向链表中是否有阻塞的线程,有则排队等待
非公平锁,不管双向链表是否有阻塞线程,都会直接尝试更改互斥变量state,假设在一个临界点,获取锁的线程释放锁,state为0,当前线程正好可以将state修改为1,表示可以拿到锁

CountDownLatch

一个线程等待其他n个线程执行完后才能执行下一步,用计数器实现,初始值为线程数,每执行完一个线程-1,为0时线程才能执行

计数器是一次性的

CyclicBarrier

循环栅栏,所有线程到达栅栏,才能下一步,可循环使用

Semaphore

令牌,限定执行次数,超过限定次数后不能再处理
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值