Thread类之Java并发编程的基础

Thread

Posted by Jay on July 3, 2019

Thread类之Java并发编程的基础

一、线程的简介和一些属性

1.ThreadMXBean获取线程相关信息
public class MultiThread {
	public static void main(String[] args) {
		// 获取Java线程管理MXBean,一个Java虚拟机只有该接口的一个单独实例(一个Java进程)
		ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();

		// 获取线程信息
		ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(false, false);

		// 打印线程id和name
		for (ThreadInfo threadInfo : threadInfos) {
			System.out.println("[" + threadInfo.getThreadId() + "] " + threadInfo.getThreadName());
		}
	}
}
// 输出
/**
[5] Monitor Ctrl-Break
[4] Signal Dispatcher
[3] Finalizer
[2] Reference Handler
[1] main
*/
2.线程优先级(priority)

Java线程中,线程优先级的范围为1至10,默认优先级为5。设置线程优先级时,针对频繁阻塞(休眠或者I/O操作)的线程需要设置较高的优先级,而偏计算(需要较多CPU时间或偏运算)的线程则设置较低的优先级,确保处理器不会被占用。在不同的JVM和操作系统上,线程设置存在差异,有些操作系统会忽略对线程优先级的设置

public class Priority {
	private static volatile boolean notStart = true;
	private static volatile boolean notEnd = true;

	public static void main(String[] args) throws InterruptedException {
		List<Task> tasks = new ArrayList<>();

		for (int i = 0; i < 10; i++) {
			int priority = i < 5 ? Thread.MIN_PRIORITY : Thread.MAX_PRIORITY;
			Task task = new Task(priority);
			tasks.add(task);
			Thread thread = new Thread(task, "Thread: " + i);
			thread.setPriority(priority);
			thread.start();
		}

		// 10个线程开始计数
		notStart = false;
		// 主线程main Sleep 10s
		TimeUnit.SECONDS.sleep(10);
		// 10个线程计数结束
		notEnd = false;

		for (Task task : tasks) {
			System.out.println("Task Priority: " + task.priority + ", count: " + task.count);
		}

	}

	static class Task implements Runnable {
		private int priority;
		private int count;

		public Task(int priority) {
			this.priority = priority;
		}

		@Override
		public void run() {
			while (notStart) {
				// 当前线程让出CPU执行权,由RUNNING转为READY就绪状态,
				// 下次这个线程仍有可能执行
				Thread.yield();
			}
			while (notEnd) {
				Thread.yield();
				count++;
			}
		}
	}

}

// 输出,最终的count计数基本相同
/**
Task Priority: 1, count: 690565
Task Priority: 1, count: 690718
Task Priority: 1, count: 692321
Task Priority: 1, count: 691728
Task Priority: 1, count: 691294
Task Priority: 10, count: 691301
Task Priority: 10, count: 691471
Task Priority: 10, count: 691912
Task Priority: 10, count: 691599
Task Priority: 10, count: 690636
*/
3.线程的状态

Java线程在其生命周期中有如下的6种状态:

线程在自身的生命周期中,随着代码的执行,在不同的状态之间切换。如下图所示:

线程状态转换

注:①操作系统中的运行和就绪两种状态在Java中称为RUNNABLE状态;②BLOCKED状态是线程阻塞在进入synchronized方法或代码块时的状态;③阻塞在concurrent包中Lock接口实现的线程状态是WAITING状态,因为concurrent包中的Lock接口对于阻塞的实现使用了LockSupport类中的相关方法。

4.Daemon线程

守护线程,主要用于程序中后台调度以及支持性工作。当一个Java虚拟机中不存在非Daemon线程时,Java虚拟机将退出。Thread.setDaemon(boolean)用于设置线程是否为Daemon线程,需在线程启动前设置。

Java虚拟机退出时,Daemon线程中的finally块并不一定会执行,如下所示:

// 无任何输出
public class Daemon {
	public static void main(String[] args){
	    Thread thread = new Thread(new DaemonRunner(), "DaemonRunner");
	    // 设置为daemon线程
	    thread.setDaemon(true);
	    thread.start();
	}

	static class DaemonRunner implements Runnable {
		@Override
		public void run() {
			try {
				SleepUtils.second(10);
			} finally {
				System.out.println("DaemonThread finally run.");
			}
		}
	}
}

因此,不能依靠Daemon线程中finally块中的内容来确保执行关闭或者清理资源的逻辑。

二、启动和终止线程

1.线程初始化与启动

线程在运行之前首先需要初始化,线程在初始化时需要提供一些属性如线程所属的线程组ThreadGroup、线程名name、线程优先级priority、是否为Daemon线程等,Thread中简化的初始化过程如下所示:

新线程对象由父线程进行空间分配,且新线程继承了父线程是否为Daemon线程、优先级、加载资源的contextClassLoader以及可继承的ThreadLocal等属性,也分配了一个惟一的TID表示这个子线程。

线程初始化之后,通过调用start()方法启动线程。父线程同步告知Java虚拟机,只要线程调度器空闲,应立即执行线程。

2.中断interrupt

中断是线程的一个标识位属性,表示一个运行中的线程是否被其他线程进行了中断操作。其他线程调用该线程的interrupt()方法对其进行中断操作。

线程可通过isInterrupted()实例方法和interrupted()静态方法判断自己是否被中断。interrupted()静态方法在判断是否被中断的同时,会清除中断标志位。

  • 对于许多声明抛出InterruptedException异常的方法,如果某线程的这个方法在运行中被中断,则在抛出InterruptedException异常之前,中断标志位会被清除,然后再抛出该异常。
  • 对于未声明抛出InterruptedException异常的方法,如果某线程的这个方法在运行中被中断,则中断标志位置位,不会被清除,程序可通过Thread.currentThread().isInterrupted()方法进行判断。

以下是中断的测试用例:

// 中断测试,查看中断标志位
public class InterruptedTest {
	public static void main(String[] args) {
		Thread sleepThread = new Thread(new SleepRunner(), "SleepRunner");
		sleepThread.setDaemon(true);

		Thread busyThread = new Thread(new BusyRunner(), "BusyRunner");
		busyThread.setDaemon(true);

		sleepThread.start();
		busyThread.start();
		// 等待5s
		SleepUtils.second(5);
		// 触发中断
		sleepThread.interrupt();
		busyThread.interrupt();

		// sleep线程 抛出异常,清除中断标志位,线程正常运行  false
		System.out.println("SleepThread interrupted is " + sleepThread.isInterrupted());
		// 中断标志位置位,不会被清除,线程正常运行  true
		System.out.println("BusyThread interrupted is " + busyThread.isInterrupted());

		SleepUtils.second(25);
	}

	// 一直睡眠
	static class SleepRunner implements Runnable {
		private static int num = 0;

		@Override
		public void run() {
			while (true) {
				SleepUtils.second(10);
				System.out.println("Here " + num++);
			}
		}
	}

	// 一直执行
	static class BusyRunner implements Runnable {
		@Override
		public void run() {
			while (true) {

			}
		}
	}

}
3.安全地终止线程

一般有以下两种方式可用于安全地终止线程:

  • 因为中断状态是线程的一个标志位,且中断操作是一种线程间交互的方式,因此可以使用中断操作来终止线程的执行。
  • 利用一个volatileboolean变量,控制线程的运行。
// 正确地关闭线程
public class CorrectShutdownTest {
	public static void main(String[] args) {
		Runner one = new Runner();
		Thread countThread = new Thread(one, "CountThreadOne");
		countThread.start();

		SleepUtils.second(1);
		// 根据中断状态终止线程
		countThread.interrupt();

		Runner two = new Runner();
		Thread countThread2 = new Thread(two, "CountThreadTwo");
		countThread2.start();

		SleepUtils.second(1);
		// 根据volatile boolean变量终止线程
		two.cancel();
	}

	/**
	 * 计数线程
	 */
	private static class Runner implements Runnable {
		private int count = 0;
		/** 开关变量 */
		private volatile boolean on = true;

		@Override
		public void run() {
			// 根据中断标志位和volatile boolean变量进行判断,终止线程
			while (on && !Thread.currentThread().isInterrupted()) {
				count++;
			}
			System.out.println(Thread.currentThread().getName() + " Count: " + count);
		}

		public void cancel() {
			on = false;
		}
	}

}