目录
多线程
实现线程的方式(执行任务的方式)
- 继承Thread类,覆盖run方法,start启动线程,调用run方法
- 实现Runnable任务接口的run方法,新建Thread时传入任务对象
- 通过Executor中的Callable和Future实现,Callable返回一个Future,表示有返回值的任务
- 线程池
Thread和Runnable方式的区别:一种基于继承,一种基于组合--组合更加灵活、且任务可复用
Callable和Runnable方式的区别:Callable对比Runnable功能更强大,能有返回值、且可抛出异常
停止线程的方法
- 在循环体里用一个标志位,条件达到自然停止
- stop()方法,已废弃,会把finally等都停掉
- Interrupt(),判断中断了就结束(类似标志位)
线程间通信
- volatile一个共享变量
- wait/notify
- lock
- JUC下的工具类
线程池
场景:
- 我给大家讲个故事,小牛准备开一家新店,开业前期需要进行宣传,于是他贴出小广告打算招几个临时工,主要是负责发传单、开业时迎宾,
- 面试工作进行了三天,开业当天生意很火爆,小牛也很高兴,但是最后有一件小事令他有点不快, 临时工乙居然跟他讨价还价,明明说好的价格,他非说他宣传的好所以生意火爆,让自己多掏点钱,碍于店里顾客众多,不想耽误生意,只能多给点让他走人
- 小牛总结了自己在临时工的开销: 招聘的时间成本+工钱+解雇时的波折
- 一年后,小牛又想起了这事,觉得这是个商机,恰巧手上有比闲钱,于是开办了一家专门外派临时工的公司,长期雇佣一些员工在当地承接临时性的工作,他认为自己所提供的业务能给商家省下招聘、解雇时的开销
- 可能某些看客已经看出来了,这家公司就是线程池
- 与其他对象池向池借用、归还对象不同,线程池不会将线程取出,而是取放任务到任务队列中,然后池中的工作线程从队列中取任务来执行
- 所以这里可以看出,一个线程池需要一个存储线程的地方、一个存储任务的队列、一个负责在线程和任务间的协作的调度器
作用: 复用、异步、线程的可管理性
线程池的工作流程
用户线程提交任务 if(任务队列未满){ 任务入队列, 返回 } if(有空闲的核心工作线程){ 拉一个空闲线程出来, 让它执行该任务, 返回 } if(核心工作线程数目<线程池支持的最大线程数目){ 新建一个核心线程, 让它执行该任务, 返回 } 走到这,说明线程池已经达到线程数目了,且都在工作中,所以只能残忍的 执行拒绝策略, 返回
七大参数
- corePoolSize: 核心线程数目
- maximunPoolSize: 线程池允许同时执行的最大线程数
- keepAliveTime: 多余的线程的最大空闲时间,超过核心线程数目的多余线程,在存活超过这个时间后即行销毁
- unit: keepAliveTime的单位
taskQueue: 任务队列
- threadFactory: 线程工厂
- handler: 拒绝策略,当任务队列满了且线程>= 最大线程数,如何对待新提交的任务
- 策略1:直接丢弃
- 策略2:直接丢弃,并抛RejectedExecutionException异常
- 策略3:丢弃队首的任务,重新提交任务
- 策略4:让提交任务的线程来执行任务
Executors提供的4种线程池
//线程数目<=nthreads //keepAliveTime为0 非核心线程立刻回收,核心线程永不回收,设置回收时超时时间为0会报异常 newFixedThreadPool = new ThreadPoolExecutor(nthread,nthreads,0L,...) LinkedBlockingQueue newSingleThreadPool = new ThreadPoolExecutor(1,1,0L, ...) newCachedThreadPool = new ThreadPoolExecutor(0,Integer.MAX_VALUE,60L,Second ,...) //指定延迟、定时执行任务的线程池 new ScheduledThreadPoolExecutor(corePoolSize); DelayedWorkQueue
前三种线程池的主要区别就是线程数,过期时间
- Fixed:线程数目固定的线程池,core=max,队列满时不会新加线程,aliveTime=0时,不回收核心线程
- Single:单线程,其余类比Fixed
- Cached:队列满时一直创线程,空闲的线程超时后全部回收
JUC包
原子操作类
JUC提供的原子性操作类,基于‘非阻塞算法CAS’实现,性能比用锁实现原子操作更好,原理大致相同
AtomicLong :原子性递增/减类,内部使用Unsafe实现
- Unsafe类提供了硬件级别的原子操作,是native方法,使用JNI访问本地C++库
LongAddr
- 目的:解决多线程CAS访问原子变量时,大多数竞争失败的线程们空自旋,浪费CPU
- 方法:将原子变量 变为 base+Cell数组,值是base+数组中各个Cell的值,这样同样多的线程竞争多个Cell资源,提高并发性
- 注:读取值时不加锁,是粗略值
LongAccumulator: 允许自定义累加规则
锁
public class Node{ int waitStatus;//挂起状态 Thread thread; Node prev,next; } public class AQS{ int state; Node head,tail;//阻塞队列 void acquire(int arg){}; void tryAcquire(int arg);//抽象方法 boolean release(int arg){}; boolean tryRelease(int arg); }
AQS(Abstract Queued Synchronizer抽象同步队列)是锁的底层框架,通过赋予state不同的含义,并重写tryAcquire和tryRelease方法来操作state,以实现 不同种类的锁
- exclusiveOwnerThread 当前持有锁的线程
- state:在几种常见锁中的含义
- ReentrantLock可重入锁(默认非公平):表示重入次数
- ReentrantReadWriteLock:低十六位表示读锁重入次数, 高十六位表示写锁重入次数
- semaphore,表示可用信号量的个数
- CountDownLatch,表示计数器的值
- head,tail 阻塞队列(Node封装了线程)
锁的目的是为了同步多线程对共享资源的访问,可以打个比方,几个哥们凑钱买了一个软件的VIP会员账户,这个软件允许这个账户同时在多台电脑登陆,但是一旦超过这个数目,就会导致其他电脑上的下号,突然下号会给他们带来不便,于是他们建了一个QQ群来进行沟通,他们为了省事,用数字来表示账户可登数目,如果你登录了,则发送这个数目-1到群里,下号则让它加1,于是小伙伴们合作愉快
- 这个故事里,那个数就被赋予了特殊的含义,表示资源的可用数目,
- 数表示0,1时就是表示资源时独占的,1是资源已被占用,相当于已被加锁
- 数表示正数时可以表示资源数目,也可以表示重复访问资源次数
这就是锁实现的原理,通过赋予AQS的state变量不同的含义,并重写tryAcquire和tryRelease方法来定义对state操作,以实现 不同种类的锁
读写锁
适应场景:读多写少场景,读写分离,允许多个线程获取读锁
class HoldCount{//线程,持有锁数 count, threadId; } firstReader 第一个获取读锁线程 firstReaderHoldCount 第一个获取读锁线程的可重入次数 cachedHoldCounter 最后一个获取读锁线程的可重入次数 exclusiveOwnerThread 当前持有写锁的线程 readHolds 是ThreadLocal,记录除第一个外其他线程获取读锁次数 state 低十六位表示读锁重入次数,ReadLock 高十六位表示写锁重入次数 CAS操作state实现读写分离 允许一个线程先获取写锁 再获取读锁
CAS自旋锁的三个问题、解决方案
- ABA:MVCC
- 自旋等待时间过长:JVM支持处理器的pause指令,延迟流水线执行指令,避免消耗过多CPU资源
- 只能针对一个变量:AtomicReference类来保证引用对象之间的原子性,可将多个变量组合成一个对象
并发集合
CopyOnWriteArrayList:基于写时复制的策略,进行写操作时,先将数组复制一份,再对复制数组进行写操作,最后替换掉原先的数组
- 好处:读不加锁;坏处:频繁写的话会导致大量的数组复制
- 操作:
- synchronized(lock){
//复制数组
newArray = Array.copyOf(array, newLength);
//对复制数组进行操作
//用新数组替换
setArray(newArray);
}- Get或迭代器方法会产生弱一致性,如Get(index)方法 return elementAt(getArray(), index);
- 因为它分成了两步,如果其他线程在它拿到array后又修改了array,就会导致不一致
并发队列
- ConcurrentLinkedQueue:CAS无阻塞、链表实现
- LinkedBlockingQueue:独占锁+链表实现
- ArrayListBlockingQueue:独占锁+有界数组的阻塞队列
- PriorityBlockingQueue:独占锁+平衡二叉树
- DelayQueue:独占锁+优先级队列,按过期优先的阻塞队列
ThreadLocal
每个线程内部有个独有的ThreadLocalMap,ThreadLocal通过在这个Map上设置值,实现线程局部变量
内存泄漏:
因为ThreadLocalMap是与Thread相同的生命周期,就可能作为Key的ThreadLocal对象先被回收了,然后存在Thread里的值对象无法被回收,造成泄漏
其他
synchronized 和 volatile
sleep和wait的区别
所属类 | 区别1 | 区别2 | 区别3 | |
sleep() | Thread的静态方法 | 不让出对象锁 | 可以不在同步块中使用 | 让出CPU,指定时间自行恢复 |
wait() | Object的成员方法 | 让出对象锁 | 必须在同步块中使用 | 必须被notify唤醒 |