多线程和并发 校招知识点总结

目录

多线程

实现线程的方式(执行任务的方式)

停止线程的方法

线程间通信

线程池

线程池的工作流程

七大参数

Executors提供的4种线程池 

JUC包

原子操作类

读写锁

CAS自旋锁的三个问题、解决方案

并发集合

ThreadLocal

其他

synchronized 和 volatile

sleep和wait的区别

 



多线程

实现线程的方式(执行任务的方式)

  • 继承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:独占锁+优先级队列,按过期优先的阻塞队列

ConcurrentHashMap

ThreadLocal

每个线程内部有个独有的ThreadLocalMap,ThreadLocal通过在这个Map上设置值,实现线程局部变量

内存泄漏:

因为ThreadLocalMap是与Thread相同的生命周期,就可能作为Key的ThreadLocal对象先被回收了,然后存在Thread里的值对象无法被回收,造成泄漏

其他

synchronized 和 volatile

sleep和wait的区别

 所属类区别1区别2区别3
sleep()Thread的静态方法不让出对象锁可以不在同步块中使用让出CPU,指定时间自行恢复
wait()Object的成员方法让出对象锁必须在同步块中使用必须被notify唤醒

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值