多线程部分基础知识总结

一、死锁原因

资源互斥、请求与保持,不可剥夺、循环等待

二、sleep()和wait()

sleep()方法没有释放锁,方法执行完成后线程会自动苏醒,进入就绪队列。
wait()方法释放了锁,需要其他线程来唤醒。当然也可以使用wait(long timeout)来设置超时自动苏醒。

三、synchronized

1.修饰静态方法
锁是当前类的class对象 ,进入同步代码前要获得当前类对象的锁
2.修饰普通方法
锁是当前实例对象 ,进入同步代码前要获得当前实例的锁
3.修饰方法块
被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,进入同步代码前要获得当前实例的锁
4.修饰类(synchronized(class))
锁是当前类的class对象 ,进入同步代码前要获得当前类对象的锁
底层原理:同步代码块的实现使用的是monitorenter和monitorexit指令,修饰普通方法时使用的是ACC_SYNCHRONIZED标识(没有使用monitorenter和monitorexit指令)。类锁时,常量池中用ACC_STATIC标志了这是一个静态方法,然后用ACC_SYNCHRONIZED标志位提醒线程去竞争monitor。
另外JDK1.6对synchronized做了大量优化(虚拟机层面),如偏向锁,轻量级锁,自选锁,适应性自旋锁、锁消除、锁粗化等技术来减少锁的开销。
参考:

四、ReentrantLock

synchronized给予JVM,而ReentrantLock基于JDK,相比synchronized增加了一些高级功能:
①提供了一种能终端等待锁的线程的机制
②可以指定是公平锁还是非公平锁,而synchronizrd只能是非公平锁
③可实现选择性通知(结合Condition实例)

五、volatile

只保证可见性和有序性,不保证原子性
可见性即每次使用该变量都要到主存中读取(JDK1.2后JAVA才可以将变量保存到本地内存,如寄存器,以前只能从主存读取,并不需要注意可见性)
有序性即禁止指令重排序

六、ThreadLocal

ThreadLocal内部维护的是ThreadLocalMap(ThreadlLocalMap是ThreadLocal的静态内部类),每一个线程有一个ThreadLocalMap,而每一个ThreadLocal就是ThreadLocalMap中的一个Entry。
由于Entry中key是ThreadLocal的弱引用,而value是强引用,所以,在ThreadLocal没有被外部强引用的情况下,在垃圾回收时,key被清理掉但value不会被清理掉,可能出现key为null的情况。ThreadLocalMap已经考虑了这种情况,调用set()、get()、remove()方法时会清理掉key为null的记录。使用完ThreadLocal方法后最好手动调用remove()。

七、引用级别

强引用:强引用就是我们平常最基本的对象引用,如果是强引用,那回收器不会回收带有强引用的对象。即使内存不足抛出OutOfMemoryError异常也不会回收强引用对象。
软引用:一个对象只有软引用,如果内存空间足够情况下垃圾回收器就不会回收它,如果内存空间不够了就会对这些只有软引用的对象进行回收。只要垃圾回收器没有回收,该软引用对象就可以继续被程序使用。
弱引用:弱引用的对象具有更短暂的生命周期,在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。
虚引用:虚引用顾名思义就是形同虚设,虚引用并不决定对象的生命周期,如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。配合引用队列使用,通过不断轮询引用队列获取对象回收事件(相当于可以获得一个对象回收的通知)。
引用队列:在使用引用对象时,通过判断get()方法返回的值是否为null来判断对象是否已经被回收,当这样做并不是非常高效,特别是当我们有很多引用对象,如果想找出哪些对象已经被回收,需要遍历所有所有对象。更好的方案是使用引用队列,在构造引用对象时与队列关联,当gc(垃圾回收线程)准备回收一个对象时,如果发现它还仅有软引用(或弱引用,或虚引用)指向它,就会在回收该对象之前,把这个软引用(或弱引用,或虚引用)加入到与之关联的引用队列(ReferenceQueue)中。如果一个软引用(或弱引用,或虚引用)对象本身在引用队列中,就说明该引用对象所指向的对象被回收了,所以要找出所有被回收的对象,只需要遍历引用队列。

八、线程池

好处:降低资源消耗,提高响应速度,提高线程的可管理性。
通过Executors创建的三种常见线程池种类

  • FixedThreadPool:固定线程数量的线程池
  • SingleThreadPool:只有一个线程的线程池
  • CachedThreadPool:可根据实际情况调整线程数量的线程池

《阿里巴巴开发手册》强制线程池不允许使用Executors创建,而使用ThreadPoolExecutor创建,做一遍更加明确线程池的运行规则,避免资源耗尽的风险。
ThreadPoolExecutor常见参数
corePoolSIze:核心线程数量
maximumPoolSize:当队列中存放的任务达到队列容量后,当前可以同时运行的线程数量变为最大线程数。
workQueue:如果线程池数量达到核心线程数后,新任务就会存放到队列中,队列满后就会将以同时运行的线程数量变为最大线程数。
其他参数
keepAliveTime:当线程池中的数量大于corePoolSize时,如果没有新任务提交,核心线程外的线程不会立即销毁,而是等待keepAliveTime后才会被回收销毁。
unit:keepAliveTime的时间单位
threadFactory:Executor创建新线程时使用
hander:饱和策略
饱和策略
ThreadPoolExecutor.AbortPolicy: 直接抛出RejectedExecutionException来拒绝新任务的处理
ThreadPoolExecutor.CallerRunsPolicy:不会丢弃任何一个请求,但会降低新任务提交速度,可能增加队列容量,影响程序整体性能。
ThreadPoolExecutor.DiscardPolicy:不处理新任务,直接丢弃
ThreadPoolExecutor.DiscardOldestPolicy:丢弃最早未处理的任务请求
原理图示:
在这里插入图片描述

九、Runnable和Callable

Runnable自Java1.0便有,Callable是1.5引入的,以便处理Runnable不支持的用例。Runnable接口不会返回结果或抛出异常,但Callable可以。

十、execute()和submit()

execute()方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池成功执行。
submit()方法用于提交需要返回值的任务,并返回一个Future类型的对象。可以通过Future的get()方法来获取返回值,get()方法会阻塞当前线程直到任务完成,而使用get(long timeout,TimeUnit unit)方法则会阻塞当前线程一段时间后立即返回,即便任务没有执行完成。

十一、JUC包中的4类原子类

基本类型:AtomicInteger、AtomicLong、AtomicBoolean
数组类型:AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray
引用类型:AtomicReference、AtomicStampedReference、AtomicMarkableReference
对象的属性修改类型:AtomicIntegerFieldUpdater、AtomicLongFieldUpdater

十二、AQS

原理图:
在这里插入图片描述
AQS的核心思想:如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并将共享资源设置为锁定状态,如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制AQS是用CLH队列锁实现的,即将暂时获取不到锁的线程加入到队列中。
通俗点,AQS就是基于CLH队列,用volatile修饰共享变量state,线程通过CAS去改变状态符,成功则获取锁成功,失败则进入等待队列,等待被唤醒。
AQS底层使用了模板方法模式。

CLH(Craig,Landin,and Hagersten)队列是一个虚拟的双向队列,虚拟的双向队列即不存在队列实例,仅存在节点之间的关联关系。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值