第四章 Java并发编程基础
[toc]
线程简介
定义
- 进程:进程是程序的一次执行,进程是一个程序及其数据在处理机上顺序执行时所发生的活动,进程是 具有独立功能的程序在一个数据集合上运⾏的过程,它是系统进行资源分配和调度的一个独立单位
- 线程:操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位
为什么要使用多线程
- 更多的处理器核心
- 更快的响应时间
- 更好的编程模型
优先级
现代操作系统基本采用时分的形式调度运行的线程,操作系统会分出一个个时间片,线程会分配到若干时间片,当线程的时间片用完了就会发生线程调度,并等待着下次分配。线程 分配到的时间片多少也就决定了线程使用处理器资源的多少,而线程优先级就是决定线程需 要多或者少分配一些处理器资源的线程属性。
在Java线程中,通过一个整型成员变量priority来控制优先级,优先级的范围从1~10,在线程构建的时候可以通过setPriority(int)方法来修改优先级,默认优先级是5,优先级高的线程分 配时间片的数量要多于优先级低的线程。
状态
Java线程一共有六种状态
image
Java线程状态变迁
image
Daemon线程
Daemon线程是一种支持型线程,因为它主要被用作程序中后台调度以及支持性工作。这意味着,当一个Java虚拟机中不存在非Daemon线程的时候,Java虚拟机将会退出。可以通过调用Thread.setDaemon(true)将线程设置为Daemon线程。
注意:Daemon线程的finally代码块不一定会执行
示例代码
package Daemon;
import util.SleepUtils;
public class Daemon {
public static void main(String[] args) {
Thread thread = new Thread(new DaemonRunner(), "Daemon");
thread.setDaemon(true);
thread.start();
}
static class DaemonRunner implements Runnable {
@Override
public void run() {
try {
System.out.println("start");
SleepUtils.second(10);
}finally {
System.out.println("Daemon");
}
}
}
}
package util;
import java.util.concurrent.TimeUnit;
public class SleepUtils {
public static void second(long seconds){
try {
TimeUnit.SECONDS.sleep(seconds);
}catch (InterruptedException ignored){
}
}
}
启动和终止线程
构造线程
在运行线程之前首先要构造一个线程对象,线程对象在构造的时候需要提供线程所需要的属性,如线程所属的线程组、线程优先级、是否是Daemon线程等信息。
启动线程
线程对象在初始化完成之后,调用start()方法就可以启动这个线程。线程start()方法的含义是:当前线程(即parent线程)同步告知Java虚拟机,只要线程规划器空闲,应立即启动调用start()方法的线程。
注意:启动一个线程前,最好为这个线程设置线程名称,因为这样在使用jstack分析程序或者进行问题排查时,就会给开发人员提供一些提示,自定义的线程最好能够起个名字。
理解中断
中断可以理解为线程的一个标识位属性,它表示一个运行中的线程是否被其他线程进行了中断操作。中断好比其他线程对该线程打了个招呼,其他线程通过调用该线程的interrupt() 方法对其进行中断操作。
线程通过检查自身是否被中断来进行响应,线程通过方法isInterrupted()来进行判断是否被中断,也可以调用静态方法Thread.interrupted()对当前线程的中断标识位进行复位。如果该线程已经处于终结状态,即使该线程被中断过,在调用该线程对象的isInterrupted()时依旧会返回false。
从Java的API中可以看到,许多声明抛出InterruptedException的方法(例如Thread.sleep(longmillis)方法)这些方法在抛出InterruptedException之前,Java虚拟机会先将该线程的中断标识位清除,然后抛出InterruptedException,此时调用isInterrupted()方法将会返回false。
过期的方法
大家对于CD机肯定不会陌生,如果把它播放音乐比作一个线程的运作,那么对音乐播放做出的暂停、恢复和停止操作对应在线程Thread的API就是suspend()、resume()和stop()。
不建议使用的原因主要有:以suspend()方法为例,在调用后,线程不会释放已经占有的资源(比如锁),而是占有着资源进入睡眠状态,这样容易引发死锁问题。同样,stop()方法在终结一个线程时不会保证线程的资源正常释放,通常是没有给予线程完成资源释放工作的机会, 因此会导致程序可能工作在不确定状态下。
安全地终止线程
中断状态是线程的一个标识位,而中断操作是一种简便的线程间交互方式,而这种交互方式最适合用来取消或停止任务。除了中断以外,还可以利用一个boolean变 量来控制是否需要停止任务并终止该线程。
线程间通信
线程开始运行,拥有自己的栈空间,就如同一个脚本一样,按照既定的代码一步一步地执行,直到终止。但是,每个运行中的线程,如果仅仅是孤立地运行,那么没有一点儿价值,或者 说价值很少,如果多个线程能够相互配合完成工作,这将会带来巨大的价值。
volatile和synchronized关键字
具体见volatile和synchronized原理及应用
等待/通知机制
定义
前者是生产者,后者就是消费者,这种模式隔离了“做什么”(what)和“怎么做”(How),在功能层面上实现了解耦,体系结构上具备了良好的伸缩性,
Java中相关方法
等待/通知的相关方法是任意Java对象都具备的,因为这些方法被定义在所有对象的超类 java.lang.Object上。
[图片上传失败...(image-3ed21-1608647593453)]
实现思路
等待/通知机制,是指一个线程A调用了对象O的wait()方法进入等待状态,而另一个线程B调用了对象O的notify()或者notifyAll()方法,线程A收到通知后从对象O的wait()方法返回,进而执行后续操作。上述两个线程通过对象O来完成交互,而对象上的wait()和notify/notifyAll()的 关系就如同开关信号一样,用来完成等待方和通知方之间的交互工作。
具体代码
等待/通知的经典范式
等待方遵循如下原则。
- 获取对象锁
- 如果条件不满足,那么调用对象的wait()方法,被通知后仍要检查条件
- 条件满足则执行对应逻辑
具体伪代码
synchronized(对象) {
while(条件不满足) {
对象.wait();
}
对应的处理逻辑
}
通知方遵循如下原则
- 获取对象锁
- 改变条件
- 通知所有等待在对象上的线程
具体伪代码
synchronized(对象) {
改变条件
对象.notifyAll();
}
管道输入/输出流
管道输入/输出流和普通的文件输入/输出流或者网络输入/输出流不同之处在于,它主要用于线程之间的数据传输,而传输的媒介为内存。
管道输入/输出流主要包括了如下4种具体实现:PipedOutputStream、PipedInputStream、PipedReader和PipedWriter,前两种面向字节,而后两种面向字符。
Thread.join()的使用
含义
如果一个线程A执行了thread.join()语句,其含义是:当前线程A等待thread线程终止之后才从thread.join()返回。
具体代码
package TestJoin;
import java.util.concurrent.TimeUnit;
public class Join {
public static void main(String[] args) throws InterruptedException {
Thread previous = Thread.currentThread();
for (int i=0; i<10; i++){
Thread thread = new Thread(new Domino(previous),String.valueOf(i));
thread.start();
previous = thread;
}
TimeUnit.SECONDS.sleep(5);
System.out.println(Thread.currentThread().getName() + " terminate.");
}
static class Domino implements Runnable {
private Thread thread;
public Domino(Thread thread) {
this.thread = thread;
}
@Override
public void run() {
try {
thread.join();
}catch (InterruptedException ignored){
}
System.out.println(Thread.currentThread().getName() + " terminate.");
}
}
}
输出如下
main terminate.
0 terminate.
1 terminate.
2 terminate.
3 terminate.
4 terminate.
5 terminate.
6 terminate.
7 terminate.
8 terminate.
9 terminate.
从上述输出可以看到,每个线程终止的前提是前驱线程的终止,每个线程等待前驱线程终止后,才从join()方法返回,这里涉及了等待/通知机制(等待前驱线程结束,接收前驱线程结束通知)。
源码
public final synchronized void join(long millis)
throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (millis == 0) {
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
ThreadLocal的使用
定义
ThreadLocal,即线程变量,是一个以ThreadLocal对象为键、任意对象为值的存储结构。这个结构被附带在线程上,也就是说一个线程可以根据一个ThreadLocal对象查询到绑定在这个 线程上的一个值。
网友评论