参考文章:
Thread源码剖析
多线程全面详解总结
Java多线程之interrupt()的深度研究
jdk版本:1.8.0_201
1. 线程名
我们在使用多线程的时候,想要查看线程名是很简单的,调用Thread.currentThread().getName()
即可。
如果没有做什么的设置,我们会发现线程的名字是这样子的:主线程叫做main,其他线程是Thread-x.
下面来看它的实现方法:
/**
* Allocates a new {@code Thread} object. This constructor has the same
* effect as {@linkplain #Thread(ThreadGroup,Runnable,String) Thread}
* {@code (null, null, gname)}, where {@code gname} is a newly generated
* name. Automatically generated names are of the form
* {@code "Thread-"+}<i>n</i>, where <i>n</i> is an integer.
*/
public Thread() {
init(null, null, "Thread-" + nextThreadNum(), 0);
}
nextThreadNum()
方法实现:
/* For autonumbering anonymous threads. */
private static int threadInitNumber;
private static synchronized int nextThreadNum() {
return threadInitNumber++;
}
init
方法中,设置线程名的地方:
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
if (name == null) {
throw new NullPointerException("name cannot be null");
}
this.name = name;
Thread类还提供了设置线程名的方法:
public final synchronized void setName(String name) {
checkAccess();
if (name == null) {
throw new NullPointerException("name cannot be null");
}
this.name = name;
if (threadStatus != 0) {
setNativeName(name);
}
}
检查是否有权限的方法checkAccess()
:
public final void checkAccess() {
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkAccess(this);
}
}
测试一下:
public class MyThread implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
public static void main(String[] args) {
Runnable thread = new MyThread();
Thread thread1 = new Thread(thread, "set name1");
Thread thread2 = new Thread(thread, "set name2");
thread2.setName("set name3");
thread1.start();
thread2.start();
System.out.println(Thread.currentThread().getName());
}
}
运行结果:
main
set name3
set name1
2. 守护线程
守护线程是为其他线程服务的
- 垃圾回收线程就是守护线程
守护线程有一个特点:
- 当别的用户线程执行完了,虚拟机就会退出,守护线程也就会被停止掉了。
- 也就是说:守护线程作为一个服务线程,没有服务对象就没有必要继续运行了
使用线程的时候要注意的地方
- 在线程启动前设置为守护线程,方法是
setDaemon(boolean on)
- 使用守护线程不要访问共享资源(数据库、文件等),因为它可能会在任何时候就挂掉了。
- 守护线程中产生的新线程也是守护线程
setDaemon(boolean on)
方法如下:
public final void setDaemon(boolean on) {
checkAccess();
if (isAlive()) {
throw new IllegalThreadStateException();
}
daemon = on;
}
可以看到,如果线程已经启动,则会抛出异常。
测试一下:
public class MyThread implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
public static void main(String[] args) {
Runnable thread = new MyThread();
Thread thread1 = new Thread(thread, "set name1");
Thread thread2 = new Thread(thread, "set name2");
thread2.setName("set name3");
thread2.setDaemon(true);
thread1.start();
thread2.start();
System.out.println(Thread.currentThread().getName());
}
}
运行结果:
set name1
set name3
main
3. 优先级线程
线程优先级高仅仅表示线程获取的CPU时间片的几率高,但这不是一个确定的因素。
线程的优先级是高度依赖于操作系统的,Windows和Linux就有所区别(Linux下优先级可能就被忽略了)
可以看到的是,Java提供的优先级默认是5,最低是1,最高是10:
/**
* The minimum priority that a thread can have.
*/
public final static int MIN_PRIORITY = 1;
/**
* The default priority that is assigned to a thread.
*/
public final static int NORM_PRIORITY = 5;
/**
* The maximum priority that a thread can have.
*/
public final static int MAX_PRIORITY = 10;
设置优先级的方法:
public final void setPriority(int newPriority) {
ThreadGroup g;
checkAccess();
if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {
throw new IllegalArgumentException();
}
if((g = getThreadGroup()) != null) {
if (newPriority > g.getMaxPriority()) {
newPriority = g.getMaxPriority();
}
setPriority0(priority = newPriority);
}
}
private native void setPriority0(int newPriority);
4. 线程生命周期
4.1 线程睡眠—sleep
线程睡眠的方法:
- sleep(long millis) 在指定的毫秒数内让正在执行的线程休眠。
- sleep(long millis, int nanos) 在指定的毫秒数加指定的纳秒数内让正在执行的线程休眠。
public static native void sleep(long millis) throws InterruptedException;
public static void sleep(long millis, int nanos)
throws InterruptedException {
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (nanos < 0 || nanos > 999999) {
throw new IllegalArgumentException(
"nanosecond timeout value out of range");
}
if (nanos >= 500000 || (nanos != 0 && millis == 0)) {
millis++;
}
sleep(millis);
}
调用sleep方法会进入阻塞状态,等时间到了,进入的是就绪状态而并非是运行状态。
测试一下:
public class MyThread implements Runnable {
private int time = 5;
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss:S");
@Override
public void run() {
while (time >= 0) {
System.out.println(Thread.currentThread().getName() + ":" + time-- + " " + sdf.format(new Date()));
try {
// 睡眠1秒
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
Runnable thread = new MyThread();
Thread thread1 = new Thread(thread, "倒计时1");
Thread thread2 = new Thread(thread, "倒计时2");
thread1.start();
thread2.start();
}
}
运行结果:
倒计时1:5 09:17:59:87
倒计时2:4 09:17:59:87
倒计时2:3 09:18:00:88
倒计时1:2 09:18:00:88
倒计时1:1 09:18:01:88
倒计时2:0 09:18:01:89
注意:
Java线程调度是Java多线程的核心,只有良好的调度,才能充分发挥系统的性能,提高程序的执行效率。但是不管程序员怎么编写调度,只能最大限度的影响线程执行的次序,而不能做到精准控制。因为使用sleep方法之后,线程是进入阻塞状态的,只有当睡眠的时间结束,才会重新进入到就绪状态,而就绪状态进入到运行状态,是由系统控制的,我们不可能精准的去干涉它,所以如果调用Thread.sleep(1000)使得线程睡眠1秒,可能结果会大于1秒,就像本例。
4.2 线程让步—yield
该方法和sleep方法类似,也是Thread类提供的一个静态方法,可以让正在执行的线程暂停,但是不会进入阻塞状态,而是直接进入就绪状态。相当于只是将当前线程暂停一下,然后重新进入就绪的线程池中,让线程调度器重新调度一次。也会出现某个线程调用yield方法后暂停,但之后调度器又将其调度出来重新进入到运行状态。
查看源码:
/**
* A hint to the scheduler that the current thread is willing to yield
* its current use of a processor. The scheduler is free to ignore this
* hint.
*
* <p> Yield is a heuristic attempt to improve relative progression
* between threads that would otherwise over-utilise a CPU. Its use
* should be combined with detailed profiling and benchmarking to
* ensure that it actually has the desired effect.
*
* <p> It is rarely appropriate to use this method. It may be useful
* for debugging or testing purposes, where it may help to reproduce
* bugs due to race conditions. It may also be useful when designing
* concurrency control constructs such as the ones in the
* {@link java.util.concurrent.locks} package.
*/
public static native void yield();
通过注释可以看到,这个方法很少用到,调用yield方法会先让别的线程执行,但是不确保真正让出CPU。
测试一下:
public class MyThread implements Runnable {
private int time = 10;
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss:S");
@Override
public void run() {
while (time >= 0) {
System.out.println(Thread.currentThread().getName() + ":" + time-- + " " + sdf.format(new Date()));
if (time % 2 == 0) {
Thread.yield();
}
}
}
public static void main(String[] args) {
Runnable thread = new MyThread();
Thread thread1 = new Thread(thread, "倒计时1");
Thread thread2 = new Thread(thread, "倒计时2");
thread1.start();
thread2.start();
}
}
运行结果:
倒计时1:10 09:38:43:105
倒计时2:9 09:38:43:105
倒计时1:8 09:38:43:107
倒计时2:7 09:38:43:107
倒计时1:6 09:38:43:107
倒计时1:5 09:38:43:107
倒计时1:3 09:38:43:107
倒计时2:4 09:38:43:107
倒计时1:2 09:38:43:107
倒计时2:1 09:38:43:108
倒计时1:0 09:38:43:108
sleep和yield的区别:
①、sleep方法声明抛出InterruptedException,调用该方法需要捕获该异常。yield没有声明异常,也无需捕获。
②、sleep方法暂停当前线程后,会进入阻塞状态,只有当睡眠时间到了,才会转入就绪状态。而yield方法调用后 ,是直接进入就绪状态。
4.3 线程合并—join
调用join方法,会等待该线程执行完毕后才执行别的线程。
查看源码:
/**
* Waits for this thread to die.
*
* <p> An invocation of this method behaves in exactly the same
* way as the invocation
*
* <blockquote>
* {@linkplain #join(long) join}{@code (0)}
* </blockquote>
*
* @throws InterruptedException
* if any thread has interrupted the current thread. The
* <i>interrupted status</i> of the current thread is
* cleared when this exception is thrown.
*/
public final void join() throws InterruptedException {
join(0);
}
第一句话就说明了,需要等待这个线程死亡,也就是调用完run()
方法。
继续看源码:
/**
* Waits at most {@code millis} milliseconds for this thread to
* die. A timeout of {@code 0} means to wait forever.
*
* <p> This implementation uses a loop of {@code this.wait} calls
* conditioned on {@code this.isAlive}. As a thread terminates the
* {@code this.notifyAll} method is invoked. It is recommended that
* applications not use {@code wait}, {@code notify}, or
* {@code notifyAll} on {@code Thread} instances.
*
* @param millis
* the time to wait in milliseconds
*
* @throws IllegalArgumentException
* if the value of {@code millis} is negative
*
* @throws InterruptedException
* if any thread has interrupted the current thread. The
* <i>interrupted status</i> of the current thread is
* cleared when this exception is thrown.
*/
从注释可以看出,0代表永远等待。
循环调用wait
方法,当线程终止了,会调用notifyAll
方法来唤醒。
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;
}
}
}
public final native void wait(long timeout) throws InterruptedException;
测试一下:
public class MyThread implements Runnable {
private int time = 10;
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss:S");
@Override
public void run() {
while (time >= 0) {
System.out.println(Thread.currentThread().getName() + ":" + time-- + " " + sdf.format(new Date()));
}
if (time < 0) {
System.out.println(Thread.currentThread().getName() + ": end");
}
}
public static void main(String[] args) {
Runnable thread = new MyThread();
Thread thread1 = new Thread(thread, "倒计时1");
Thread thread2 = new Thread(thread, "倒计时2");
thread1.start();
try {
thread1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
thread2.start();
}
}
运行结果:
倒计时1:10 09:44:23:31
倒计时1:9 09:44:23:31
倒计时1:8 09:44:23:32
倒计时1:7 09:44:23:32
倒计时1:6 09:44:23:32
倒计时1:5 09:44:23:32
倒计时1:4 09:44:23:32
倒计时1:3 09:44:23:32
倒计时1:2 09:44:23:32
倒计时1:1 09:44:23:32
倒计时1:0 09:44:23:32
倒计时1: end
倒计时2: end
4.4 终止线程—interrupt
线程中断在之前的版本有stop方法,但是被设置过时了。现在已经没有强制线程终止的方法了。
由于stop方法可以让一个线程A终止掉另一个线程B
- 被终止的线程B会立即释放锁,这可能会让对象处于不一致的状态。
- 线程A也不知道线程B什么时候能够被终止掉,万一线程B还处理运行计算阶段,线程A调用stop方法将线程B终止,那就很无辜了。
总而言之,Stop方法太暴力了,不安全,所以被设置过时了。
我们一般使用的是interrupt来请求终止线程。
- 要注意的是:interrupt不会真正停止一个线程,它仅仅是给这个线程发了一个信号告诉它,它应该要结束了(明白这一点非常重要)
- 也就是说:Java设计者实际上是想线程自己来终止,通过上面的信号,就可以判断处理什么业务了。
- 具体到底中断还是继续运行,应该由被通知的线程自己处理
Thread t1 = new Thread( new Runnable(){
public void run(){
// 若未发生中断,就正常执行任务
while(!Thread.currentThread.isInterrupted()){
// 正常任务代码……
}
// 中断的处理代码……
doSomething();
}
} ).start();
再次说明:调用interrupt()
并不是要真正终止掉当前线程,仅仅是设置了一个中断标志。这个中断标志可以给我们用来判断什么时候该干什么活,什么时候中断由我们自己来决定,这样就可以安全地终止线程了。
看源码:
/**
* Interrupts this thread.
*
中断当前线程
* <p> Unless the current thread is interrupting itself, which is
* always permitted, the {@link #checkAccess() checkAccess} method
* of this thread is invoked, which may cause a {@link
* SecurityException} to be thrown.
*
只能自己调用中断方法,不然会抛出安全异常
* <p> If this thread is blocked in an invocation of the {@link
* Object#wait() wait()}, {@link Object#wait(long) wait(long)}, or {@link
* Object#wait(long, int) wait(long, int)} methods of the {@link Object}
* class, or of the {@link #join()}, {@link #join(long)}, {@link
* #join(long, int)}, {@link #sleep(long)}, or {@link #sleep(long, int)},
* methods of this class, then its interrupt status will be cleared and it
* will receive an {@link InterruptedException}.
*
* <p> If this thread is blocked in an I/O operation upon an {@link
* java.nio.channels.InterruptibleChannel InterruptibleChannel}
* then the channel will be closed, the thread's interrupt
* status will be set, and the thread will receive a {@link
* java.nio.channels.ClosedByInterruptException}.
*
* <p> If this thread is blocked in a {@link java.nio.channels.Selector}
* then the thread's interrupt status will be set and it will return
* immediately from the selection operation, possibly with a non-zero
* value, just as if the selector's {@link
* java.nio.channels.Selector#wakeup wakeup} method were invoked.
*
前面说了,java设计者设置中断标志的目的是想由被通知的线程自己处理,而这些方式都阻塞掉了。
被阻塞掉的线程调用中断方法是不合理的(不允许中断已阻塞的线程),因为可能会造成中断无效。
* <p> If none of the previous conditions hold then this thread's interrupt
* status will be set. </p>
*
上面的三种情况都不发生,才能将对应的标志位置换
* <p> Interrupting a thread that is not alive need not have any effect.
*
中断一个不活动的线程是没有意义的
* @throws SecurityException
* if the current thread cannot modify this thread
*
* @revised 6.0
* @spec JSR-51
*/
public void interrupt() {
if (this != Thread.currentThread())
checkAccess();
synchronized (blockerLock) {
/ 看看是不是阻塞的线程调用,如sleep、wait、join
Interruptible b = blocker;
/ 如果是,抛出异常,将中断标志位改为false
/ 一般我们会在catch中再次修改
if (b != null) {
interrupt0(); // Just to set the interrupt flag
b.interrupt(this);
return;
}
}
/ 如果很顺利,就可以直接修改标志位
interrupt0();
}
现在看下抛出的异常InterruptedException
:
/**
* Thrown when a thread is waiting, sleeping, or otherwise occupied,
* and the thread is interrupted, either before or during the activity.
* Occasionally a method may wish to test whether the current
* thread has been interrupted, and if so, to immediately throw
* this exception. The following code can be used to achieve
* this effect:
* <pre>
* if (Thread.interrupted()) // Clears interrupted status!
* throw new InterruptedException();
* </pre>
*
* @author Frank Yellin
* @see java.lang.Object#wait()
* @see java.lang.Object#wait(long)
* @see java.lang.Object#wait(long, int)
* @see java.lang.Thread#sleep(long)
* @see java.lang.Thread#interrupt()
* @see java.lang.Thread#interrupted()
* @since JDK1.0
*/
当线程正在执行sleep、wait等方法时,调用线程中断,就会抛出这个异常
public
class InterruptedException extends Exception {
private static final long serialVersionUID = 6700697376100628473L;
/**
* Constructs an <code>InterruptedException</code> with no detail message.
*/
public InterruptedException() {
super();
}
/**
* Constructs an <code>InterruptedException</code> with the
* specified detail message.
*
* @param s the detail message.
*/
public InterruptedException(String s) {
super(s);
}
}
所以说:interrupt方法压根是不会对线程的状态造成影响的,它仅仅设置一个标志位罢了
interrupt线程中断还有另外两个方法(检查该线程是否被中断):
- 静态方法interrupted()–>会清除中断标志位
- 实例方法isInterrupted()–>不会清除中断标志位
/**
* Tests whether the current thread has been interrupted. The
* <i>interrupted status</i> of the thread is cleared by this method. In
* other words, if this method were to be called twice in succession, the
* second call would return false (unless the current thread were
* interrupted again, after the first call had cleared its interrupted
* status and before the second call had examined it).
*
* <p>A thread interruption ignored because a thread was not alive
* at the time of the interrupt will be reflected by this method
* returning false.
*
* @return <code>true</code> if the current thread has been interrupted;
* <code>false</code> otherwise.
* @see #isInterrupted()
* @revised 6.0
*/
public static boolean interrupted() {
return currentThread().isInterrupted(true);
}
通过注释可以看出,interrupted()
方法会清除中断标志位
/**
* Tests whether this thread has been interrupted. The <i>interrupted
* status</i> of the thread is unaffected by this method.
*
* <p>A thread interruption ignored because a thread was not alive
* at the time of the interrupt will be reflected by this method
* returning false.
*
* @return <code>true</code> if this thread has been interrupted;
* <code>false</code> otherwise.
* @see #interrupted()
* @revised 6.0
*/
public boolean isInterrupted() {
return isInterrupted(false);
}
通过注释可以看出,isInterrupted()
方法不会影响中断标志位
下面测试一下:
public class MyThread implements Runnable {
@Override
public void run() {
int i = 0;
try {
while (i < 100) {
// 睡个半秒钟我们再执行
Thread.sleep(500);
System.out.println(i++);
}
} catch (InterruptedException e) {
// 判断该阻塞线程是否还在
System.out.println("该阻塞线程是否还在:" + Thread.currentThread().isAlive());
// 判断该线程的中断标志位状态
System.out.println("该线程的中断标志位:" + Thread.currentThread().isInterrupted());
System.out.println("In Runnable");
e.printStackTrace();
}
}
public static void main(String[] args) {
Thread thread = new Thread(new MyThread());
thread.start();
try {
// 睡眠2秒
Thread.sleep(2000);
} catch (InterruptedException e) {
System.out.println("In main");
e.printStackTrace();
}
thread.interrupt();
}
}
运行结果如下:
0
1
2
3
该阻塞线程是否还在:true
该线程的中断标志位:false
In Runnable
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at testThread.MyThread.run(MyThread.java:12)
at java.lang.Thread.run(Thread.java:748)
执行流程分析:
- main方法所在的线程睡眠了2秒,此时自定义的线程在其他线程中执行
- 自定义线程中,每隔半秒,执行一次操作
- main方法所在线程在睡眠2秒后,设置了自定义线程的中断
- 由于自定义线程中执行了sleep方法,因此该线程会立马中断,停止阻塞,并抛出
InterruptedException
异常。在catch中,可以看出这个线程还是存活状态,但是该线程明明已经被中断,而isInterrupted()方法竟然返回了false,为什么呢?
请往上看interrupt()
方法的源码,我们调用sleep、wait等此类可中断(throw InterruptedException)方法时,一旦方法抛出InterruptedException,当前调用该方法的线程的中断状态就会被jvm自动清除了,就是说我们调用该线程的isInterrupted
方法时是返回false。如果你想保持中断状态,可以再次调用interrupt
方法设置中断状态。这样做的原因是,java的中断并不是真正的中断线程,而只设置标志位(中断位)来通知用户。如果你捕获到中断异常,说明当前线程已经被中断,不需要继续保持中断位。
interrupted
是静态方法,返回的是当前线程的中断状态。例如,如果当前线程被中断(没有抛出中断异常,否则中断状态就会被清除),你调用interrupted方法,第一次会返回true。然后,当前线程的中断状态被方法内部清除了。第二次调用时就会返回false。如果你刚开始一直调用isInterrupted,则会一直返回true,除非中间线程的中断状态被其他操作清除了。