线程天天用,看过其完整源码的有多少?
线程目录
- 构造
- 启动
- 状态
- 中断
构造
- 创建一个线程有几种方式?
Threads are represented by the Thread class. The only way for a user to create a thread is to create an object of this class; each thread is associated with such an object. A thread will start when the start() method is invoked on the corresponding Thread object.
Java语言规范
中告诉我们只有一种方式:
- 创建一个
Thread
类实例 - 调用
Thread#start()
方法
-
Thread
类有多少种构造函数?
八种,实际传入的参数最多只有四个,最终都是调用此方法:
private void init(ThreadGroup g, Runnable target, String name,
long stackSize) {
init(g, target, name, stackSize, null, true);
}
-
group
所属线程组 -
target
目标线程 -
name
线程名 -
stackSize
栈深 (线程私有内存空间, 传0忽略此参数)
继续看下这个方法的最终形态
/**
* Initializes a Thread.
*
* @param g the Thread group
* @param target the object whose run() method gets called
* @param name the name of the new Thread
* @param stackSize the desired stack size for the new thread, or
* zero to indicate that this parameter is to be ignored.
* @param acc the AccessControlContext to inherit, or
* AccessController.getContext() if null
* @param inheritThreadLocals if {@code true}, inherit initial values for
* inheritable thread-locals from the constructing thread
*/
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) {
/* 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 doesn't have a strong opinion of 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(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 */
tid = nextThreadID();
}
不展开了, 看看注释吧~
启动
现在一个线程创建好了,怎么启动呢?- 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 */
}
}
}
- 首先这是一个同步方法, 表示可能有多个线程想要启动此线程, 但只有一个会成功
在源码中只要用到了同步块或
CAS
的, 都表示这块代码有可能被多个线程同时执行
- 第一段就是校验了, 如果已经被其他线程捷足先登启动好了, 那么再启动就报个错
IllegalThreadStateException
- 添加到线程组 (线程组这块下次说)
- 调用
native
方法start0()
, 如果启动失败线程组需要进一步处理
让我们通过此方法注释中的描述来了解一下到底这个start0()
方法做了啥:
使这个线程开始执行,JVM会执行当前线程的
run()
方法
执行start()
方法的结果就是:两个线程并发执行:(1)当前线程,也就是执行start()
方法的这个线程;(2)另一个线程,执行run()
方法的线程.
多次启动一个线程永远是非法操作, 尤其是一个线程完成执行就不会被重新启动. (my English is poor....
)
再看下Thread
的run
方法做了啥
@Override
public void run() {
if (target != null) {
target.run();
}
}
单纯地调用传入的target
的run()
方法....
问两个问题:
-
Thread
类的run()
方法跟普通方法有啥区别吗? - 为啥启动一个新的线程永远是需要调用
start()
方法?
其实原因就在于这个native
的start0()
, 上面注释中明确说了, 此方法调用后的结果是两个线程, 会另起一个线程去执行run()
方法.
如果你直接调用run()
启动会怎么样呢? 很简单, 就是顺序执行呗, 当前线程执行完了run()
方法再往下执行, 线程池中就是这种方式来控制线程数量.(参见 死磕源码系列 - ThreadPoolExecutor
)
状态
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 <tt>Object.wait()</tt>
* on an object is waiting for another thread to call
* <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
* that object. A thread that has called <tt>Thread.join()</tt>
* 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 |
线程创建好但还没有启动 |
RUNNABLE |
运行中或者等待调度运行,就是传说中的等待时间片,执行了start() 方法之后的状态 |
BLOCKED |
等待进入一个Monitor锁 (注释中最后半句 调用wait() 方法后重新进入同步块, 这里有点问题,后面说) |
WAITING |
执行Object#wait() /Thread#join() /LockSupport#park() 方法后的状态 |
TIMED_WAITING |
执行Thread#sleep /Object#wait(long) /Thread#join(long) /LockSupport#parkNanos /LockSupport#parkUntil 方法后的状态 |
TERMINATED |
线程完成执行后的状态 |
可能经常弄混淆的就是BLOCKED
和两个WAITING
状态,下面看一个例子(注意锁
住的是什么):
1、如果类锁被获取,那么其他类锁的代码将同步阻塞
2、如果对象锁被获取,那么同一个对象锁的代码将同步阻塞
public class ThreadSleepWait {
public static void main(String[] args){
Stream.of("T1", "T2").forEach(name -> new Thread(ThreadSleepWait::show, name).start());
}
private static void show() {
synchronized (ThreadSleepWait.class) {
System.out.println("[" + Thread.currentThread().getName() + "] begin...");
try {
ThreadSleepWait.class.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("[" + Thread.currentThread().getName() + "] end...");
}
}
}
查看堆栈:
"T2" #12 prio=5 os_prio=0 tid=0x0000000019b74800 nid=0x1f4c in Object.wait() [0x000000001a43f000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x00000000d6718638> (a java.lang.Class for com.example.demo.thread.ThreadSleepWait)
at java.lang.Object.wait(Object.java:502)
at com.example.demo.thread.ThreadSleepWait.show(ThreadSleepWait.java:28)
- locked <0x00000000d6718638> (a java.lang.Class for com.example.demo.thread.ThreadSleepWait)
at com.example.demo.thread.ThreadSleepWait$$Lambda$2/769287236.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)
"T1" #11 prio=5 os_prio=0 tid=0x0000000019b73800 nid=0x3e3c in Object.wait() [0x000000001a33f000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x00000000d6718638> (a java.lang.Class for com.example.demo.thread.ThreadSleepWait)
at java.lang.Object.wait(Object.java:502)
at com.example.demo.thread.ThreadSleepWait.show(ThreadSleepWait.java:28)
- locked <0x00000000d6718638> (a java.lang.Class for com.example.demo.thread.ThreadSleepWait)
at com.example.demo.thread.ThreadSleepWait$$Lambda$2/769287236.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)
- 俩线程都处于
WAITING
状态, 符合上面场景, 另外两个方法可以自行试试~
输出结果是:
[T1] begin...
[T2] begin...
- 两个线程都能执行, 说明
wait()
释放资源的同时也释放了Monitor
锁
把代码改动一下:
public static void main(String[] args){
Stream.of("T1", "T2").forEach(name -> new Thread(ThreadSleepWait::show, name).start());
}
private static void show() {
synchronized (ThreadSleepWait.class) {
System.out.println("[" + Thread.currentThread().getName() + "] begin...");
try {
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("[" + Thread.currentThread().getName() + "] end...");
}
}
输出一行后
[T1] begin...
查看堆栈:
"T2" #12 prio=5 os_prio=0 tid=0x0000000019beb000 nid=0x4fc8 waiting for monitor entry [0x000000001a48f000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.example.demo.thread.ThreadSleepWait.show(ThreadSleepWait.java:25)
- waiting to lock <0x00000000d6718718> (a java.lang.Class for com.example.demo.thread.ThreadSleepWait)
at com.example.demo.thread.ThreadSleepWait$$Lambda$2/769287236.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)
"T1" #11 prio=5 os_prio=0 tid=0x0000000019bea800 nid=0x2184 waiting on condition [0x000000001a38f000]
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 com.example.demo.thread.ThreadSleepWait.show(ThreadSleepWait.java:30)
- locked <0x00000000d6718718> (a java.lang.Class for com.example.demo.thread.ThreadSleepWait)
at com.example.demo.thread.ThreadSleepWait$$Lambda$2/769287236.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)
-
T1
得到运行, 调用Thread#sleep(long)
方法后处于TIMED_WAITING
状态 - 由于此锁为类锁,
T2
必须等待T1
释放此锁, 也就是处于等待进入一个同步块
的状态, 即BLOCKED
这里顺带提一句
sleep
与wait
的区别, 上面的例子也有印证.
-
wait
方法会释放锁, 而且会释放此锁上的所有资源, 注意一点, 若此线程同时持有其他锁并不会释放 -
wait
方法有wait()
方法, 然后wait(0)
表示永久等待 -
sleep
方法不会释放已持有锁, 但会释放CPU资源 -
sleep
方法没有sleep()
方法,sleep(0)
表示不休眠(表象是这样, 其实会让出一些未使用的时间片)
wait
为啥需要在同步块中执行?因为wait
的意思就是释放锁, 如果你都没有进入同步块(获得锁), 你又如何释放呢? 这个时候就会抛出一个锁状态异常IllegalMonitorStateException
最终输出:
[T1] begin...
[T1] end...
[T2] begin...
[T2] end...
- 阻塞执行。
中断
首先, 中断不等于线程结束.
没有任何文档中明确指出线程被中断后应该被终结.
最近看的源码中也有一些中断处理的例子, 比如线程池中的工作线程使用AQS
来防止执行任务时被中断(提前锁住),ReentrantLock
中的中断延迟响应
等等;
那么中断到底是什么?
先来看下Thread
类中的中断相关方法:
// 仅返回中断标识
public boolean isInterrupted() {
return isInterrupted(false);
}
// 静态方法, 清空中断标识, 同时返回原有中断标识
public static boolean interrupted() {
return currentThread().isInterrupted(true);
}
// 仅设置中断标识 -> true
public void interrupt() {
interrupt0(); // Just to set the interrupt flag
}
/**
* Tests if some Thread has been interrupted. The interrupted state
* is reset or not based on the value of ClearInterrupted that is
* passed.
*/
private native boolean isInterrupted(boolean ClearInterrupted);
- 处理机制
- 中断一个阻塞线程(wait/sleep/join)
抛出 InterruptedException 异常, 并清除中断标识位(置为false)
Java 高并发编程详解
一书中有一句话我觉得解释得很有道理
可中断方法捕获到了中断信号后,为了不影响线程中其他方法的使用,将中断标识复位是一种很合理的设计。
-
中断一个运行线程
仅将中断标识置为true, 不会抛出 InterruptedException 异常 -
注释中还单独列举
I/O operation
和{@link java.nio.channels.Selector}
的处理(比如抛出指定异常)
- 应答机制
-
运行时始终检测中断标识位 while(!t.isInterrupted()) {...}
-
阻塞时正确处理中断异常
InterruptedException
这里有个需要注意的点是当抛出InterruptedException
时, 其中断标识位会被清除(也就是设回 false
), 所以建议是不要捕获了此异常后啥都不做, 推荐做法是直接往上抛出.
中断标识与 InterruptedException 异常 仅是线程对外提供能被感知到的一种信号, 线程自身决定如何处理此信号
while(!t.isInterrupted()) {
try {
// main work
} catch (InterruptedException e) {
// do something when interrupted
}
}
十点多啦,回家喝CoCo~~
网友评论