学习资料
《Java并发编程的艺术》第4章 4.1~4.3
1.线程简介
1.1 什么是线程
现代操作系统调度的最小单元,也叫轻量级进程
一个进程可以创建多个线程,线程有各自的计数器,堆栈和局部变量等属性
处理器在线程上高速切换(时间片调度),让使用者感觉到这些线程是同时执行的
1.2 为什么要使用多线程
使用多线程的原因:
- 更多的处理器核心:一个线程在同一时刻只能运行在一个CPU核上,有多个核的cpu就可以在同时刻运行多个线程,提高运行速度(并行)
- 更快的响应速度:将数据一致性不强的操作派发给其他线程处理(也可以是消息队列),能让请求更快处理完成,缩短响应时间
- 更好的编程模型:Java提供了良好且一致的多线程编程模型
1.3 线程优先级
thread.setPriority(n)
:设置线程优先级,1~10,默认为5
有些操作系统会忽略优先级的设置,设置优先级没有效果(类Unix操作系统)
在Wnidows下,优先级高的线程分配的时间片数量要多于优先级低的线程:
- 频繁阻塞的线程(IO或休眠),应设置较高优先级
- 偏重计算(CPU密集型)的线程应设置较低优先级,防止独占cpu
1.4 线程运行状态
线程六种状态:初始,运行,阻塞,等待,超时等待,终止
线程状态切换:
注意:
- Java将操作系统线程状态的运行和就绪状态合并为运行态
- synchronized对应的是阻塞状态,但是JUC的Lock接口对应的却是(超时)等待状态,因为JUC中该接口的实现都使用了LockSupport类的方法
1.5 Daemon线程
也叫守护线程,后台线程
JVM中没有非Daemon线程时,所有的Daemon线程都要立即终止,可能会导致Daemon线程中的代码未执行完毕,Daemon线程中的finally块也不一定会执行
thread.setDaemon(true);
2.启动和终止线程
2.1 构造线程
构造线程是通过父线程来构造的,在Thread类的init方法中进行构造
private void init(ThreadGroup g, Runnable target, String name,long stackSize, AccessControlContext acc) {
//1.name参数有效性检查
//2.设置传入参数的值
//3.指定当前线程为父线程
//4.deamon,priority属性设置为父线程对应的属性
//5.将父线程的InheritableThreadLocal复制过来
//6.分配一个线程ID标记该线程
}
2.2 启动线程
thread.start()
:当前线程(父线程)告知JVM,只要线程规划器空闲,立即启动thread线程
最好为线程设置名称thread.setName(name)
,方便使用jstack排查问题
2.3 理解中断
中断可以理解为线程的一个标识位属性
thread.interrupt()
:其他线程调用thread的该方法,将thread标记为中断
thread.isInterrupted()
:true表示thread有中断标记,false表示没有中断标记,对终止状态的线程调用该方法返回结果都是false
Thread.interrupted()
:清除该类对象的所有中断标记
2.4 过期的suspend()、resume()和stop()
thread.suspend()
,thread.resume()
,thread.stop()
过期API不建议使用,不会释放(suspend)或者不会正确释放(stop)占有资源,导致程序出现不确定的状态
suspend/resume暂停挂起可以使用等待通知机制来替代
2.5 安全地终止线程
通过对中断状态的交互控制来,还可以通过对Volatile类型的boolean变量来控制
3.线程间通信
3.1 volatile和synchronized关键字
volatile修饰字段,表示任何对该变量的访问都要从共享内存中获取,且对它的改变也必须刷新回共享内存,保证所有线程对变量访问的可见性
Synchronized可以修饰方法或者以同步块的形式来使用,确保多个线程在同一时刻只有一个线程处于方法或同步块中
- 本质是对一个对象监视器(monitor)进行获取,排他,同一时刻只能有一个线程获取
- 每个对象都有自己的监视器,只有获取到该监视器的线程才能进入到同步块,否则就会阻塞在同步队列
SynchronizedQueue
Synchronized示意图:
3.2 等待/通知机制及范式
等待方:消费者,WaitThread
-
等待方原则:
- 要获取对象的锁
- 条件不满足,调用对象的wait()方法,被通知后仍要检查条件
- 条件满足则执行对应的逻辑
-
示例代码:
synchronized(obj){//锁对象 while(条件不满足){ obj.wait(); } //对应处理逻辑 }
通知方:生产者,NotifyThread
-
通知方原则:
- 要获取对象的锁
- 改变条件
- 通知所有等待在对象上的线程
-
示例代码:
synchronized(obj){ //修改条件 obj.notifyAll(); }
示意图:
3.3 管道输入输出流
管道输入/输出流用于线程之间的数据传输,传输媒介为内存,有四种具体实现:
- 字节:
- PipedOutputStream
- PipedInputStream
- 字符:
- PipedWriter
- PipedReader
需要使用 connect()
将输入流和输出流绑定,否则会抛出IOException
示例代码:
...main(...){
PipedWriter out=new PipedWriter();
PipedReader in=new PipedReader();
out.connect(in); //绑定
Thread inThread=new Thread(new InThread(in)); //读取数据的线程
inThread.start();
...
out.write("A"); //main线程写
...
}
public class InThread implements Runnable{
private PipedReader in;
public InThread(PipedReader in){
this.in;
}
public void run(){
...
in.read(); //读取
...
}
}
3.4 thread.join()的使用
thread.join()
:当前线程等待,thread线程执行终止后才继续执行当前线程
逻辑结构与等待/通知类似
超时重载类型:join(long millis)
和join(long millis,int nanos)
3.5 ThreadLocal的使用
ThreadLocal,线程变量,是一个以ThreadLocal对象为键,任意对象为值的存储结构
一个线程可以通过ThreadLocal对象查询到绑定在这个线程上的一个值(线程独有的值)
示例代码:
ThreadLocal<LONG> tll=new ThreadLocal<>();
tll.set();//设置值
tll.get();//获取值