- 本系列博客,主要是面向Java8的源码。
- 本系列博客主要参考汪文君老师 《Java高并发编程详解》一书
- 转载请注明出处,多谢~。
1. 线程的start方法剖析
/**
* Causes this thread to begin execution; the Java Virtual Machine
* calls the <code>run</code> method of this thread.
*/
public synchronized void start() {
if (threadStatus != 0)
throw new IllegalThreadStateException();
group.add(this);
boolean started = false;
try {
start0();//核心部分就是这个start0本地方法,也就是JNI方法,run方法就是被此方法调用
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
}
}
}
2. Runnable接口的引入
//Runnable接口非常简单,只定义了一个无参数无返回值的run方法
public interface Runnable {
public abstract void run();
}
可以通过继承Thread然后重写run方法实现自己的业务逻辑,也可以实现Runnable接口实现
//构造Thread时传递Runnable
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
}
});
//Lambda简化
Thread thread = new Thread(() -> {
});
注意:
- 准确地讲,创建线程只有一种方式那就是构造Thread类,而实现线程的执行单元则有两种方式
- Thread负责线程本身相关的职责和控制,而Runnable则负责逻辑执行单元的部分
3. 线程的父子关系
Thread的所有构造函数,最终都会调用一个方法 init()
public Thread() {
init(null, null, "Thread-" + nextThreadNum(), 0);
}
private void init(ThreadGroup g, Runnable target, String name,
long stackSize) {
init(g, target, name, stackSize, null, true);
}
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();
......
}
结论:
- 一个线程的创建肯定是由另一个线程完成的
- 被创建的线程的父线程就是创建它的线程
4. Thread与ThreadGroup
在Thread的构造函数中,可以显示的指定线程的Group,也就是ThreadGroup
//接着读Thread init方法的源码,你就会发现
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();
}
}
结论:
- main线程所在的ThreadGroup成为main
- 构造一个线程时如果没有显示的指定ThreadGroup,那么它将会和父线程同属于一个ThreadGroup
5. 守护线程
守护线程是一类比较特殊的线程,一般用于处理一些后台的工作,比如JDK的垃圾回收线程
JVM程序在什么情况下会退出?
The Java Virtual Machine exits when the only threads running are all daemon threads.
在正常退出的情况下,若JVM中没有一个非守护线程,则JVM的进程会退出
public class Demo {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
while (true) {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread.setDaemon(true); //将thread设置为守护线程
thread.start(); //启动thread线程
Thread.sleep(2000L);
System.out.println("Main thread exit");
}
}
运行结果:main线程结束生命周期后,JVM也随之退出运行
6. Thread API
6.1 线程sleep
//sleep是一个静态方法,会让当前线程进入指定毫秒数的休眠
//注意:休眠不会放弃monitor锁的所有权
Thread.sleep()
//使用TimeUnit代替Thread.sleep
TimeUnit.HOURS.sleep(3)//休眠3小时
TimeUnit.MINUTES.sleep(24)//休眠24分钟
TimeUnit.SECONDS.sleep(17)//休眠17秒
TimeUnit.MILLISECONDS.sleep(12)//休眠12毫秒
6.2 线程yield
//yield方法属于一种启发式的方法
//其会提醒调度器我愿意放弃当前的CPU资源,如果CPU资源不紧张,则会忽略 RUNNING->RUNNABLE
public static native void yield();
注意:
- sleep会导致当前线程暂停指定的时间,没有CPU时间片的消耗
- yield只是对CPU调度器的一个提示,如果CPU调度器没有忽略这个提示,它会导致线程上下文的切换
- sleep会使线程短暂block,会在给定的时间内释放CPU资源
- 一个线程sleep另一个线程调用interrupt会捕获到中断信号,而yield则不会
6.3 设置线程优先级
public final void setPriority(int newPriority)
为线程设置优先级
public final int getPriority()
获取线程的优先级
源码分析:
public final static int MAX_PRIORITY = 10;
public final static int MIN_PRIORITY = 1;
public final void setPriority(int newPriority) {
ThreadGroup g;
checkAccess();
if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) { //线程的优先级不能小于1也不能大于10
throw new IllegalArgumentException();
}
if((g = getThreadGroup()) != null) {
//如果线程的优先级大于线程所在group的优先级,指定的优先级就会失效,取而代之的是group的最大优先级
if (newPriority > g.getMaxPriority()) {
newPriority = g.getMaxPriority();
}
setPriority0(priority = newPriority);
}
}
6.4 获取线程ID
public long getId()
线程的ID在整个JVM进程中都会是唯一的,并且都是从0开始逐次递增
自己创建的线程绝非第0号线程
6.5 获取当前线程
public static native Thread currentThread();
用于返回当前执行线程的引用
6.6 线程interrupt
public void interrupt()
public static boolean interrupted()
判定当前线程是否被中断
public boolean isInterrupted()
判定当前线程是否被中断
以下方法的调用会使得当前线程进入阻塞状态,调用interrupt方法可以打断阻塞
- Object的
wait()
方法- Object的
wait(long)
方法- Object的
wait(long,int)
方法- Thread的
sleep(long)
方法- Thread的
sleep(long,int)
方法- Thread的
join()
方法- Thread的
join(long)
方法- Thread的
join(long,int)
方法- InterruptibleChannel的io操作
- Selector的wakeup方法
- 其他方法
上述的方法都会使得当前线程进入阻塞状态
另外一个线程调用被阻塞线程的interrupt方法可以打断阻塞
因此这些方法有时候被称为可中断的方法
注意:仅仅打断了线程的阻塞状态,不等于生命周期结束
interrupt到底做了什么?
①:在一个线程内部存在着名为interrupt flag的标识
②:如果一个线程被interrupt,那么它的flag将被设置
③:线程执行可中断方法被阻塞时,调用interrupt方法将其中断,反而会导致flag被清除
④:如果一个线程是死亡状态,iterrupt会直接被忽略
public class Demo1 {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
while (true) {
//空的死循环,不会捕捉中断信号
}
});
thread.start(); //启动thread线程
Thread.sleep(2000L);
System.out.println(thread.isInterrupted());//false
thread.interrupt();//
Thread.sleep(2000L);
System.out.println(thread.isInterrupted());//true
}
}
public class Demo2 {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
while (true) {
try {
Thread.sleep(1000L);//可中断方法,会捕捉到中断信号,并且擦去interrupt标识
} catch (InterruptedException e) {
System.out.println("I am be interrupted");
}
}
});
thread.start();
Thread.sleep(2000L);
System.out.println(thread.isInterrupted());//false
thread.interrupt();
Thread.sleep(2000L);
System.out.println(thread.isInterrupted());//false
}
}
interrupted()
和isInterrupted()
区别
interrupted是一个静态方法,虽然其也用于判断当前线程是否被中断,但是调用该方法会直接擦除线程的interrupt标识
如果当前线程被打断,第一次调用Interrupt方法会返回true,并且擦除interrupt标识,第二次之后的调用都会返回false,除非再次被打断
public class ThreadInterrupted {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
while (true) {
System.out.println(Thread.interrupted());
}
});
thread.setDaemon(true);
thread.start();
TimeUnit.MILLISECONDS.sleep(2);
thread.interrupt();
}
}
/**
* 输出
* ...
* false
* false
* true
* false
* false
* ...
*/
interrupt注意事项:
//interrupted源码
public static boolean interrupted() {
return currentThread().isInterrupted(true); //擦除interrupt标识
}
//isInterrupted源码
public boolean isInterrupted() {
return isInterrupted(false); //不擦除interrupt标识
}
//都调用了isInterrupted这个本地方法
private native boolean isInterrupted(boolean ClearInterrupted);//ClearInterrupted主要来控制是否擦除interrupt标识
分析下面两段程序,有什么不同?
public class Demo1 {
public static void main(String[] args) throws InterruptedException {
// 1,判断当前线程是否被中断
System.out.println("Main Thread is interrupted? " + Thread.interrupted());
//2,中断当前线程
Thread.currentThread().interrupt();
//3,判断当前线程是否已经被中断
System.out.println("Main Thread is interrupted? " + Thread.currentThread().isInterrupted());
try {
//4,执行可中断方法
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
System.out.println("Interrupted Exception");
}
}
}
//输出
// Main Thread is interrupted? false
// Main Thread is interrupted? true
// Interrupted Exception
public class Demo2 {
public static void main(String[] args) throws InterruptedException {
// 1,判断当前线程是否被中断
System.out.println("Main Thread is interrupted? " + Thread.currentThread().isInterrupted());
//2,中断当前线程
Thread.currentThread().interrupt();
//3,判断当前线程是否已经被中断
System.out.println("Main Thread is interrupted? " + Thread.interrupted());
try {
//4,执行可中断方法
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
System.out.println("Interrupted Exception");
}
}
}
//输出
//Main Thread is interrupted? false
//Main Thread is interrupted? true
可以看出Demo2的可中断方法没有抛出异常,意味着没有被中断,而Demo1被中断了
原因:
interrupted()
立即擦除了Interrupt标识isInterrupted()
捕获到了InterruptedException
之后才会擦除Interrupt标识
6.7 线程join
可中断方法
join某个线程A,会使当前线程B进入等待,知道A生命周期结束,或者到达给定时间,B此时是BLOCKED的
public class ThreadJoin {
public static void main(String[] args) throws InterruptedException {
//1.定义两个线程
List<Thread> threads = IntStream.range(1, 3).mapToObj(ThreadJoin::create).collect(Collectors.toList());
//2.启动两个线程
threads.forEach(Thread::start);
//3.执行这两个线程的join方法
for (Thread thread : threads) {
thread.join();
}
//4.main线程循环输出
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "#" + i);
shortSleep();
}
}
//构造一个简单线程,每个线程只是简单的循环输出
private static Thread create(int seq) {
return new Thread(() -> {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "#" + i);
shortSleep();
}
}, String.valueOf(seq));
}
private static void shortSleep() {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
运行上面的代码:
//输出 线程一和线程二交替输出直到生命周期结束,main线程才开始运行
...
2#8
1#8
1#9
2#9
main#0
main#1
main#2
...
//注释掉3 线程一和线程二和main线程交替执行
总结:join方法会使当前线程永远的等待下去,直到期间被另外的线程中断,或者join的线程执行结束,或者join的另外两个重载方法指定的毫秒数时间到达后,当前线程才会退出阻塞
6.8 如何关闭一个线程
6.8.1 正常关闭
-
线程结束生命周期正常结束
-
捕获中断信号关闭线程
public class Way1 { public static void main(String[] args) throws InterruptedException { Thread t = new Thread() { @Override public void run() { while (!isInterrupted()) { //working } } }; t.start(); TimeUnit.MINUTES.sleep(1); t.interrupt(); } } public class Way2 { public static void main(String[] args) throws InterruptedException { Thread t = new Thread(() -> { for (; ; ) { //working try { TimeUnit.MILLISECONDS.sleep(1); } catch (InterruptedException e) { break; } } }); t.start(); TimeUnit.MINUTES.sleep(1); t.interrupt(); } }
-
使用volatile开关控制
public class FlagThreadExit { static class MyTask extends Thread { private volatile boolean closed = false; @Override public void run() { System.out.println("I will start work"); while (!closed && !isInterrupted()) { //working... } System.out.println("I will stop work"); } public void close() { closed = true; this.interrupt(); } } public static void main(String[] args) throws InterruptedException { MyTask t = new MyTask(); t.start(); TimeUnit.MINUTES.sleep(1); System.out.println("System will be end"); t.close(); } }
上面的例子定义了一个closed开关变量,并且是用volatile修饰,同样可以关闭线程
6.8.2 异常退出
在一个线程的执行单元中,是不允许抛出checked异常的,不论Thread的run方法还是Runnable的run方法,如果线程运行过程中需要捕获checked异常并且判断是否还有运行下去的必要,那么可以将checked异常封装成unchecked异常(RuntimeException)抛出进而结束线程的生命周期
6.8.3 进程假死
标签:第一,Thread,基础,thread,线程,interrupt,sleep,多线程,public From: https://blog.csdn.net/qq_70003997/article/details/139186553所谓假死,就是进程虽然存在,但是没有日志输出,程序不进行任何的作业,看起来就像死了一样,但事实上并没有死
一般是由于某个线程阻塞了或者出现死锁的情况