【学习笔记】Java并发编程的艺术——第4章 Java并发编程基础
第4章 Java并发编程基础
4.1 线程简介
4.1.1 什么是线程
操作系统调度的最小单位,有自己的计数器栈、局部变量、共享内存。
4.1.2 为什么要使用线程
1>利用处理器多核特性
2>提高响应速度
将一系列操作中对一致性要求低的操作(如发送通知邮件等)交给其他线程(有时用消息队列)处理。
3>更好的编程模型(java)
4.1.3 线程的优先级
默认优先级为父线程优先级,通过priority来控制
频繁I/O或sleep的线程,阻塞较多,应设置高优先级,多CPU运算的应设置较低的优先级,确保处理器不被独占
4.1.4 线程的状态
线程状态转换P89
4.1.5 Daemon线程(守护线程)
一种支持型线程,主要用作程序中后台调度及支持性工作,不存在非Deamon线程时Java虚拟机会退出
【Deamon线程中的finally块不一定会被执行】
4.2 启动和终止线程
4.2.1 伪造线程
由父线程分配资源,继承parent是否为Deamon、优先级和加载资源的context class loader,以及复制父类ThreadLocal,分配唯一ID
4.2.2 启动线程
调用start()方法
4.2.3 理解中断
1>一个线程的中断标识,如果中断一个sleep线程,则会抛出异常并停止运行(可做finally操作)
2>可以在线程中while判断自己的中断标识,当别的线程中断它时,作相应处理或退出
4.2.4 过期的suspend()、resume()和stop()
可能导致死锁等问题(因资源无法正常释放)不建议使用
【sleep基本不消耗处理器性能】
4.2.5 安全的终止线程
可通过中断标识,或自己搞个boolean来控制相关线程的中止,让相关线程感知到自己要被中止,以正常释放资源
4.3 线程间通信
synchronized保证了可见性与排他性
4.3.1 volatile与synchronized关键字
volatile没必要不用多使用,会减少性能
synchronized:
1>获得对象监视器(锁)的可进入同步代码块
2>每个对象都有一个监视器,其获取是排他的
3>没有获取到监视器的会阻塞在同步块方法入口处
4>阻塞时会放入同步队列,待获取到监视器的线程释放锁后,唤醒队列中的线程
4.3.2 等待/唤醒机制
1>一个线程在等待另一个线程通知并做出相应处理时,可以通过循环+sleep的方式进行
但是存在以下两点问题:
①频繁循环消耗cpu性能
②sleep太久导致响应不及时
2>wait与notify
①用Lock锁对象来调用这两个方法
②wait立即释放锁并进入等待队列
③notify使等待线程由等待队列进入同步队列
④notify不立即释放锁(同步方法结束才释放),但会立即唤醒
4.3.3 等待/通知的经典范式
1>等待方原则(wait)[消费者]
①获取锁
②条件不满足,调用wait,唤醒后仍要做判断(while)
③条件满足,执行对应逻辑
2>通知方原则(notify)[生产者]
①获取锁
②改变条件
③通知所有等待线程(notifyAll)
4.3.4 管道输入/输出流
Piped~JDK,可将输入输出流连接绑定,用作线程之间的通信(内存)
4.3.5 Thread.join的使用
循环wait(0),直到等待的线程执行完毕后调用this.notify
【wait的作用范围为线程】
调用线程获取执行线程对象的监视器然后使用该对象wait调用线程,等待执行线程notify
4.3.6 ThreadLocal的使用
相当于一个Map,key为Thread,value为
4.4 线程应用实例
4.4.1 等待超时模式
wait(int)
在wait返回时,判断等待时间,等待时间大于该值则停止等待
4.4.2 针对于数据库连接池这样的昂贵资源,获取时应加超时控制
4.4.3 线程池技术及其示例
线程池主要常量:核心线程数量,最大线程数量,阻塞队列,动态创建线程清楚时间
线程池实现主要抽象:
1>线程池对象定义以上常量,各种Job、worker集合
2>工作线程对象:创建对象以判断Job队列情况并执行Job任务
3>任务对象:Job对象保存需执行的任务
4.4.4 一个基于线程池技术的简单web服务器
首先搞一个socket来监听某个端口,将接收到的socket交给HttpRequestHandler,然后线程池中的线程来执行处理器。
【线程太少,无法发挥处理器性能,太多又会有无效开销】