20大进阶架构专题每日送达
Java并发编程
之前发过,但是因为之前忘记标记原创,没办法收录在【并发编程专题】里面,作为强迫症的我,必须要重发一次。本文为第 11 篇,前面几篇没看过的,可以在文末找到前几篇的跳转链接。本文介绍线程调度的如下几个操作:
每个线程执行时都有一个优先级的属性,优先级高的线程可以获得较多的执行机会,而优先级低的线程则获得较少的执行机会。
操作系统采用时分的形式调度运行的线程,操作系统会分出一个个时间片,线程会分配到若干时间片,当线程的时间片用完了就会发生线程调度,并等待着下次分配。线程分配到的时间片多少也就决定了线程使用处理器资源的多少,而线程优先级就是决定线程需要多或者少分配一些处理器资源的线程属性。
Thread 类通过一个整型成员变量 priority 来控制优先级,优先级的范围从 1 ~ 10,默认优先级是 5。
举例:如下代码,一般情况下,高级线程更先执行完毕。
public class Test1 {
public static void main(String[] args) throws InterruptedException {
new MyThread("高级", 10).start();
new MyThread("低级", 1).start();
}
}
class MyThread extends Thread {
public MyThread(String name, int pro) {
super(name);// 设置线程的名称
setPriority(pro);// 设置线程的优先级
}
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
System.out.println(this.getName() + "线程第" + i + "次执行!");
}
}
}
虽然 Java 提供了 10 个优先级别,但这些优先级别需要操作系统的支持,所以需要注意:
Daemon 线程是一种支持型线程,在后台守护一些系统服务,比如 JVM 的垃圾回收、内存管理等线程都是守护线程。
与之对应的就是用户线程,用户线程就是系统的工作线程,它会完成整个系统的业务操作。
用户线程结束后就意味着整个系统的任务全部结束了,因此系统就没有对象需要守护的了,守护线程自然而然就会退出。所以当一个 Java 应用只有守护线程的时候,虚拟机就会自然退出。
举例:
public class Test {
public static void main(String[] args) {
Thread t1 = new MyCommon();
Thread t2 = new Thread(new MyDaemon());
t2.setDaemon(true); // 设置为守护线程
t2.start();
t1.start();
}
}
class MyCommon extends Thread {
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("线程1第" + i + "次执行!");
try {
Thread.sleep(7);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class MyDaemon implements Runnable {
public void run() {
for (long i = 0; i < 9999999L; i++) {
System.out.println("后台线程第" + i + "次执行!");
try {
Thread.sleep(7);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
执行结果:
后台线程第0次执行!
线程1第0次执行!
线程1第1次执行!
后台线程第1次执行!
后台线程第2次执行!
线程1第2次执行!
线程1第3次执行!
后台线程第3次执行!
线程1第4次执行!
后台线程第4次执行!
后台线程第5次执行!
后台线程第6次执行!
后台线程第7次执行!
通过结果可以看到,用户线程 MyCommon 执行完毕之后,程序中只有守护线程 MyDaemon,虚拟机退出,守护线程 MyDaemon 也就结束了。
Thread 类 boolean 类型的 daemon 属性标志守护线程,通过 setDaemon(boolean on)方法设置守护线程。
public class Daemon {
public static void main(String[] args) {
Thread thread = new Thread(new DaemonRunner(), "DaemonRunner");
thread.setDaemon(true);
thread.start();
System.out.println("主线程执行完毕");
}
static class DaemonRunner implements Runnable {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println("DaemonThread finally run.");
}
}
}
}
运行 Daemon 程序,只会输出"主线程执行完毕"。
main 线程在启动了线程 DaemonRunner 之后随着 main 方法执行完毕而终止,而此时 Java 虚拟机中已经没有非 Daemon 线程,虚拟机需要退出。JDaemon 线程 DaemonRunner 立即终止,DaemonRunner 中的 finally 块并没有执行。
中断代表线程状态,每个线程都关联了一个中断状态,用 boolean 值表示,初始值为 false。中断一个线程,其实就是设置了这个线程的中断状态 boolean 值为 true。
注意区分字面意思,中断只是一个状态,处于中断状态的线程不一定要停止运行。
Thread 类线程中断的方法:
// 设置一个线程的中断状态为true
public void interrupt() {}
// 检测线程中断状态,处于中断状态返回true
public boolean isInterrupted() {}
// 静态方法,检测调用这个方法的线程是否已经中断,处于中断状态返回true
// 注意:这个方法返回中断状态的同时,会将此线程的中断状态重置为false
public static boolean interrupted() {}
以下方法会自动感知中断:
Object 类的 wait()、wait(long)、wait(long, int)
Thread 类的 join()、join(long)、join(long, int)、sleep(long)、sleep(long, int)
当一个线程处于 sleep、wait、join 这三种状态之一时,如果此时线程中断状态为 true,那么就会抛出一个 InterruptedException 的异常,并将中断状态重新设置为 false。
举例:利用中断结束线程
public class Test1 {
public static void main(String[] args) throws InterruptedException {
MyThread thread = new MyThread();
thread.start();
Thread.sleep(3000);
thread.interrupt();
}
}
class MyThread extends Thread {
int i = 0;
@Override
public void run() {
while (true) {
System.out.println(i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println("中断异常被捕获了");
return;
}
i++;
}
}
}
执行结果:
0
1
2
中断异常被捕获了
MyThread 线程一直循环打印数字,3s 之后主线程将 MyThread 线程中断,MyThread 线程处于 sleep 状态会自动感应中断,抛出 InterruptedException 异常,线程结束执行。
当一个线程必须等待另一个线程执行时,就用到 join。
Thread 类中的三个 join 方法:
// 当前线程加入该线程后面,等待该线程终止。
void join()
// 当前线程等待该线程终止的时间最长为 millis 毫秒。如果在millis时间内,该线程没有执行完,那么当前线程进入就绪状态,重新等待cpu调度
void join(long millis)
// 等待该线程终止的时间最长为 millis 毫秒 + nanos 纳秒。如果在millis时间内,该线程没有执行完,那么当前线程进入就绪状态,重新等待cpu调度
void join(long millis,int nanos)
使用举例:将主线程加入到子线程后面,不过如果子线程在 1 毫秒时间内没执行完,则主线程便不再等待它执行完,进入就绪状态,等待 cpu 调度。
public class Test1 {
public static void main(String[] args) throws InterruptedException {
MyThread t = new MyThread();
t.start();
t.join(1);// 将主线程加入到子线程后面,不过如果子线程在1毫秒时间内没执行完,则主线程便不再等待它执行完,进入就绪状态,等待cpu调度
for (int i = 0; i < 30; i++) {
System.out.println("main线程第" + i + "次执行!");
}
}
}
class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("MyThread线程第" + i + "次执行!");
}
}
}
join 实现:三个 join 方法都调用同一个 join(long millis)方法,join 其实就是通过将主线程 wait 相应时间来实现的。
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) {
// 只要子线程MyThread isAlve,主线程就一直挂起
while (isAlive()) {
wait(0);
}
} else {
// 1.delay时间>0,主线程wait delay时间
// 2.主线程自动唤醒之后,再次检查如果子线程MyThread isAlive且delay时间还没到就就继续将主线程wait
// 3.循环1 2 ,直到子线程MyThread执行完或者主线程wait时间超过millis
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
sleep 方法是 Thread 的静态方法,sleep 让线程进入到阻塞状态,交出 CPU,让 CPU 去执行其他的任务。
sleep 方法不会释放锁。
yield 方法是 Thread 的静态方法,yield 方法让当前正在执行的线程进入到就绪状态,让出 CPU 资源给其他的线程。
注意:
yield 方法只是让当前线程暂停一下,重新进入就绪线程池中,让系统的线程调度器重新调度器重新调度一次,完全可能出现这样的情况:当某个线程调用 yield()方法之后,线程调度器又将其调度出来重新进入到运行状态执行。
先来复习一下 synchronized 监视器锁 monitor 的实现原理。
Monitor 中有几个关键属性:
_owner:指向持有ObjectMonitor对象的线程
_WaitSet:存放处于wait状态的线程队列
_EntryList:存放处于等待锁block状态的线程队列
_recursions:锁的重入次数
_count:用来记录该线程获取锁的次数
Monitor
同步队列(锁池/_EntryList):由于线程没有竞争到锁,只能等待锁释放之后再去竞争,此时线程就处于该对象的同步队列(锁池)中,线程状态为 BLOCKED。
等待队列(等待池/_WaitSet):线程调用了 wait 方法后被挂起,等待 notify 唤醒或者挂起时间到自动唤醒,此时线程就处于该对象的等待队列(等待池)中,线程状态为 WAITING 或者 TIMED_WAITING。
wait 方法:释放持有的对象锁,线程状态由 RUNNING 变为 WAITING,并将当前线程放置到对象的等待队列;
notify 方法:在目标对象的等待集合中随意选择一个线程 T,将线程 T 从等待队列移到同步队列重新竞争锁,线程状态由 WAITING 变为 BLOCKED。
notifyAll 方法:notifyAll 方法与 notify 方法的运行机制是一样的,只是将等待队列中所有的线程全部移到同步队列。
通过设置线程优先级属性可以改变线程被 CPU 调度的机会,需要注意线程优先级不能作为程序正确性的依赖。
Daemon 线程是一种支持型线程,在后台守护一些系统服务,当只有守护线程的时候,程序就会自然退出。
线程中断代表线程状态,每个线程都关联了一个用 boolean 值表示中断状态。当一个线程处于 sleep、wait、join 这三种状态之一时,线程中断会抛出一个 InterruptedException 的异常。
线程调度还有 Thread 类的 join、sleep、yield 方法,Object 的 wait、notify/notifyAll 方法。