小周学JAVA—八股八

本文详细探讨了线程的生命周期状态,包括初始、运行、阻塞、等待和终止;解释了死锁的条件及预防策略,以及sleep()和wait()方法的区别。同时,讨论了volatile关键字的可见性和禁止指令重排序,以及乐观锁的优缺点和适用场景。
摘要由CSDN通过智能技术生成

线程的生命周期和状态?

  • NEW: 初始状态,线程被创建出来但没有被调用 start()​ 。
  • RUNNABLE: 运行状态,线程被调用了 start()​等待运行的状态。
  • BLOCKED:阻塞状态,需要等待锁释放。
  • WAITING:等待状态,表示该线程需要等待其他线程做出一些特定动作(通知或中断)。
  • TIME_WAITING:超时等待状态,可以在指定的时间后自行返回而不是像 WAITING 那样一直等待。
  • TERMINATED:终止状态,表示该线程已经运行完毕

死锁的条件、预防死锁与避免死锁

产生死锁的四个必要条件:

  1. 互斥条件:该资源任意一个时刻只由一个线程占用。
  2. 请求与保持条件:一个线程因请求资源而阻塞时,对已获得的资源保持不放。
  3. 不剥夺条件:线程已获得的资源在未使用完之前不能被其他线程强行剥夺,只有自己使用完毕后才释放资源。
  4. 循环等待条件:若干线程之间形成一种头尾相接的循环等待资源关系。

如何预防死锁? 破坏死锁的产生的必要条件:

  1. 破坏请求与保持条件:一次性申请所有的资源。
  2. 破坏不剥夺条件:占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放它占有的资源。
  3. 破坏循环等待条件:靠按序申请资源来预防。按某一顺序申请资源,释放资源则反序释放。破坏循环等待条件。

如何避免死锁?

避免死锁就是在资源分配时,借助于算法(比如银行家算法)对资源分配进行计算评估,使其进入安全状态。

sleep() 方法和 wait() 方法对比

共同点:

两者都可以暂停线程的执行。

区别:

  • 锁:sleep() ​ 方法没有释放锁,而 ​wait() ​ 方法释放了锁 。
  • 作用:wait()​ 通常被用于线程间交互/通信,sleep()​通常被用于暂停执行。
  • 自动苏醒:wait()​ 方法被调用后,线程不会自动苏醒,需要别的线程调用同一个对象上的 notify()​或者 notifyAll()​ 方法。sleep()​方法执行完成后,线程会自动苏醒,或者也可以使用 wait(long timeout)​ 超时后线程会自动苏醒。
  • 类别:sleep()​ 是 Thread​ 类的静态本地方法,wait()​ 则是 Object​ 类的本地方法

为什么 wait() 方法不定义在 Thread 中

wait()​ 是让获得对象锁的线程实现等待,会自动释放当前线程占有的对象锁。每个对象(Object​)都拥有对象锁,既然要释放当前线程占有的对象锁并让其进入 WAITING 状态,自然是要操作对应的对象(Object​)而非当前的线程(Thread​)。

类似的问题:为什么 ​sleep() ​ 方法定义在 ​Thread​ 中?

因为 sleep()​ 是让当前线程暂停执行,不涉及到对象类,也不需要获得对象锁

可以直接调用 Thread 类的 run 方法吗?

new 一个 Thread​,线程进入了新建状态。调用 start()​方法,会启动一个线程并使线程进入了就绪状态,当分配到时间片后就可以开始运行了。 start()​ 会执行线程的相应准备工作,然后自动执行 run()​ 方法的内容,这是真正的多线程工作。

但是,直接执行 run()​ 方法,会把 run()​ 方法当成一个 main 线程下的普通方法去执行,并不会在某个线程中执行它,所以这并不是多线程工作。

总结:调用 ​start() ​ 方法方可启动线程并使线程进入就绪状态,直接执行 ​run() ​ 方法的话不会以多线程的方式执行。

volatile关键字的作用

可见性:

  • 每次使用它都到主存中进行读取
  • 但不能保证数据的原子性。synchronized​ 关键字两者都能保证。

禁止指令重排序

  • 插入特定的 内存屏障 的方式来禁止指令重排序

乐观锁存在的问题

  • ABA 问题:解决思路是在变量前面追加上版本号或者时间戳

  • 循环时间长,CPU开销大
  • 高并发的场景下,乐观锁相比悲观锁来说,不存在锁竞争造成线程阻塞,也不会有死锁的问题,在性能上往往会更胜一筹。但是,如果冲突频繁发生(写占比非常多的情况),会频繁失败和重试,这样同样会非常影响性能,导致 CPU 飙升。
  • 只能保证一个共享变量的原子操作
  • CAS 只对单个共享变量有效,当操作涉及跨多个共享变量时 CAS 无效。但是从 JDK 1.5 开始,提供了AtomicReference​类来保证引用对象之间的原子性,你可以把多个变量放在一个对象里来进行 CAS 操作.所以我们可以使用锁或者利用AtomicReference​类把多个共享变量合并成一个共享变量来操作。 
  • 悲观锁通常多用于写比较多的情况下(多写场景,竞争激烈),繁失败和重试影响这样可以避免频性能,悲观锁的开销是固定的。不过,如果乐观锁解决了频繁失败和重试这个问题的话(比如LongAdder​),也是可以考虑使用乐观锁的,要视实际情况而定。
  • 乐观锁通常多于写比较少的情况下(多读场景,竞争较少),这样可以避免频繁加锁影响性能。不过,乐观锁主要针对的对象是单个共享变量(参考java.util.concurrent.atomic​包下面的原子变量类)

乐观锁一般会使用

  • 版本号机制

  • CAS 算法实现。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值