《JAVA并发编程的艺术》 第四章 复习

本文介绍了Java中的多线程概念,包括线程的优先级设置、六种线程状态,以及Daemon线程的特性。强调了线程安全问题,如中断、避免使用过期的 suspend()、resume() 和 stop() 方法,推荐使用等待/通知机制和synchronized关键字确保线程同步。此外,讨论了线程间通信的实现,如volatile和synchronized关键字的作用,以及ThreadLocal在减少资源冲突中的应用。最后,给出了线程池技术和等待超时模式在实际问题中的应用,强调了线程池的重要性和等待超时模式的灵活性。

第四章 JAVA并发编程基础

线程简介

1.java内部自身引入多线程,使用多线程的原因:
  • 更多的处理器核心
  • 更快的响应时间
  • 更好的编程模型
2.线程可以设置优先级,优先级范围为1-10(默认5)
  • 说明:

    • Java中可以为线程指定优先级,范围是1~10。但并不是所有的操作系统都支持10级的优先级划分。Java只是给操作系统提供一个优先级的参考,具体的线程在操作系统执行先后的顺序还是由操作系统决定。

    • Java默认的线程优先级是5,线程的优先级在线程调度之前设定,线程的执行顺序由调度程序决定。

    • 通常情况下,高优先级的线程是要比低优先级的线程有更高的几率优先执行,注意是更高的几率,也就是说不一定会 优先执行。

    • 设置线程的优先级通过Thread类的setPriority()方法设置。

3.线程的状态
  • java线程层面线程状态:(六种)

    • 初始(NEW):新创建了一个线程对象,但还没有调用start()方法。

    • 运行(RUNNABLE):Java线程中将就绪(ready)和运行中(running)两种状态笼统的称为“运行”。
      线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取CPU的使用权,此时处于就绪状态(ready)。就绪状态的线程在获得CPU时间片后变为运行中状态(running)。

    • 阻塞(BLOCKED):表示线程阻塞于锁。

    • 等待(WAITING):进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)。

    • 超时等待(TIMED_WAITING):该状态不同于WAITING,它可以在指定的时间后自行返回。

    • 终止(TERMINATED):表示该线程已经执行完毕。

  • 操作系统层面线程状态:(五种)

    • 新建状态(New): 线程对象被创建后,就进入了新建状态。例如,Thread thread = new Thread()。
    • 就绪状态(Runnable): 也被称为“可执行状态”。线程对象被创建后,其它线程调用了该对象的start()方法,从而来启动该线程。例如,thread.start()。处于就绪状态的线程,随时可能被CPU调度执行。
    • 运行状态(Running): 线程获取CPU权限进行执行。需要注意的是,线程只能从就绪状态进入到运行状态。
    • 阻塞状态(Blocked): 阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
      • 等待阻塞 – 通过调用线程的wait()方法,让线程等待某工作的完成。
      • 同步阻塞 – 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态。
      • 其他阻塞 – 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
    • 死亡状态(Dead): 线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
4.Daemon线程
  • 什么是Daemon(守护)线程?它有什么意义?

所谓后台(daemon)线程,是指在程序运行的时候在后台提供一种通用服务的线程,并且这个线程并不属于程序中不可或缺的部分。因此,当所有的非后台线程结束时,程序也就终止了,同时会杀死进程中的所有后台线程。

反过来说, 只要有任何非后台线程还在运行,程序就不会终止。必须在线程启动之前调用setDaemon()方法,才能把它设置为后台线程。注意:后台进程在不执行finally子句的情况下就会终止其run()方法。

后台线程的特征:如果所有的前台线程都死亡了,后台线程也会自动死亡。

注意:在构建Daemon线程时,不能依靠finally块中的内容来确保执行关闭或清理资源的逻辑

比如:

1⃣️JVM的垃圾回收线程就是Daemon线程,Finalizer也是守护线程。

2⃣️Thread Dump打印出来的线程信息,含有daemon字样的线程即为守护进程,可能会有:服务守护进程、编译守护进程、windows下的监听Ctrl+break的守护进程、Finalizer守护进程、引用处理守护进程、GC守护进程。

启动和终止线程

构造线程
  • 准备线程所需的属性,如线程所属的线程组、线程优先级、是否为Daemon(守护线程)等信息
启动线程
  • 线程对象在初始化完成之后调用start()方法启动线程
理解中断
  • 睡眠线程的中断因为声明抛出InterruptedException,所以在抛出InterruptedException之前,JAVA虚拟机会将线程的中断标识位清除,所以即使线程被中断过,在调用该线程对象的isInterrupt()时依旧会返回false

更多详情借鉴 https://blog.youkuaiyun.com/dolphin_0923/article/details/107739975

过期的suspend()、resume()和stop()
  • suspend()、resume()和stop()分别表示暂停、恢复和停止的含义
  • 不建议使用(过期的)原因
    • 调用后线程不会释放已经占有的资源(比如锁),而是占着资源进入睡眠状态,容易引发死锁问题
    • 取代(替代):等待/通知机制
安全地终止线程
  • main线程通过中断操作和cancel()方法均可使线程得以终止
  • 使用这两种方式终止线程的好处
    • 通过标识位(cancel())或者中断操作的方式能够使线程在终止时有机会去清理资源,而不是武断地将线程停止,可以确保终止线程的安全和优雅

线程间通信

volatile和synchronized关键字
  • volatile关键字:
    • 可以用来修饰字段(成员变量),就是告知程序任何对该变量的访问均需要从共享内存中获取,而对它的改变必须同步刷新会共享内存,它能保证所有线程对变量访问的可见性。
    • 不足之处:虽然实现了可见性,但是不能保证原子性。
      • 理解:当多个线程同时对同一字段进行修改时,由于多线程的并发性,会导致取同一字段变量时,变量值均相同,同时修改后的值改变并不能实现原子性,最终导致变量的修改错误。(简单理解:多条线程共同操作共享数据时,缺乏实现锁机制,造成变量修改出错)

更多详情借鉴 https://blog.youkuaiyun.com/qq_24047659/article/details/88031712

  • synchronized关键字:
    • 可以修饰方法或者以同步块的形式来进行使用,它主要确保多个线程在同一时刻,只能有一个线程处于方法或者同步块中,它保证了线程对变量访问的可见性和排他性

更多详情借鉴 https://blog.youkuaiyun.com/qq_48435252/article/details/123980558

等待/通知机制
  • 是指一个线程A调用了对象O的wait()方法进入等待状态,而另一个线程B调用了对象O的notify()/notifyAll()方法,线程A收到通知后退出等待队列,进入可运行状态,进而执行后续操作。

更多详情借鉴 https://blog.youkuaiyun.com/qq_45404693/article/details/120894232

等待/通知的经典范式

该范式分为两部分,分别针对等待方(消费者)和通知方(生产者)。

等待方遵循如下原则。

  • 获取对象的锁。
  • 如果条件不满足,那么调用对象的wait()方法,被通知后仍要检查条件。
  • 条件满足则执行对应的逻辑。

对应的伪代码如下:

synchronized(对象) {

	while(条件不满足) {

		对象.wait();

	}

	对应的处理逻辑

}

通知方遵循如下原则。

  • 获得对象的锁。
  • 改变条件。
  • 通知所有等待在对象上的线程。

对应的伪代码如下:

synchronized(对象) {
	改变条件
	对象.notifyAll();
}
管道输入/输出流
  • 主要用于线程之间的数据传输,而传输的媒介为内存
Thread.join()的使用
  • Thread.join()方法的作用是等待当前执行的线程终止。假设在线程B终止之前,修改了共享变量,线程A从线程B的join方法成功返回后,线程B对共享变量的修改将对线程A可见。
ThreadLocal的使用
  • 使用ThreadLocal线程的本地变量,每个线程创建一个私有变量(1000个任务创建10个线程池)的示例来说,使用ThreadLocal就是创建10个私有变量

  • 选择ThreadLocal还是锁?

    • 就看创建实例对象之后的复用率,复用率高就是ThreadLocal
  • 方法

    • set(T):将私有变量设置到线程中(每个线程有自己的变量)
    • get():获取线程中的私有变量
    • remove():从线程中移除私有变量的

更多详情借鉴 https://blog.youkuaiyun.com/cds86333774/article/details/51020819

​ https://blog.youkuaiyun.com/weixin_53946630/article/details/117185043

线程应用实例

源文可借鉴 https://blog.youkuaiyun.com/sunxianghuang/article/details/51966118

等待超时模式
  • 在等待/通知范式的基础上增加了超时控制,这使得该模式相比原有范式更具有灵活性
  • 即使方法执行时间过长,也不会永久地阻塞调用者,而会按照调用者的要求“按时”返回
一个简单的数据库连接池示例
  • 使用了等待超时模式
  • 当获取连接池的数量为10(10个连接),当线程的数量越多时,等待超时的数据库连接方法也会增多
  • 所以,可以看出等待超时模式的重要性,否则,将会进入永久等待状态
线程池技术

更多详情借鉴 一文读懂线程池的实现原理_老周聊架构的博客-优快云博客_线程池的实现原理

java线程池 面试题(精简)_Linias的博客-优快云博客_java 线程池 面试

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值