文章目录
1 Thread API
Thread类方法:
主要学习:
- sleep
- yield
- interrupt
- join
案例中涉及代码 --> 点击跳转
1.1 线程sleep
1.1.1 Thread中的sleep方法
- public static native void sleep(long millis) throws InterruptedException;
- public static void sleep(long millis, int nanos) throws InterruptedException
sleep会时当前线程进入指定毫秒数休眠,暂停执行,虽然给定了一个休眠的时间,但最终要以系统的定时器和调度器的精度为准,休眠并不会放弃monitor锁的所有权;
public static void main(String[] args) {
//案例用以说明sleep方法只会导致当前线程进行指定时间的休眠
//启一个线程
new Thread(() -> {
//记录匿名线程的休眠时间
long startTime = System.currentTimeMillis();
//休眠2000毫秒
sleep(2_000l);
long endTime = System.currentTimeMillis();
System.out.println(String.format("Thread -> Total spend %d ms", (endTime - startTime)));
}).start();
//记录main方法线程的休眠时间
long startTime = System.currentTimeMillis();
//休眠3000毫秒
sleep(3_000l);
long endTime = System.currentTimeMillis();
System.out.println(String.format("Main -> Total spend %d ms", (endTime - startTime)));
}
/**
* 调用Thread的静态方法sleep
* 使调用的线程休眠相应的毫秒数
*
* @param millis
*/
private static void sleep(long millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
执行结果:
Thread -> Total spend 2000 ms
Main -> Total spend 3000 ms
1.1.2 使用TimeUnit代替Thread.sleep
Jdk1.5后引入了枚举TimeUnit,对sleep进行了很好的封装,使用它可以省去时间换算的步骤,并更显优雅:
new Thread(() -> {
try {
//使用Thread.sleep 休眠1秒
Thread.sleep(1000l);
//使用TimeUnit纳秒 休眠1微秒
TimeUnit.NANOSECONDS.sleep(1000l);
//使用TimeUnit微秒 休眠1毫秒
TimeUnit.MICROSECONDS.sleep(1000l);
//使用TimeUnit毫秒 休眠1秒
TimeUnit.MILLISECONDS.sleep(1000l);
//使用TimeUnit秒 休眠1分钟
TimeUnit.SECONDS.sleep(60l);
//使用TimeUnit分钟 休眠1分钟
TimeUnit.MINUTES.sleep(1l);
//使用TimeUnit小时 休眠1天
TimeUnit.HOURS.sleep(24l);
//使用TimeUnit天 休眠1天
TimeUnit.DAYS.sleep(1l);
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
1.2 线程yield
1.2.1 Thread中的yield方法
- public static native void yield();
yield ([ji:ld放弃) 方法属于一种启发式方法,其会提醒调度器本线程愿意放弃当前的CPU资源,如果CPU资源不紧张,则会忽略这个提醒;
调用yield方法回事当前线程从RUNNING状态转换至RUNNABLE状态;
案例中如果注释if判断,执行结果总是正常的顺序,如果打开则顺序会错乱:
public static void main(String[] args) {
IntStream.range(0, 20).mapToObj(ThreadYield::create).forEach(Thread::start);
}
private static Thread create(int index) {
return new Thread(() -> {
//注释if判断 总是正常的顺序
if (index == 0)
Thread.yield();
System.out.println(Thread.currentThread().getName() + ":" + index);
});
}
执行结果:
//前两顺序乱了
Thread-1:1
Thread-0:0
Thread-2:2
Thread-3:3
Thread-4:4
Thread-5:5
Thread-6:6
Thread-7:7
Thread-8:8
Thread-9:9
1.2.2 yield和sleep
JDK1.5以前yield方法实际上时调用了sleep(0)方法,但他们存在本质的区别:
- sleep会导致当前线程暂停指定的时间,没有CPU时间片的消耗
- yield只是对CPU调度器的一个提示,如果CPU未忽略,会导致线程上下文的切换
- sleep会使线程短暂block,会在给定的时间内释放CPU资源
- yield会使线程从RUNNING转至RUNNABLE状态
- sleep会百分之百完成给定时间的休眠,而yield不一定
- 一个线程sleep另一个线程调用interrupt会不会到中断信号,而yield则不会
1.3 线程优先级
1.3.1 Thread中的优先级
- public final void setPriority(int newPriority)
- public final int getPriority()
进程有进程的优先级,线程也有优先级,理论上优先级比较高的线程会获得先被CPU调度的机会,事实上可能不会如你所愿:
- 设置线程优先级只是一个对CPU的hint[提示]操作,提示CPU某个线程要优先执行
- 两个优先级不同的线程同时执行,在不同的CPU资源情况下结果可能会不同
让两个优先级不同的线程一起跑,优先级大的出现概率会大一些:
public static void main(String[] args) {
Thread thread0 = new Thread(new Runnable() {
@Override
public void run() {
while (true) {
System.out.println("-" + Thread.currentThread().getName() + "-");
}
}
});
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
while (true) {
System.out.println("-" + Thread.currentThread().getName() + "-");
}
}
});
thread0.setPriority(2);
thread1.setPriority(10);
thread0.start();
thread1.start();
}
1.3.2 优先级源码分析
setPriority方法源码:
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);
}
}
结论:
- 优先级取值范围0~10
- 优先级的取值需要在所在threadGroup的范围内,如果大于所在threadGroup优先级最大值,则取其最大值
- 一般都不会进行优先级的设置,因为在不同CPU资源下结果会有不同
- 在不设置优先级的情况下,其优先级与其父类保持一致
1.4 获取当前线程&线程ID
- public static native Thread currentThread()
- public long getId()
方法acurrentThread():返回当前线程的引用,使用非常广泛;
方法getId():获取线程的唯一ID,线程ID在JVM进程中都会是唯一的,并且时从0开始递增的;
1.5 线程interrupt
线程内部存在一个interrupt flag:
- 有很多方法可以将线程置于阻塞状态下
- 当调用interrupt()方法后,线程interrupt flag将被赋值true
- 当线程处于阻塞状态下,调用iterrupt()方法时,会打断当前线程的阻塞并抛出InterruptedException,可中断方法捕获到异常后会将interrupt flag檫除(置为false)
- 当一个线程已经结束后,再调用interrupt()方法会被忽略
1.5.1 线程进入阻塞状态
调用如下方法可以使线程进入阻塞状态:
- Object的wait方法
- Thread的sleep方法
- Thread的join方法
- InterruptibleChannel的io操作
- Selector的wakeup方法
- 等
此时,若另外一个线程调用被阻塞线程的interrupted()中断方法,则会打断这种阻塞状态,并抛出一个InterruptedException异常:
1.5.2 线程的interrupt方法
- public static boolean interrupt()
调用当前线程的interrupted()方法,可以打断当前线程的阻塞:
public static void main(String[] args) throws InterruptedException {
//new一个线程
Thread thread = new Thread(() -> {
try {
//休眠5分钟
TimeUnit.MINUTES.sleep(5l);
} catch (InterruptedException e) {
//被打断时打印
System.out.println("fuck! i am be interrupted.");
}
});
//启动线程
thread.start();
//主线程休眠5s
TimeUnit.SECONDS.sleep(5l);
//调用thread的interrupt方法 打断thread
thread.interrupt();
}
执行过程:
执行结果:
fuck! i am be interrupted.
1.5.2 线程的isInterrupted方法
- public boolean isInterrupted()
isInterrupted()方法用于判断当前线程是否被中断,该方法只是对interrupted flag的一个判断,并不会影响flag发生任何变化;
public static void main(String[] args) throws InterruptedException {
//new一个线程
Thread thread = new Thread(() -> {
//控循环
while (true) {
//不进行sleep 因为可中断方法捕捉到中断信号后,会将其檫除
}
});
//启动线程
thread.start();
//主线程休眠2ms
TimeUnit.MILLISECONDS.sleep(2l);
//调用thread的interrupt方法 打断thread
System.out.printf("Thread is interrupted ? %s\n", thread.isInterrupted());
//调用thread的interrupt方法 打断thread,并将interrupt flag置为true
thread.interrupt();
System.out.printf("Thread is interrupted ? %s\n", thread.isInterrupted());
}
执行结果:
Thread is interrupted ? false
Thread is interrupted ? true
将程序修改一下:
public static void main(String[] args) throws InterruptedException {
//new一个线程
Thread thread = new Thread(() -> {
try {
//休眠5分钟
TimeUnit.MINUTES.sleep(5l);
} catch (InterruptedException e) {
//被打断时打印
//可中断方法捕捉到中断信号后,会将其檫除
System.out.println("fuck! i am be interrupted.");
}
});
//设置为daemon,让jvm自动退出
thread.setDaemon(true);
//启动线程
thread.start();
//主线程休眠3s
TimeUnit.SECONDS.sleep(3l);
//调用thread的interrupt方法 打断thread
System.out.printf("Thread is interrupted ? %s\n", thread.isInterrupted());
//调用thread的interrupt方法 打断thread,并将interrupt flag置为true
thread.interrupt();
//休眠3秒等待sleep被打断捕获异常后檫除interrupt flag
TimeUnit.SECONDS.sleep(3l);
System.out.printf("Thread is interrupted ? %s\n", thread.isInterrupted());
}
执行结果:
Thread is interrupted ? false
fuck! i am be interrupted.
Thread is interrupted ? false
1.5.3 线程的interrupted方法
- public static boolean interrupted()
interrupted是一个静态方法:
- 用于判断当前线程是否被中断
- 调用该方法会擦除线程的interrpute flag
- 线程被打断后,第一次调用返回true,并将flag擦除,第二次以后的调用永远返回false
public static void main(String[] args) throws InterruptedException {
//new一个线程
Thread thread = new Thread(() -> {
while (true) {
//第一次调用 返回true 并檫除interrput flag
//第二次以后只返回false了
System.out.println(Thread.interrupted());
}
});
//设置为daemon,让jvm自动退出
thread.setDaemon(true);
//启动线程
thread.start();
//主线程休眠3s
TimeUnit.SECONDS.sleep(1l);
//调用thread的interrupt方法 打断thread,并将interrupt flag置为true
thread.interrupt();
}
interrupt()被调用后,flag被置为true,调用interrupted(),第一次返回flage的值,并将其差擦,后边再调用只返回flag的值,执行结果:
....
false
false
false
true
false
false
false
....
1.5.4 interrupt注意事项
- private native boolean isInterrupted(boolean ClearInterrupted)
interrupted方法和isInterrupt方法和都调用了同一个本地方法isInterrupted(boolean ClearInterrupted),isInterrupt传入false(不清除flag),interrupted传入true(清除flag);
1.6 线程join
- public final synchronized void join(long millis) throws InterruptedException
- public final synchronized void join(long millis, int nanos) throws InterruptedException
- public final void join() throws InterruptedException
线程B join 线程A,线程B会进入等待,直到线程A生命周期结束,或者到达指定的时间,这段时间内线程B处于BLOCKED状态:
public static void main(String[] args) throws InterruptedException {
//1 创建2个线程
List<Thread> threadList = IntStream.range(1, 3)
.mapToObj(ThreadJoin::createThread).collect(Collectors.toList());
//2 启动线程
threadList.forEach(Thread::start);
//3 main方法线程调用这两个线程的join方法
for (Thread thread : threadList) {
thread.join();
}
//4 mian方法线程会等上边两个线程执行完后才进行打印
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + "-->" + i);
sleepForMoment();
}
}
/**
* 创建线程 并休眠5次 2s
*
* @param index
* @return
*/
private static Thread createThread(int index) {
return new Thread(() -> {
Thread.currentThread().setName("weixx" + index);
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + "-->" + i);
sleepForMoment();
}
});
}
/**
* 休眠2秒
*/
private static void sleepForMoment() {
try {
TimeUnit.SECONDS.sleep(2l);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
执行结果:
完整结果:
weixx1-->0
weixx2-->0
weixx1-->1
weixx2-->1
weixx1-->2
weixx2-->2
weixx1-->3
weixx2-->3
weixx1-->4
weixx2-->4
main-->0
main-->1
main-->2
main-->3
main-->4
现在将3出的join方法注掉,会看到weixx-1、weixx-2和main方法线程交替执行:
1.7 线程的关闭
1.7.1 正常关闭
1.7.1.1 线程声明周期正常结束
逻辑单元执行完逻辑后,线程结束生命周期,就会正常退出,这是线程的正常结束;
1.7.1.2 捕获中断信号关闭线程
通过标记来控制逻辑单元的结束,来结束线程生命周期:
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
System.out.println("我正在运行...");
while (!Thread.currentThread().isInterrupted()) {
//空循环
}
System.out.println("我还在运行...");
});
thread.start();
TimeUnit.SECONDS.sleep(8l);
System.out.println("线程即将被关闭...");
thread.interrupt();
}
执行结果:
我正在运行...
线程即将被关闭...
我还在运行...
Thread-0一直在空循环,直到main主线程休眠结束,执行interrupt():
1.7.1.3 通过volatile开关控制
思路跟中断标记一样,都是做一个标记,当这个标记为true时,让执行单元结束来结束线程生命周期;
1.7.2 异常关闭
1.7.2.1 异常退出
线程的执行单元中是不允许跑出cheched异常的,不论Thread中的run方法试试Runnable中的run方法,如果需要在运行过程中捕获checked异常并判断是否有执行下去的必要,那么可以将checked异常封装成unchecked(RuntimeException)异常抛出去进而结束线程生命周期;
1.7.2.2 进程假死
进程假死时,看不到任何逻辑的执行,就像死了一样,大部分原因是某个线程阻塞了,或是出现了死锁的情况,这时候需要借助jstack、jconsole等工具来进行核查诊断,线程假死生命周期并没有结束。
参考文献:
[ 1 ] Java高并发编程详解 汪文君著。–北京:机械工业出版社,2018年6月第1版