共计 13822 个字符,预计需要花费 35 分钟才能阅读完成。
本文出处 Thread 线程知识点解说 转载请阐明出处
外部属性
// 线程名,如果创立时没有指定则应用 Thread- + 创立序列号
private volatile String name;
// 线程优先级 Java 只是给操作系统一个优先级的参考值,线程最终在操作系统的优先级是多少还是由操作系统决定。private int priority;
// 守护线程
private boolean daemon = false;
// 为 JVM 保留字段
private boolean stillborn = false;
private long eetop;
/* What will be run. */
private Runnable target;
// 线程组,每一个线程必然存于一个线程组中,线程不能独立于线程组外
private ThreadGroup group;
// 类加载器,当线程须要加载类时,会应用外部类加器
private ClassLoader contextClassLoader;
/* For autonumbering anonymous threads. */
private static int threadInitNumber;
private static synchronized int nextThreadNum() {return threadInitNumber++;}
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
/*
* InheritableThreadLocal values pertaining to this thread. This map is
* maintained by the InheritableThreadLocal class.
*/
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
/*
* The requested stack size for this thread, or 0 if the creator did
* not specify a stack size. It is up to the VM to do whatever it
* likes with this number; some VMs will ignore it.
*/
private final long stackSize;
/*
* JVM-private state that persists after native thread termination.
*/
private long nativeParkEventPointer;
/*
* Thread ID
*/
private final long tid;
/* For generating thread ID */
private static long threadSeqNumber;
// 这个线程号是整个 Thread 类共享的
private static synchronized long nextThreadID() {return ++threadSeqNumber;}
/*
* 线程状态
*/
private volatile int threadStatus;
构造函数
public Thread() {this(null, null, "Thread-" + nextThreadNum(), 0);
}
public Thread(ThreadGroup group, Runnable target, String name,
long stackSize) {this(group, target, name, stackSize, null, true);
}
private Thread(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(); // 从创立 Thread 的线程中获取到父线程
SecurityManager security = System.getSecurityManager();
if (g == null) {
/* Determine if it's an applet or not */
/* If there is a security manager, ask the security manager
what to do. */
if (security != null) {g = security.getThreadGroup();
}
/* If the security manager doesn't have a strong opinion
on the matter, use the parent thread group. */
if (g == null) { // 没有设置线程组则应用以后线程的线程组
g = parent.getThreadGroup();}
}
/* checkAccess regardless of whether or not threadgroup is
explicitly passed in. */
g.checkAccess();
/*
* Do we have the required permissions?
*/
if (security != null) {if (isCCLOverridden(getClass())) {
security.checkPermission(SecurityConstants.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);
/* Stash the specified stack size in case the VM cares */
this.stackSize = stackSize;
/* Set thread ID */
this.tid = nextThreadID();}
构造方法其实都是对 Thread 外部属性进行初始化,比方线程名、优先级、类加器、线程 Id。如果没有设置这些属性全副继承自以后的。让我比拟奇怪是十分重要的 threadStatus
没有赋值,而是应用了默认值,我猜测这个变量全程都是由 c ++ 来变更的,所以不必要应用 Java 进行赋值。
曾经初始化的线程对象能够通过 set 办法去批改守护线程、线程名、优先级。
线程状态
public enum State {
/**
* Thread state for a thread which has not yet started.
*/
NEW,
/**
* Thread state for a runnable thread. A thread in the runnable
* state is executing in the Java virtual machine but it may
* be waiting for other resources from the operating system
* such as processor.
*/
RUNNABLE,
/**
* Thread state for a thread blocked waiting for a monitor lock.
* A thread in the blocked state is waiting for a monitor lock
* to enter a synchronized block/method or
* reenter a synchronized block/method after calling
* {@link Object#wait() Object.wait}.
*/
BLOCKED,
/**
* Thread state for a waiting thread.
* A thread is in the waiting state due to calling one of the
* following methods:
* <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>
*
* <p>A thread in the waiting state is waiting for another thread to
* perform a particular action.
*
* For example, a thread that has called {@code Object.wait()}
* on an object is waiting for another thread to call
* {@code Object.notify()} or {@code Object.notifyAll()} on
* that object. A thread that has called {@code Thread.join()}
* is waiting for a specified thread to terminate.
*/
WAITING,
/**
* Thread state for a waiting thread with a specified waiting time.
* A thread is in the timed waiting state due to calling one of
* the following methods with a specified positive waiting time:
* <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,
/**
* Thread state for a terminated thread.
* The thread has completed execution.
*/
TERMINATED;
}
线程状态常常被问于面试中,几个状态和代表涵义大家都有记一记。
状态 | 形容 | 场景 |
---|---|---|
NEW | Thread 线程刚刚被创立,创立状态 | new Thread |
RUNNABLE | 运行状态,线程正在运行中 | Thread.start |
BLOCKED | 梗塞状态 | synchronized 竞争失败 |
WAITING | 期待,这种状态要么有限期待上来,要么被唤醒 | Object.wait、Lock |
TIMED_WAITING | 期待超时,在期待时设置了工夫,到时会主动唤醒 | Thread.sleep、LockSupport.parkNanos |
TERMINATED | 死亡状态 | 线程曾经执行完工作 |
从下图能够发现从创立 -> 运行 -> 死亡 这个过程是不可逆的。
线程运行和进行
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) // 状态必须是创立状态 NEW,避免一个对象屡次调用 start 办法
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); // 退出线程组容器中,未开始线程数 -1
boolean started = false;
try {start0();
started = true;
} finally {
try {
// 进入到这里,则 start0 创立一个线程失败了,要从线程组中删除它,未开始线程再加回来
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();
start
办法比较简单的,先判断状态是否正确,在创立之前退出到线程组外面,失败了再移除。start0 办法应该就是调用系统资源真正去创立一个线程了,而且线程状态也是由这个办法批改的。
run
办法只有应用 Thread 来创立线程,并且应用 Runnable 传参才会执行这里 run 办法,继承形式应该是间接调用子类 run 办法了。
public void run() {if (target != null) { // 有传入 Runnable 对象,则调用该对象实现 run 办法
target.run();}
}
stop
办法尽管在 Java2 曾经被官网停用了,很值得去理解下的。
@Deprecated(since="1.2")
public final void stop() {SecurityManager security = System.getSecurityManager();
if (security != null) {checkAccess();
if (this != Thread.currentThread()) {security.checkPermission(SecurityConstants.STOP_THREAD_PERMISSION);
}
}
// A zero status value corresponds to "NEW", it can't change to
// not-NEW because we hold the lock.
if (threadStatus != 0) { // 不是 NEW,线程曾经运行了,如果被挂起了,须要对它进行唤醒
resume(); // Wake up thread if it was suspended; no-op otherwise}
// The VM can handle all thread states
stop0(new ThreadDeath()); // 进行线程,并且抛出一个异样给 JVM
}
private native void stop0(Object o);
看完这个办法,也没有看进去 stop()
能干什么,我也不是很分明这个 stop 能干什么,我将写几个例子验证性能。
创立几个线程去执行下工作,执行一会后,对所有线程调用 stop 办法,是否会退出工作。
public class ThreadStopTest {public static void main(String[] args) {ThreadStopTest t = new ThreadStopTest();
Runnable r = () -> {
int i = 0;
while (i < 1000){t.spinMills(500);
System.out.println(Thread.currentThread().getName() + ":" + i);
i++;
}
};
Thread t1 = new Thread(r);
Thread t2 = new Thread(r);
Thread t3 = new Thread(r);
t1.start();
t2.start();
t3.start();
t.spinMills(2000);
t1.stop();
t2.stop();
t3.stop();}
public void spinMills(long millisecond){long start = System.currentTimeMillis();
while (System.currentTimeMillis() - start < millisecond){// 自旋,模仿执行工作}
}
}
执行后果
Thread-1 : 0
Thread-0 : 0
Thread-2 : 0
Thread-1 : 1
Thread-0 : 1
Thread-2 : 1
Thread-2 : 2
Thread-1 : 2
Thread-0 : 2
调用完 stop 办法,线程立即退出工作,连一个异样都没有抛出的,真的是十分罗唆。如果有人不下心应用 stop 办法,呈现问题都十分难排除,所以 Java 官网早早就停止使用它了,具体看官网阐明
如果想优雅进行一个正在运行的线程,官网倡议应用interrupted()
。线程中断就是指标线程发送一个中断信号,可能收到中断信号线程本人实现退出逻辑。简略点说就是线程 A 在干活,忽然有集体对它做了一个动作,线程 A 在晓得这个动作涵义,它会晓得本人要停下来。说白这就一个动作,如果线程逻辑没有解决这个动作代码,线程并不会退出的。看下 Thread 类外面有那些办法。
办法 | 备注 |
---|---|
interrupt() | 中断指标线程,给指标线程发一个中断信号,线程被打上中断标记 |
isInterrupted() | 判断指标线程是否被中断,不会革除中断标记 |
interrupted | 判断指标线程是否被中断,会革除中断标记 |
实现一个简略例子
public static void main(String[] args) throws InterruptedException {Runnable r = () -> {while (!Thread.currentThread().isInterrupted()){
//do some
System.out.println(System.currentTimeMillis());
}
System.out.println("线程筹备退出啦");
Thread.interrupted();};
Thread t = new Thread(r);
t.start();
Thread.sleep(1000);
t.interrupt();}
下面代码外围是中断状态,如果中断被革除了,那程序不会跳出 while 循环的,上面改一下,增加一个 sleep 办法
public static void main(String[] args) throws InterruptedException {Runnable r = () -> {while (!Thread.currentThread().isInterrupted()){
//do some
try {Thread.sleep(400);
} catch (InterruptedException e) {e.printStackTrace();
}
System.out.println(System.currentTimeMillis());
}
System.out.println("线程筹备退出啦");
Thread.interrupted();};
Thread t = new Thread(r);
t.start();
Thread.sleep(1000);
t.interrupt();}
执行后果:发送中断后,Thread.sleep 间接抛出一个异样,并不会跳出循环。
因为 sleep 会响应中断,抛出一个中断异样,再革除线程中断状态。再回到 while 判断时,中断状态曾经被革除了,持续循环上来。sleep()
是一个动态 native 办法,使以后执行的线程休眠指定工夫,然而休眠的线程不会放弃监控器的锁(synchronized), 当任何线程要中断以后线程时,会抛出 InterruptedException
异样,并且清理以后线程的中断状态。所以在办法调用上就会抛出这个异样,让调用者去解决中断异样。
join 和 yield 办法
join()
就是一个期待办法,期待以后线程工作执行后,再次唤醒被调用的线程,经常用来管制多线程工作执行程序。
/**
* 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.
*/
public final synchronized void join(final long millis)
throws InterruptedException {if (millis > 0) {if (isAlive()) { // 这里获取线程状态,只是不是开始和死亡就算 alive 了
final long startTime = System.nanoTime();
long delay = millis;
do {wait(delay);
} while (isAlive() && (delay = millis -
TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime)) > 0); // 在指定工夫内沉睡
}
} else if (millis == 0) {while (isAlive()) {wait(0);
}
} else {throw new IllegalArgumentException("timeout value is negative");
}
}
想理解办法次要看办法正文就行,在指定工夫内期待被调用者的线程死亡,如果没有死亡工夫到了会自行唤醒,如果工夫为 0 则永远期待上来,直到执行线程执行完工作。唤醒是由 notifyAll 执行的,然而没看见在哪里执行这个办法。查了一下材料晓得每个线程执行实现后都会调用 exit()办法,在 exit 会调用 notifyAll。yield()
: 单词翻译过去就是退让的意思。次要作用当线程获取到执行权时,调用这个办法会被动让出执行器,它跟下面 wait、sleep 不同,线程状态是没有扭转的,此时任然是 RUN。比方一个线程获取锁失败了,这时线程什么不能干,获取锁自身是很快,此时将线程挂起了,有点得失相当,不如此时让出 CPU 执行器,让其余线程去执行。既不会节约 CPU 宝贵时间,也不须要太消耗性能。这个办法常常用于 java.util.concurrent.locks
包下同步办法,看过并发工具类的同学应该都意识它。
线程间合作
wait 办法让以后线程进入期待状态 (WAITING),并且开释监控锁,只有当其余线程调用 notify 或者 notifyAll 才会唤醒线程。
notify 唤醒一个在期待状态的线程,从新进入 RUNNABLE 状态。
notifyAll 唤醒所有正在期待状态的线程,从新进入 RUNNABLE 状态。
下面三个办法都必须在监控锁(synchronized)下应用,不然会抛出 IllegalMonitorStateException。
wait、notify 两个办法联合就能够实现线程之间合作。比方最经典的生产者 - 消费者模型:当上游消费者发送发送信息太多,导致队列挤压曾经满了,这时消费者这边能够应用 wait, 让生产者停下里,当消费者曾经开始生产了,此时队列曾经被生产走一个信息了,有空间了,消费者能够调用 notify,让上游生产者持续运作起来。当队列外面信息曾经被生产完时,消费者会调用 wait,让线程进入期待中,当上游线程有信息发送到队列时,此时队列中信息就不是全空的了,就能够调用 wait 唤醒一个期待消费者。这样就能够造成线程之间互相通信的成果了。
简略实现消费者 - 生产者模型
public void push(T t){synchronized (lock){
size++;
if (size == QUEUE_CAPACTIY) {
try {lock.wait();
} catch (InterruptedException e) {e.printStackTrace();
}
}
lock.notify();
// 入队列中
}
}
public T poll(){synchronized (lock){
size--;
if (size == 0) {
try {lock.wait();
} catch (InterruptedException e) {e.printStackTrace();
}
}
lock.notify();
return T;
}
}
Callable 和 Thread 关系
咱们晓得了所有的线程其实都是 Thread.start 去创立的,重写 run 办法达到异样执行工作,然而 Callable 这个接口是否也是应用 Thread 或者 Runnable 接口,次要看 FutureTask
就晓得如何实现了。
看下 run 办法
public void run() {
// 如果线程曾经被创立了,则不须要再次执行工作了
if (state != NEW ||
!RUNNER.compareAndSet(this, null, Thread.currentThread()))
return;
try {
Callable<V> c = callable; //callable 办法实现类
if (c != null && state == NEW) { // 刚刚初始化的状态
V result;
boolean ran;
try {result = c.call(); // 执行工作
ran = true;
} catch (Throwable ex) {
result = null;
ran = false;
setException(ex); // 保留异样, 将期待队列的线程全副唤醒过去
}
if (ran)
set(result); // 保留执行后果,将期待队列的线程全副唤醒过去
}
} finally {
// runner must be non-null until state is settled to
// prevent concurrent calls to run()
runner = null;
// state must be re-read after nulling runner to prevent
// leaked interrupts
int s = state;
if (s >= INTERRUPTING)
handlePossibleCancellationInterrupt(s);
}
}
能够看出 Callable 依然是应用 Thread 来创立线程的,外部通过保护 state 来判断工作状态,在 run 办法中执行 call 办法,保留异样和执行后果。
看下 get() 如何获取执行后果的吧
public V get() throws InterruptedException, ExecutionException {
int s = state;
if (s <= COMPLETING) // 还在执行中
s = awaitDone(false, 0L); // 期待工作执行实现或者中断,会梗塞调用线程
return report(s);
}
/**
* Awaits completion or aborts on interrupt or timeout.
*
* @param timed true if use timed waits
* @param nanos time to wait, if timed
* @return state upon completion or at timeout
*/
private int awaitDone(boolean timed, long nanos)
throws InterruptedException {
// The code below is very delicate, to achieve these goals:
// - call nanoTime exactly once for each call to park
// - if nanos <= 0L, return promptly without allocation or nanoTime
// - if nanos == Long.MIN_VALUE, don't underflow
// - if nanos == Long.MAX_VALUE, and nanoTime is non-monotonic
// and we suffer a spurious wakeup, we will do no worse than
// to park-spin for a while
long startTime = 0L; // Special value 0L means not yet parked
WaitNode q = null;
boolean queued = false;
for (;;) {
int s = state;
if (s > COMPLETING) { // 如果状态曾经有执行中变成其余,间接将状态返回
if (q != null)
q.thread = null;
return s;
}
else if (s == COMPLETING) // 正在执行中,让出 CPU 执行权,而不是变换线程状态
// We may have already promised (via isDone) that we are done
// so never return empty-handed or throw InterruptedException
Thread.yield();
else if (Thread.interrupted()) { // 解决线程中断,退出自旋
removeWaiter(q); // 删除队列中的线程
throw new InterruptedException();}
else if (q == null) {if (timed && nanos <= 0L)
return s;
q = new WaitNode();}
else if (!queued) // 将期待后果线程放入一个队列中,其实这个队列就是来解决期待后果线程的中断的
queued = WAITERS.weakCompareAndSet(this, q.next = waiters, q);
else if (timed) {
final long parkNanos;
if (startTime == 0L) { // first time
startTime = System.nanoTime();
if (startTime == 0L)
startTime = 1L;
parkNanos = nanos;
} else {long elapsed = System.nanoTime() - startTime;
if (elapsed >= nanos) {removeWaiter(q);
return state;
}
parkNanos = nanos - elapsed;
}
// nanoTime may be slow; recheck before parking
if (state < COMPLETING) // 工作没有启动,挂起期待线程
LockSupport.parkNanos(this, parkNanos);
}
else
LockSupport.park(this); // 工作没有开始,挂起调用者,工作实现后会将它唤醒的
}
}
当初根本就明了,应用 run 调用 call 办法,将执行后果保存起来,而后 get 办法这边应用自旋办法期待执行后果,并且应用队列将期待的线程保存起来,来解决线程的唤醒、中断。
总结
这里简略说了 Thread 的构造方法,属性设置,比拟重要就是线程几个状态,状态流转、线程启动进行,中断解决, 几个罕用办法的介绍。简略说了下 FutureTask
实现原理, 联合下面提到的知识点,下面提到这些常识都是挺重要的,你能够看到大部分 Java 并发类都用到这些常识来开发的,频繁呈现在面试中也是能够了解的。