摘要
在Java中,Thread类代表一个线程,本篇博文主要分享Thread相关的知识。
概述
在Java中,和线程密切相关的有Thread、Runable、还有java.util.concurrent并发包下面的相关类。此处我们主要介绍的是前面两个类;
一、Runable接口
任何用于给线程执行的实例,都应该实现Runnable接口。 该实例的类必须定义一个不带参数的方法run。
此接口旨在为那些希望在活动时执行代码的对象提供公共协议。例如,Runnable由类Thread实现。 处于活动状态仅仅意味着线程已经启动,但还没有停止。此外,Runnable提供了类在不子类化Thread的情况下处于活动状态的方法。实现Runnable的类可以在不子类化Thread的情况下运行,方法是实例化一个Thread实例,并将自身作为目标传入。
在大多数情况下,如果您只打算覆盖run()方法,而不覆盖其他Thread方法,那么应该使用Runnable接口。这一点很重要,因为除非程序员有意修改或增强类的基本行为,否则不应该子类化类。
@FunctionalInterface
public interface Runnable {
/**
* 当一个实现接口Runnable的对象被用来创建一个线程时,启动该线程会导致在单独执行的线程中调用对象的run方法。
* run方法的一般约定是它可以采取任何动作。
* @see java.lang.Thread#run()
*/
public abstract void run();
}
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface FunctionalInterface {}
@FunctionalInterface是一个函数接口,里面没有任何方法,仅代表一个标记。
二、Thread类
Thread是Java中一个非常重要的类,在我们程序运行过程中,线程是必不过可少的。
了解该类的相关知识,对我们之后的编程都会非常有用。但是,这个类相对来说还是非常复杂的,总共2000多行代码。
(1)、Thread类定义
public class Thread implements Runnable {
.....
}
从源码可以看到Thread类实现了Runnable接口,类访问权限public。
Thread类的类声明上面的注释有100行(详细阅读源码注释,对代码的理解非常重要),内容如下:
线程是程序中的执行线程。 Java虚拟机允许应用程序同时运行多个执行线程。每个线程都有优先级, 高优先级线程优先于低优先级线程执行。 每个线程可以也可以不被标记为守护进程。当运行在某个线程中的代码创建一个新的thread对象时,新线程的优先级最初被设置为与创建线程的优先级相等,并且当且仅当创建线程是一个守护线程时,它是一个守护线程。
当Java Virtual Machine启动时,通常有一个非守护线程(它通常调用某个指定类的main方法)。 Java虚拟机继续执行线程,直到出现以下情况:
- 类Runtime的exit方法已经被调用,并且安全管理器已经允许进行退出操作。
- 所有非守护线程的线程都已经死亡,无论是通过调用run方法返回,还是通过抛出一个传播到run方法之外的异常。
有两种方法可以创建新的执行线程。 一种是声明一个类是Thread的子类。 这个子类应该覆盖run方法的类Thread。 然后可以分配和启动子类的实例。 例如,一个线程计算比指定值大的质数,可以这样写:
class PrimeThread extends Thread {
long minPrime;
PrimeThread(long minPrime) {
this.minPrime = minPrime;
}
public void run() {
// compute primes larger than minPrime
....
}
}
下面的代码将创建一个线程并启动它运行:
PrimeThread p = new PrimeThread(143);
p.start();
创建线程的另一种方法是声明一个实现Runnable接口的类。 然后该类实现run方法。 然后可以分配类的实例,在创建Thread时作为参数传递,并启动。 另一种风格的相同示例如下:
class PrimeRun implements Runnable {
long minPrime;
PrimeRun(long minPrime) {
this.minPrime = minPrime;
}
public void run() {
// compute primes larger than minPrime
}
}
下面的代码将创建一个线程并启动它运行:
PrimeRun p = new PrimeRun(143);
new Thread(p).start();
每个线程都有一个用于标识的名称。 多个线程可以有相同的名称。 如果在创建线程时没有指定名称,则会为其生成一个新名称。除非另有说明,否则将null参数传递给该类中的构造函数或方法将引发NullPointerException异常。
参考:Runnable、Runtime#exit(int) 、Thread#run() 、Thread#stop()
(2)、本地方法注册
就是将Java定义的本地方法和系统或者C/C++编写的类库中的本地方法连接起来。此处,知识不清楚的小伙伴可参考<<JDK 源码分析 - Object 类>>这篇博文。
//注册本地方法
private static native void registerNatives();
static {
registerNatives();
}
//返回对当前执行的线程对象的引用。
public static native Thread currentThread();
/**
* 提示调度器当前线程愿意放弃其当前对处理器的使用。 调度器可以随意忽略这个提示。
* Yield是一种启发式尝试,用于改善相关程序直接的线程,否则会过度使用CPU. 它的使用应该与详细的概要分析和基准测试相结合,
*以确保它实际上具有预期的效果。
*
* 很少适合使用这种方法。 它可能用于调试或测试目的,在这些目的中,它可能有助于重现由于竞争条件造成的bug。
* 在设计并发控制结构(比如{@link java.util.concurrent.locks}时,它可能也很有用。
*/
public static native void yield();
/**
* 使当前正在执行的线程休眠(暂时停止执行)指定的毫秒数,取决于系统计时器和调度器的精度和准确性。 线程不会失去任何锁(监视器)的所有权。
* millis 睡眠的时间长度,以毫秒为单位
* throws IllegalArgumentException 如果{@code millis}的值是负的
* throws InterruptedException 如果有线程中断了当前线程。 当抛出此异常时,清除当前线程的中断状态。
*/
public static native void sleep(long millis) throws InterruptedException;
private native void start0();
//测试某个线程是否被中断。 中断状态是否重置取决于传递的ClearInterrupted的值。
private native boolean isInterrupted(boolean ClearInterrupted);
//测试此线程是否活动。 如果线程已经启动并且还没有死亡,那么线程就是活的。
public final native boolean isAlive();
/**
* 当且仅当当前线程持有指定对象上的监视器锁时,返回 true。
*
* 这个方法被设计用来允许程序断言当前线程已经持有一个指定的锁: assert Thread.holdsLock(obj);
*
* obj- 要测试锁所有权的对象
* 如果obj为null,throws NullPointerException
* 如果当前线程持有指定对象上的监视器锁。返回true
*/
public static native boolean holdsLock(Object obj);
private native static StackTraceElement[][] dumpThreads(Thread[] threads);
private native static Thread[] getThreads();
/* 一些私有辅助方法 */
private native void setPriority0(int newPriority);//设置线程优先级
private native void stop0(Object o);//停止线程
private native void suspend0();//挂起线程
private native void resume0();//恢复挂起的线程
private native void interrupt0();//设置中断标记
private native void setNativeName(String name);//设置名字
(3)、属性
private volatile String name;//线程的名字
private int priority;//线程的优先级
private Thread threadQ;
private long eetop;
private boolean single_step;//是否单步执行此线程
private boolean daemon = false;//是否是守护线程,默认不是守护线程
private boolean stillborn = false;//JVM state
private Runnable target;//需要完成的任务
private ThreadGroup group;//线程组
private ClassLoader contextClassLoader;//此线程的上下文类加载器
private AccessControlContext inheritedAccessControlContext;//此线程的继承的访问控制上下文
private static int threadInitNumber;//用于自动编号匿名线程。
private static synchronized int nextThreadNum() {
return threadInitNumber++;
}
ThreadLocal.ThreadLocalMap threadLocals = null;//属于当前线程的线程本地变量,这个map由ThreadLocal类维护
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;//当前线程可继承的线程本地变量,这个map由InheritableThreadLocal类维护
private long stackSize;//此线程请求的堆栈大小,如果创建者没有指定堆栈大小,则为0。 VM可以对这个数字做任何它想做的事情; 部分虚拟机会忽略。
private long nativeParkEventPointer;//本机线程终止后任然存在,jvm私有状态
private long tid;//线程ID
private static long threadSeqNumber;//用于生成线程ID
private volatile int threadStatus = 0;//Java线程状态,初始化以指示线程“尚未启动”
private static synchronized long nextThreadID() {
return ++threadSeqNumber;
}
//提供给当前调用java.util.concurrent.locks. locksupport.park的参数。 由(private) java.util.concurrent.locks.LockSupport.setBlocker设置,
//通过java.util.concurrent.locks.LockSupport.getBlocker访问
volatile Object parkBlocker;
//在可中断的I / O操作中阻塞该线程的对象(如果有)。设置此线程的中断状态后,应调用阻塞程序的中断方法
private volatile Interruptible blocker;
private final Object blockerLock = new Object();
/* Set the blocker field; invoked via sun.misc.SharedSecrets from java.nio code
*/
void blockedOn(Interruptible b) {
synchronized (blockerLock) {
blocker = b;
}
}
public final static int MIN_PRIORITY = 1;//线程可以拥有的最低优先级。
public final static int NORM_PRIORITY = 5;//分配给线程的默认优先级。
public final static int MAX_PRIORITY = 10;//线程可以拥有的最大优先级。
(4)、构造方法
public Thread() {
init(null, null, "Thread-" + nextThreadNum(), 0);
}
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
/**
* Creates a new Thread that inherits the given AccessControlContext.
* This is not a public constructor.
*/
Thread(Runnable target, AccessControlContext acc) {
init(null, target, "Thread-" + nextThreadNum(), 0, acc, false);
}
public Thread(ThreadGroup group, Runnable target) {
init(group, target, "Thread-" + nextThreadNum(), 0);
}
public Thread(String name) {
init(null, null, name, 0);
}
public Thread(ThreadGroup group, String name) {
init(group, null, name, 0);
}
public Thread(Runnable target, String name) {
init(null, target, name, 0);
}
public Thread(ThreadGroup group, Runnable target, String name) {
init(group, target, name, 0);
}
public Thread(ThreadGroup group, Runnable target, String name,
long stackSize) {
init(group, target, name, stackSize);
}
可以看到上面的构造方法都调用了一个init()方法,我们来看看init这个方法到底做了哪些事情;
(5)、init()私有方法
private void init(ThreadGroup g, Runnable target, String name,
long stackSize) {
init(g, target, name, stackSize, null, true);
}
/**
* 初始化一个线程。
*
* @param g the Thread group
* @param target the object whose run() method gets called
* @param name the name of the new Thread
* @param stackSize 新线程所需的堆栈大小,或表示忽略此参数的0。
* @param acc 继承的AccessControlContext , 或者如果为null,则AccessController.getContext()
* @param inheritThreadLocals 如果{@code为真},则从构造线程继承可继承的线程局部变量的初始值
*/
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 parent = currentThread();
SecurityManager security = System.getSecurityManager();
if (g == null) {
/* 确定它是否是一个applet */
/* 如果有SecurityManager,询问SecurityManager该怎么办*/
if (security != null) {
g = security.getThreadGroup();
}
/* 如果SecurityManager没有很强的意见,可以使用父线程组。*/
if (g == null) {
g = parent.getThreadGroup();
}
}
//检查访问,不管线程组是否显式传入
g.checkAccess();
//我们是否拥有所需的权限?
if (security != null) {
if (isCCLOverridden(getClass())) {
security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
}
}
//增加线程组中未启动线程的计数
g.addUnstarted();
this.group = g;
//根据父线程是否为守护线程,初始化子线程是否为守护线程
this.daemon = parent.isDaemon();
//根据父线程的优先级,初始化子线程的优先级
this.priority = parent.getPriority();
//根据父线程的上下文类加载器初始化子线程的上下文类加载器
if (security == null || isCCLOverridden(parent.getClass()))
this.contextClassLoader = parent.getContextClassLoader();
else
this.contextClassLoader = parent.contextClassLoader;
this.inheritedAccessControlContext =
acc != null ? acc : AccessController.getContext();
this.target = target;
setPriority(priority);
//继承父线程的可继承线程本地变量
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
/* 保存指定的堆栈大小,以防JVM关心 */
this.stackSize = stackSize;
/* Set thread ID */
tid = nextThreadID();
}
从上面的源码可以了解到,线程创建新线程时,默认会继承父线程的可继承线程本地变量。
(6)、start启动方法
/**
* 使线程开始执行; Java虚拟机调用该线程的run方法。
* 结果是两个线程并发运行:当前线程(调用start方法返回)和另一个线程(执行它的run方法)。
* 一个线程启动一次以上是不合法的。尤其是线程一旦完成执行就不能重新启动。
*
* @exception IllegalThreadStateException 如果线程已经启动。
*/
public synchronized void start() {
/**
* 此方法不会被主方法线程或由VM创建/设置的“系统”组线程调用。 将来添加到此方法的任何新功能可能也必须添加到VM中。
* 状态值为零对应状态“NEW”。
*/
if (threadStatus != 0)
throw new IllegalThreadStateException();
//通知组这个线程即将启动,这样就可以将它添加到组的线程列表中,并且可以减少组的未启动计数。
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 */
}
}
}
(7)、run方法
/**
* 如果这个线程通过使用单独的Runnable 对象构造,那么Runnable对象的run方法将被调用
* 否则, 这个方法不做任何事情,直接返回。
* Thread子类应该重写该方法.
*/
@Override
public void run() {
if (target != null) {
target.run();
}
}
(8)、sleep方法
/**
* 使当前正在执行的线程休眠(暂时停止执行)指定的毫秒数,取决于系统计时器和调度器的精度和准确性。 线程不会失去任何锁(监视器)的所有权。
* millis 睡眠的时间长度,以毫秒为单位
* throws IllegalArgumentException 如果{@code millis}的值是负的
* throws InterruptedException 如果有线程中断了当前线程。 当抛出此异常时,清除当前线程的中断状态。
*/
public static native void sleep(long millis) throws InterruptedException;
/**
* 使当前正在执行的线程休眠(暂时停止执行),休眠时间为指定的毫秒数加上指定的纳秒数,具体时间取决于系统计时器和调度器的精度。
* 线程不会失去任何监视器锁的所有权。
* @param millis 睡眠的时间长度,以毫秒为单位
* @param nanos {@code 0-999999} 额外的纳秒睡眠时间
* @throws IllegalArgumentException 如果{@code millis}的值为负值,或者{@code nanos}的值不在{@code 0-999999}的范围内
* @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");
}
//如果500000<nanos <999999|| (nanos != 0 && millis == 0),则睡眠的毫秒数+1。
if (nanos >= 500000 || (nanos != 0 && millis == 0)) {
millis++;
}
sleep(millis);
}
(9)、interrupt方法
/**
* 中断线程
*
* 除非当前线程总是被允许中断自己,否则会调用该线程的{@link #checkAccess() checkAccess}方法,这可能会引发{@link SecurityException}异常。
*
* 如果这个线程在调用{@link Object#wait() wait()}, {@link Object#wait(long) wait(long)},或{@link Object}类的{@link #wait(long, int) wait(long, int)}方法, * 或该类的{@link #join()}, {@link #join(long, int)}, {@link #sleep(long)},或{@link #sleep(long, int)}方法时被阻塞,那么它的中断状态将被清除,
* 并接收一个{@link InterruptedException}。
*
* 如果这个线程在I/O操作中被{@link java.nio.channels.InterruptibleChannel InterruptibleChannel}阻塞,那么通道将被关闭,线程的中断状态将被设置,
* 并且线程将收到{@link java.nio.channels.ClosedByInterruptException}。
*
* 如果这个线程阻塞在{@link java.nio.channels.. Selector} 那么线程的中断状态将被设置,并且它将从选择操作中立即返回,可能带有一个非零的值,
* 就像selector的{@link java.nio.channels. Selector#wakeup wakeup}方法被调用
*
* 如果前面的条件都不成立,那么这个线程的中断状态将被设置。
* 中断一个没有活动的线程不会产生任何影响。
*
* @throws SecurityException 如果当前线程不能修改此线程
*/
public void interrupt() {
if (this != Thread.currentThread())
checkAccess();
synchronized (blockerLock) {
Interruptible b = blocker;
if (b != null) {
interrupt0(); // Just to set the interrupt flag
b.interrupt(this);
return;
}
}
interrupt0();
}
(10)、exit方法
/**
* 系统会调用这个方法,让线程在实际退出之前有机会进行清理。
*/
private void exit() {
if (group != null) {
group.threadTerminated(this);
group = null;
}
/*将线程的引用字段置空: see bug 4006245 */
target = null;
/* Speed the release of some of these resources */
threadLocals = null;
inheritableThreadLocals = null;
inheritedAccessControlContext = null;
blocker = null;
uncaughtExceptionHandler = null;
}
(11)、setPriority方法
/**
* 更改此线程的优先级。
* 首先不带参数调用这个线程的checkAccess方法。 这可能导致抛出一个SecurityException
*
* 否则,该线程的优先级被设置为指定的newPriority和该线程的线程组允许的最大优先级的较小值。
*
* @param newPriority 将线程的优先级设置为newPriority
* @exception IllegalArgumentException 如果优先级不在MIN_PRIORITY到MAX_PRIORITY范围内。
* @exception SecurityException 如果当前线程不能修改此线程。
*/
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);
}
}
(12)、join方法
该方法底层使用Object对象的wait(long)方法来实现,如果相关知识不太熟悉,可参考<<JDK 源码分析 - Object 类>>
/**
* 等待最多{@code millis}毫秒这个线程死亡。 {@code 0}的超时意味着永远等待。
*
* 这个实现使用了根据条件{@code this.isAlive}循环调用{@code This.wait}。 当线程终止调用{@code this.notifyAll}方法。
* 建议应用程序不要对{@code Thread}实例使用{@code wait}、{@code notify}或{@code notifyAll}。
*
* @param millis 等待的时间,以毫秒为单位
*
* @throws IllegalArgumentException 如果{@code millis}的值是负的
*
* @throws InterruptedException 如果有线程中断了当前线程。 当抛出此异常时,当前线程的中断状态被清除。
*/
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 synchronized void join(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++;
}
join(millis);
}
//调用wait (0) 方法一直等待,直到获得锁或者被中断
public final void join() throws InterruptedException {
join(0);
}
(13)、线程状态内部类State
/**
* 一个线程的状态。 线程可以处于下列状态之一:
* NEW:尚未启动的线程处于这种状态。
* RUNNABLE:在Java虚拟机中执行的线程就是这种状态。
* BLOCKED:等待监视器锁而被阻塞的线程就处于这种状态。
* WAITING:无限等待另一个线程执行特定操作的线程就处于这种状态。
* TIMED_WAITING:在指定的等待时间内等待另一个线程执行操作的线程就是这种状态。
* TERMINATED:已退出的线程处于这种状态。
* 线程在给定的时间点只能处于一种状态。 这些状态是不反映任何操作系统线程状态的虚拟机状态。
*/
public enum State {
/**
*尚未启动的线程的线程状态。
*/
NEW,
/**
* 可运行线程的线程状态。 处于可运行状态的线程正在Java虚拟机中执行,但它可能正在等待来自操作系统(如处理器)的其他资源。
*/
RUNNABLE,
/**
* 等待监视器锁的线程阻塞的线程状态。 一个处于阻塞状态的线程正在等待一个监视器锁进入一个同步的块/方法,
* 或者在调用{@link Object#wait() Object.wait}之后重新进入一个同步的块/方法。
*/
BLOCKED,
/**
* 等待线程的线程状态。
* 由于调用以下方法之一,线程处于等待状态:
* <ul>
* <li>{@link Object#wait() Object.wait} with no timeout</li>
* <li>{@link #join() Thread.join} with no timeout</li>
* <li>{@link LockSupport#park() LockSupport.park}</li>
* </ul>
*
* 处于等待状态的线程正在等待另一个线程执行特定的操作。
*/
WAITING,
/**
* 具有指定等待时间的等待线程的线程状态。 线程处于定时等待状态,因为调用了下列方法之一,并指定了正的等待时间:
* <ul>
* <li>{@link #sleep Thread.sleep}</li>
* <li>{@link Object#wait(long) Object.wait} with timeout</li>
* <li>{@link #join(long) Thread.join} with timeout</li>
* <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
* <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
* </ul>
*/
TIMED_WAITING,
/**
* 终止线程的线程状态。
* 线程已完成执行。
*/
TERMINATED;
}
(14)、Java线程的状态关系
在操作系统层面,线程有五种状态:新建、就绪、运行、阻塞、死亡 在Java中,线程定义了六种状态:新建、就绪(包括就绪和运行两种状态)、等待、限时等待、阻塞、死亡