最近在学习《Java并发编程的艺术》这本书,想把自己的一些学习经历记录下来。
线程简介
首先,线程的基本概念就不再赘述,随便搜索一下就能查到。要明确的一点是,即使是一个普通的main方法,也有很多线程参与其中,如下:
public static void main(String[] args) {
// 获取Java线程管理的MXBean
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(false, false);
for (ThreadInfo threadInfo : threadInfos) {
System.out.println("[" + threadInfo.getThreadId() + "]" + threadInfo.getThreadName());
}
}
以下是运行在macOS 10.13上的结果:
[5]Monitor Ctrl-Break
[4]Signal Dispatcher
[3]Finalizer
[2]Reference Handler
[1]main
关于这几个线程的解释,可以参考这篇文章
启动线程
创建线程的两种方式就不多说了,继承Thread或者实现Runnable,需要注意的是,如果实现Runnable接口,必须new Thread(runnable).start(),start()方法被用来启动新创建的线程,而且start()内部调用了run()方法,这和直接调用run()方法的效果不一样。当直接调用run()方法的时候,只会是在原来的线程中调用,没有新的线程启动,start()方法才会启动新线程。
查看Thread类start方法的源码
/**
* Causes this thread to begin execution; the Java Virtual Machine
* calls the <code>run</code> method of this thread.
* <p>
* The result is that two threads are running concurrently: the
* current thread (which returns from the call to the
* <code>start</code> method) and the other thread (which executes its
* <code>run</code> method).
* <p>
* It is never legal to start a thread more than once.
* In particular, a thread may not be restarted once it has completed
* execution.
*
* @exception IllegalThreadStateException if the thread was already
* started.
* @see #run()
* @see #stop()
*/
public synchronized void start() {
/**
* This method is not invoked for the main method thread or "system"
* group threads created/set up by the VM. Any new functionality added
* to this method in the future may have to also be added to the VM.
*
* A zero status value corresponds to state "NEW".
*/
if (threadStatus != 0)
throw new IllegalThreadStateException();
/* Notify the group that this thread is about to be started
* so that it can be added to the group's list of threads
* and the group's unstarted count can be decremented. */
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
private native void start0();
/**
* If this thread was constructed using a separate
* <code>Runnable</code> run object, then that
* <code>Runnable</code> object's <code>run</code> method is called;
* otherwise, this method does nothing and returns.
* <p>
* Subclasses of <code>Thread</code> should override this method.
*
* @see #start()
* @see #stop()
* @see #Thread(ThreadGroup, Runnable, String)
*/
@Override
public void run() {
if (target != null) {
target.run();
}
}
可以看到在start方法的说明中,会由JVM去为这个线程调用run方法。
start方法中调用了start0方法,该方法是个native方法。
run方法中调用了runnable中实现的run方法。
线程优先级
现代操作系统调度线程采用的是时间分片的形式,而线程优先级就是决定线程需要多或者少分配一些处理器资源的线程属性。在Java中,通过priority来控制优先级,默认为5,范围为1-10。有些操作系统会忽略对线程优先级的设定。
private static volatile boolean notStart = true;
private static volatile boolean notEnd = true;
public static void main(String[] args) throws Exception {
List<Job> jobs = new ArrayList<>();
for (int i = 0; i < 10; i++) {
int priority = i < 5 ? Thread.MIN_PRIORITY : Thread.MAX_PRIORITY;
Job job = new Job(priority);
jobs.add(job);
Thread thread = new Thread(job, "Thread:" + i);
thread.setPriority(priority);
thread.start();
}
notStart = false;
TimeUnit.SECONDS.sleep(10);
notEnd = false;
for (Job job : jobs) {
System.out.println("Job Priority:" + job.priority + ",Count:" + job.jobCount);
}
}
static class Job implements Runnable {
private int priority;
private long jobCount;
public Job(int priority) {
this.priority = priority;
}
@Override
public void run() {
while (notStart) {
Thread.yield();
}
while (notEnd) {
Thread.yield();
jobCount++;
}
}
}
以下是运行在macOS 10.13上的结果:
Job Priority:1,Count:10929483
Job Priority:1,Count:10826630
Job Priority:1,Count:10840996
Job Priority:1,Count:10736457
Job Priority:1,Count:10603571
Job Priority:10,Count:10817804
Job Priority:10,Count:10866459
Job Priority:10,Count:10581152
Job Priority:10,Count:10550895
Job Priority:10,Count:10543968
可以看到优先级1和优先级10计数的结果非常相近,没有明显的差距,操作系统完全忽略了java线程优先级的设定,因此线程优先级不能作为程序正确性的依赖。
线程的状态
Java线程在运行的生命周期中可能处于下表的6种不同的状态,在给定的一个时刻,线程只能处于其中的一个状态。
状态名称 | 说明 |
---|---|
NEW | 初始状态,线程被构建,但是还没有调用start()方法 |
RUNNABLE | 运行状态,Java线程将操作系统中的就绪和运行两种状态统称为“运行中” |
BLOCKED | 阻塞状态,表示线程阻塞于锁 |
WAITING | 等待状态,表示线程进入等待状态,进入该状态表示当前线程需要等待其他线程做出一些特定动作(通知或中断) |
TIME_WAITING | 超时等待状态,该状态不同于WAITING,它是可以在指定的时间自行返回的 |
TERMINATED | 终止状态,表示当前线程已经执行完毕 |
下面通过一个案例来深入理解线程状态,会用到jstack工具,代码如下
public static void main(String[] args) {
new Thread(new TimeWaiting(),"TimeWaitingThread").start();
new Thread(new Waiting(),"WaitingThread").start();
// 使用两个Blocked线程,一个获取锁成功,另一个被阻塞
new Thread(new Blocked(),"BlockedThread-1").start();
new Thread(new Blocked(),"BlockedThread-2").start();
}
/***
* 该线程不断进行睡眠
*/
static class TimeWaiting implements Runnable{
@Override
public void run() {
while (true){
SleepUtils.second(100);
}
}
}
// 该线程在Waiting.class实例上等待
static class Waiting implements Runnable{
@Override
public void run() {
while (true){
synchronized (Waiting.class){
try {
Waiting.class.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
// 该线程在Blocked.class实例上加锁后,不会释放该锁,而是不断进行睡眠
static class Blocked implements Runnable{
@Override
public void run() {
synchronized (Blocked.class){
while (true){
SleepUtils.second(100);
}
}
}
}
static class SleepUtils{
public static final void second(long seconds){
try {
TimeUnit.SECONDS.sleep(seconds);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
运行该示例,进入终端,用jps命令找到进程的ID,比如929,接着输入jstack 929,我截取了部分输出,如下
// BlockedThread-2线程一直阻塞在获取锁上
"BlockedThread-2" #14 prio=5 os_prio=31 tid=0x00007f9f3a810800 nid=0xa103 waiting for monitor entry [0x0000700005934000]
java.lang.Thread.State: BLOCKED (on object monitor)
at Thread.WhatThread.ThreadState$Blocked.run(ThreadState.java:65)
- waiting to lock <0x000000076ac30488> (a java.lang.Class for Thread.WhatThread.ThreadState$Blocked)
at java.lang.Thread.run(Thread.java:748)
// BlockedThread-1线程获取到了锁,一直在sleeping,也就是超时等待状态
"BlockedThread-1" #13 prio=5 os_prio=31 tid=0x00007f9f3c010000 nid=0x5903 waiting on condition [0x0000700005831000]
java.lang.Thread.State: TIMED_WAITING (sleeping)
at java.lang.Thread.sleep(Native Method)
at java.lang.Thread.sleep(Thread.java:340)
at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
at Thread.WhatThread.ThreadState$SleepUtils.second(ThreadState.java:74)
at Thread.WhatThread.ThreadState$Blocked.run(ThreadState.java:65)
- locked <0x000000076ac30488> (a java.lang.Class for Thread.WhatThread.ThreadState$Blocked)
at java.lang.Thread.run(Thread.java:748)
// WaitingThread一直处于等待状态
"WaitingThread" #12 prio=5 os_prio=31 tid=0x00007f9f3b0be000 nid=0xa303 in Object.wait() [0x000070000572e000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x000000076ac2c510> (a java.lang.Class for Thread.WhatThread.ThreadState$Waiting)
at java.lang.Object.wait(Object.java:502)
at Thread.WhatThread.ThreadState$Waiting.run(ThreadState.java:49)
- locked <0x000000076ac2c510> (a java.lang.Class for Thread.WhatThread.ThreadState$Waiting)
at java.lang.Thread.run(Thread.java:748)
// TimeWaitingThread线程也一直处于超时等待状态
"TimeWaitingThread" #11 prio=5 os_prio=31 tid=0x00007f9f3b0bd800 nid=0xa503 waiting on condition [0x000070000562b000]
java.lang.Thread.State: TIMED_WAITING (sleeping)
at java.lang.Thread.sleep(Native Method)
at java.lang.Thread.sleep(Thread.java:340)
at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
at Thread.WhatThread.ThreadState$SleepUtils.second(ThreadState.java:74)
at Thread.WhatThread.ThreadState$TimeWaiting.run(ThreadState.java:36)
at java.lang.Thread.run(Thread.java:748)
线程在自身的生命周期中,并不是固定地处于某个状态,而是随着代码的执行在不同的状态之间进行切换
守护线程
守护线程的概念就不多说了,随便搜索一下就能查到,需要注意的是,如果将一个线程设置为守护线程,那么需要在这个线程start之前设置,并且如果run方法内有finally块,那么这个finally块不一定会被执行。
线程中断
中断可以理解为线程的一个标识位属性。其他线程通过调用该线程的interrupt方法来对其进行中断操作。
线程通过检查自身是否被中断来进行响应,主要通过isInterrupter()方法或者Thread.interrupted()方法。在Java中,许多会抛出InterruptedException的方法,JVM会将标识位进行清除。可以通过以下代码观察这个结论
public static void main(String[] args) {
Thread sleepThread = new Thread(new SleepRunner(), "SleepThread");
Thread busyThread = new Thread(new BusyRunner(), "BusyThread");
sleepThread.start();
busyThread.start();
SleepUtils.second(5);
sleepThread.interrupt();
busyThread.interrupt();
System.out.println("SleepThread interrupted is " + sleepThread.isInterrupted());
System.out.println("BusyThread interrupted is " + busyThread.isInterrupted());
}
static class SleepRunner implements Runnable{
@Override
public void run() {
while (true){
SleepUtils.second(10);
}
}
}
static class BusyRunner implements Runnable{
@Override
public void run() {
while (true){
}
}
}
static class SleepUtils{
public static final void second(long seconds){
try {
TimeUnit.SECONDS.sleep(seconds);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
在SleepThread中打断会触发InterruptedException,中断标记会被清除,结果如下
SleepThread interrupted is false
BusyThread interrupted is true
安全地终止线程
上文提到的中断状态是线程的一个标识位,可以用来取消或停止任务。除了中断之外,还可以利用boolean变量来控制是否需要停止任务并终止线程。
public static void main(String[] args) throws Exception{
Runner one = new Runner();
Runner two = new Runner();
Thread countThread_1 = new Thread(one, "CountThread-1");
Thread countThread_2 = new Thread(two, "CountThread-2");
countThread_1.start();
countThread_2.start();
TimeUnit.SECONDS.sleep(1);
countThread_1.interrupt();
two.cancel();
}
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;
}
}
这种做法可以让线程在终止时有机会去清理资源,比起武断的终止线程,这种做法更加优雅安全。