线程是操作系统调度的最小单元,多线程能够同时执行。从启动一个线程到线程间不同的通信方式。
在一个进程里可以创建多个线程,这些线程都拥有自己的计数器、堆栈和局部变量等属性,并且能够访问共享的内存变量。
Java程序天生就是多线程程序,因为执行main方法的是一个名称为main的线程。
使用多线程的原因主要有以下几种
1、更多的处理器核心:一个线程在一个时刻只能运行在同一个处理器核心上。
2、更快的响应时间。
3、更好的编程模型。
优先级
1、现代操作系统基本采用时分的方式调度线程,优先级高的线程比优先级低的线程分配的时间片数量多。
2、线程的默认优先级是5。
3、对于偏重CPU计算的线程设置较低的优先级,确保处理器不会被独占。
4、针对频繁阻塞(偏重IO操作的)的线程需要设置较高优先级。
5、某些操作系统会忽略优先级的设定,如MAC和Linux。
线程的状态
1、线程在运行的生命周期中可能处于6种状态中的一种,在给定的一个时刻,线程只能处于其中的一个状态。
2、这六种状态
- New:初始状态
- Runnable:运行状态
- Blocked:阻塞状态
- Waiting:等待状态
- Time_waiting:超时等待状态
- Terminated:终止状态
3、线程的状态转换图
4、java.concurrent包中的Lock类,是将线程进入到waiting状态,因为Lock底层采用的是LockSupport类实现的。
Daemon线程
1、Daemon线程是一种支持型线程,因为他被用作程序中后台调度以及支持性工作。
2、当一个Java虚拟机中不存在非Daemon线程时,Java虚拟机将退出。
3、可以用Thread.setDaemon(true)将线程设置为Daemon线程。只能在启动线程之前设置,不能在启动线程之后设置。
4、Daemon被用作完成支持性工作,但是在Java虚拟机退出时,Daemon线程中的finally块并不一定执行。在构建Daemon线程时,不能依靠finally块中的内容来确保执行关闭或清理资源的逻辑。
线程的属性
1、线程组
2、线程的名字
3、线程的优先级
4、线程是否为Daemon线程
构造线程
一个新建的线程是由其parent线程来进行空间分配的,而child线程继承了parent线程的是否为Daemon线程,优先级和加载资源的contentClassLoader,以及可继承的ThreadLocal,同时还会分配一个唯一的ID来标识这个Child线程。
理解中断
1、是线程的一个标志位。
2、其他线程调用这个线程的中断方法,那么这个线程被中断。
3、这个线程的方法isInterrupted,来判断线程是否被中断了。
4、如果该线程已经处于终结状态,那么调用isInterrupted方法,仍然返回false。
5、调用Thread.interrpted静态方法,使中断标识位复位。
6、Thread.sleep方法,在抛出InterruptedException之前,会先将中断标识位复位,这时候调用isInterrupted方法,返回false。中断标识位被清除了。
7、一般用来优雅安全的停止线程。
suspend,resume,stop
对CD机的暂停,恢复和停止相当于线程的suspend,resume,和stop。
这些方法已经不建议使用了,因为suspend方法没有释放对应的资源,而是占用着资源睡眠了,容易引起死锁。
而且暂停和恢复操作,可以用等待,通知机制来替代。
安全的终止线程
1、Thread.currentThread.interrupted
2、通过标志位来操作
以上两种办法,在线程中判断一下,是否被中断了,或者标志位是否要求停止线程,来进行结束接下来的操作。可以安全的释放资源。
线程间通信
线程开始运行时,拥有自己的栈空间。
Java支持多个线程同时访问一个对象或者对象的成员变量,由于每个线程可以拥有这个变量的拷贝(虽然对象和成员变量分配的内存是在共享内存中,但是每个执行的线程还是可以拥有一份拷贝),这样可以加速程序的运行,同时也是多核处理器的一个显著特性,所以程序在执行过程中,一个线程看到的变量并不一定是最新的。
1、关键字volatile可以用来修饰字段(成员变量),就是告知程序任何对该变量的访问均需要从共享内存中获取,而对它的改变必须同步刷新回共享内存,它能保证所有线程对该变量的的访问的可见性。但是过多的使用volatile是不必要的,因为它会减低程序的执行效率。
2、关键字synchronized
- 可以修饰方法或者以同步块的形式进行使用,它主要确保多个线程在同一个时刻,只能有一个线程处于方法或者同步块中,它保证了线程对变量访问的可见性和排他性。
- 其本质是对一个对象的监视器的获取,而这个获取过程是排他的,也就是同一个时刻只能有一个线程获取到由synchronized所保护对象的监视器。
- 任意一个对象都拥有自己的监视器,当这个对象由同步块或者这个对象的同步方法调用时,执行方法的线程必须先获取到该对象的监视器才能进入同步块或者同步方法,而没有获取到监视器的线程将会被阻塞在同步块或者同步方法的入口处,进入BLOCKED状态。
3、等待通知机制
- 解决生产者与消费者问题。
- 每一个对象都拥有等待通知方法,因为它们被定义在了所有对象的超类Object上了。
- 等待通知机制是指一个线程A调用了对象O的wait方法进入等待状态,而另一个线程B调用了对象O的notify或者notifyAll方法,线程A收到通知后从对象O的wait方法返回,进而进行后续操作。上述两个线程通过对象O来完成交互,而对象上的wait和notify、notifyAll的关系就如同开关信号一样,用来完成等待方和通知方之间的交互工作。
4、调用wait,notify以及notifyAll时需要注意的细节
- 调用wait,notify以及notifyAll时需要先对调用对象加锁。
- 调用wait方法后,线程状态由Running变为waiting,并将当前线程放置到对象等待队列。
- notify以及notifyAll方法调用后,等待线程依旧不会从wait返回,需要调用notify或notifyAll的线程释放锁以后,等待线程才有机会从wait返回。
- notify方法将等待队列中的一个等待线程从等待队列中移到同步队列中,而notifyAll方法则是将等的队列中所有的线程都移到同步队列,被移动的线程状态由waiting变为blocked。
- 从wait方法返回的前提是获得了调用对象的锁。
5、等待方遵循如下原则 (消费者)的经典范式
- 获取对象的锁
- 如果条件不满足,那么调用对象的wait方法,被通知后仍要检查条件。
- 条件满足,则要执行对应的逻辑。
6、通知方遵循如下的原则 (生产者)的经典范式
- 获得对象的锁
- 改变条件
- 通知所有等待在对象上的线程
7、管道输入,输出流
- 主要用于线程之间的数据传输,而传输的介质为内存。
- 主要包括如下四种实现:PipedOutputStream,PipedInputStream,PipedReader,PipedWriter。前两种面向字节,后两种面向字符。
- 必须将输出流与输入流进行连接,否则会报异常IOException。
package chapter04;
import java.io.IOException;
import java.io.PipedReader;
import java.io.PipedWriter;
public class Piped {
public static void main(String[] args) throws Exception {
PipedWriter out = new PipedWriter();
PipedReader in = new PipedReader();
// 将输出流和输入流进行连接,否则在使用时会抛出IOException
out.connect(in);
Thread printThread = new Thread(new Print(in), "PrintThread");
printThread.start();
int receive = 0;
try {
while ((receive = System.in.read()) != -1) {
// 任何main线程的输入,通过PipedWriter(out)写入
out.write(receive);
}
} finally {
out.close();
}
}
static class Print implements Runnable {
private PipedReader in;
public Print(PipedReader in) {
this.in = in;
}
public void run() {
int receive = 0;
try {
// 通过PipedReader(in)将内容输出并打印
while ((receive = in.read()) != -1) {
System.out.print((char) receive);
}
} catch (IOException ex) {
}
}
}
}
8、A线程调用B线程的Thread.join,需要B线程执行结束后,A线程才会继续执行。
9、ThreadLocal
- 线程变量
- 附着在线程上
- key是,value是可以是任意对象
- 为每个使用该变量的线程提供副本
- 有三个重要的方法:set,get,initialValue(只会被调用一次,当第一次set或者get被调用的时候被调用)
- 使得有状态bean可以在线程之间共享了,而且有状态的bean也可以是单例的。
10、等待超时模式