《Java并发编程的艺术》 学习之路
第四章 Java并发编程基础
4.1 线程简介
4.1.1 什么是线程
操作系统调度的最小单元是线程,也叫轻量级进程(LightWeight Process),在一个进程里可以创建多个线程,这些线程都拥有各自的计数器、堆栈和局部变量等属性,并且能够访问共享的内存变量。
4.1.2 为什么要使用多线程
- (1)更多的处理器核心
- (2)更快的响应时间
- (3)更好的编程模型
4.1.3 线程优先级
优先级的范围从1~10,默认优先级是5,可以通过setPriority(int)方法来修改优先级。
4.1.4 线程的状态
Java线程在运行的生命周期中可能6种不同的状态如下图:

Java 线程状态变化:

Java将操作系统中的运行和就绪两个状态合并称为运行状态。阻塞状态是线程阻塞在进入synchronized关键字修饰的方法或代码块(获取锁)时的状态,但是阻塞在java.concurrent包中Lock接口的线程状态却是等待状态,因为java.concurrent包中Lock接口对于阻塞的实现均使用了LockSupport类中的相关方法。
4.1.5 Daemon线程(守护线程)
Daemon线程是一种支持型线程,因为它主要被用作程序中后台调度以及支持性工作。
当一个Java虚拟机中不存在非Daemon线程的时候,Java虚拟机将会退出。
通过调用 Thread.setDaemon(true) 将线程设置为Daemon线程。
特别注意:!!!
1.Daemon属性需要在启动线程之前设置,不能在启动线程之后设置
2.在构建Daemon线程时,不能依靠finally块中的内容来确保执行关闭或清理资源的逻辑。
3.在Java虚拟机退出时Daemon线程中的finally块并不一定会执行。
4.2 启动和终止线程
4.2.1 构造线程
一个新构造的线程对象是由其 parent线程 来进行空间分配的,而child线程继承了parent是否为Daemon、优先级和加载资源的contextClassLoader以及可继承的ThreadLocal,同时还会分配一个唯一的ID来标识这个child线程。至此,一个能够运行的线程对象就初始化好了,在堆内存中等待着运行。
4.2.2 启动线程
线程对象在初始化完成之后,调用start()方法就可以启动这个线程。线程start()方法的含义是:当前线程(即parent线程)同步告知Java虚拟机,只要线程规划器空闲,应立即启动调用start()方法的线程。
启动一个线程前,最好为这个线程设置线程名称,因为这样在使用jstack分析程序或者进行问题排查时,就会给开发人员提供一些提示,自定义的线程最好能够起个名字。
4.2.3 理解中断
中断可以理解为线程的一个 标识位属性 ,它表示一个运行中的线程是否被其他线程进行了中断操作。
在抛出InterruptedException之前,Java虚拟机会先将该线程的中断标识位清除,然后抛出InterruptedException,此时调用isInterrupted()方法将会返回false。
4.2.4 过期的suspend()、resume()和stop()
suspend()方法:暂停
resume()方法:恢复
stop()方法:终止
suspend不推荐使用原因:在调用后,线程不会释放已经占有的资源(比如锁),而是占有着资源进入睡眠状态,这样容易引发死锁问题。
stop不推荐使用的原因:在终结一个线程时不会保证线程的资源正常释放,通常是没有给予线程完成资源释放工作的机会,因此会导致程序可能工作在不确定状态下。
4.2.5 安全地终止线程
通过 标识位或者中断操作 的方式能够使线程在终止时有机会去清理资源。
private static class Runner implements Runnable {
private long i;
private volatile boolean on = true;
@Override
public void run() {
while (on && !Thread.currentThread().isInterrupted()){
i++;
}
System.out.println("Count i = " + i);
}
public void cancel() {
on = false;
}
}
4.3 线程间通信
4.3.1 volatile和synchronized关键字
使用volatile修饰变量保证线程之间可见性,使用sychronized关键字修饰方法或类或代码块,保证排他性
4.3.2 等待/通知机制

- 1)使用wait()、notify()和notifyAll()时需要先对调用对象 加锁 。
- 2)调用wait()方法后,线程状态由 RUNNING 变为WAITING,并将当前线程放置到对象的等待队列。
- 3)notify()或notifyAll()方法调用后,等待线程依旧不会从wait()返回,需要调用notify()或notifAll()的线程 释放锁 之后,等待线程才有机会从wait()返回。
- 4)notify()方法将等待队列中的一个等待线程从等待队列中移到同步队列中,而notifyAll()方法则是将等待队列中所有的线程全部移到同步队列,被移动的线程(等待线程)状态由WAITING变为BLOCKED。
- 5)从wait()方法返回的前提是获得了调用对象的锁。
为什么要加锁???
因为要确保等待线程从wait()方法返回时能够感知到通知线程对变量做出的修改。
4.3.3 等待/通知的经典范式
等待方(消费者)遵循如下原则。
1)获取对象的锁。
2)如果条件不满足,那么调用对象的wait()方法,被通知后仍要检查条件。
3)条件满足则执行对应的逻辑。
synchronized(obj) {
while(条件不满足) {
obj.wait();
}
//对应的处理逻辑
}
通知方 (生产者) 遵循如下原则。
1)获得对象的锁。
2)改变条件。
3)通知所有等待在对象上的线程。
synchronized(obj) {
改变条件
obj.notifyAll();
}
4.3.4 管道输入/输出流
管道输入/输出流:用于线程之间的数据传输,而传输的媒介为内存。
管道输入/输出流主要包括了如下4种具体实现:PipedOutputStream、PipedInputStream、PipedReader和PipedWriter,前两种面向字节,而后两种面向字符。
对于Piped类型的流,必须先要进行绑定,也就是调用connect()方法,如果没有将输入/输出流绑定起来,对于该流的访问将会抛出异常!!!
4.3.5 Thread.join()的使用
线程A执行了thread.join()语句,含义:当前线程A等待thread线程终止之后才从thread.join()返回。
有两个超时方法 join(long millis)和join(longmillis,int nanos) 如果规定时间内thread没有终止就返回。
4.3.6 ThreadLocal的使用
ThreadLocal,即线程变量,是一个以ThreadLocal对象为键、任意对象为值的存储结构(存储Map)。
每个线程内部存储的一块空间。(后续会有一篇详细介绍ThreadLocal)
未完待续 持续更新ing。。。
本文整理内容出自《Java并发编程的艺术》一书 第二版, 大力推荐~~~
本文深入讲解Java并发编程的基础概念,包括线程的生命周期、状态、优先级及 Daemon 线程,探讨线程的启动与终止,分析中断机制,解释线程间通信的重要性和方法,如 volatile、synchronized 和 wait/notify 机制。

922

被折叠的 条评论
为什么被折叠?



