JAVA进阶系列 - 并发编程 - 第5篇 Thread API

该昵称无法识别 2021-01-23 20:03:54
java 编程 并发 进阶 系列


在上一篇中介绍了 Thread 类的构造方法,可是光有构造方法也不够,我们还得再学习多一些该类常用的 API 才行,这样才能对该类有更深刻的了解,同时也能让我们有更多的选择。

Thread类提供的API有几十个,由于篇幅问题,本篇文章仅选择几个有代表性的来进行讲解。剩余的API小伙伴们感兴趣的可以通过源码进行查看,也可以给我留言,我们共同探讨共同学习。

目标

  1. currentThread
  2. setPriority
  3. yield
  4. sleep
  5. interrupt
  6. interrupted
  7. join

内容

1. currentThread

该方法用于返回当前执行线程的引用,我们可以在代码块中通过它来获取当前的线程对象,虽然看起来很简单,但是使用非常广泛,在后续的内容中都会大量使用到该方法。

方法:

public static native Thread currentThread();

代码:

/**
* 这个例子我们可以看到 Thread.currentThread() 这里拿到的都是各自的执行线程引用对象。
*/
public class CurrentThreadDemo {
public static void main(String[] args) {
// 打印结果为:true
Thread t = new Thread(() -> {
System.out.println("t".equals(Thread.currentThread().getName()));
}, "t");
t.start();
// 打印结果为:true
System.out.println("main".equals(Thread.currentThread().getName()));
}
}

2. setPriority

进程有进程的优先级,线程同样也有优先级,理论上是优先级比较高的线程会优先被CPU进行调度,但事实上往往并不会如你所愿。

如果CPU比较忙,设置优先级可能会获得更多的CPU时间片,但是在CPU空闲的情况下,设置优先级几乎不会有任何作用。所以,我们不要试图在程序设计中使用优先级来绑定某些业务或者让业务依赖于优先级,这产生的结果可能会与你所期望的结果不一致。

方法:

public final void setPriority(int newPriority); // 设置线程优先级
public final int getPriority(); // 获取线程优先级

案例:

/**
* t1 线程的优先级比 t2 线程的低,正常来说应该统计数量会比 t2 线程的少
* 但是我这里随机测试统计到的次数为:
* t1: 59573
* t2: 34321
* 不同的CPU资源情况会有不同的运行结果,小伙伴们可以多测试几次看看
*/
public class PriorityDemo {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
while (true) {
System.out.println("t1");
}
}, "t1");
Thread t2 = new Thread(() -> {
while (true) {
System.out.println("t2");
}
}, "t2");
// 最小值为:1,中间值为:5,最大值为:10
t1.setPriority(9);
t2.setPriority(10);
t1.start();
t2.start();
}
}

3. yield

yield方法属于一种启发式的方法,它给调度程序的提示是当前线程愿意放弃对处理器的当前使用。调度程序可以随意忽略此提示。应将其使用与详细的性能分析和基准测试结合起来,以确保它实际上具有所需的效果。

此方法很少被使用。对于调试或测试目的,它可能很有用,因为它可能有助于重现由于竞争条件而产生的错误。

方法:

public static native void yield();

案例:

/**
* 将注释部分放开的话可以看到控制台输出的结果有时候是 0 在前面,有时候是 1 在前面
* 而将该部分注释之后,你会发现一只都是 0 在前面
*/
public class YieldDemo {
public static void main(String[] args) {
IntStream.range(0, 2).mapToObj(YieldDemo::test).forEach(Thread::start);
}
private static Thread test(int index){
return new Thread(() -> {
// if (index == 0){
// Thread.yield();
// }
System.out.println(index);
});
}
}

4. sleep

sleep是一个静态方法,根据系统计时器和调度程序的精度和准确性,使当前正在执行的线程进入休眠状态(暂时停止执行)达指定的毫秒数。该线程不会失去任何监视器的所有权(例如monitor锁,关于monitor锁在后续的文章中会进行详细讲解)。

方法:

public static native void sleep(long millis); // 休眠的毫秒数
public static void sleep(long millis, int nanos); // 休眠的毫秒数与纳秒数

案例:

/**
* 在这个例子中,我们分别在自定义线程喝主线程中进行了休眠,每个线程的休眠互不影响
* Thread.sleep() 只会导致当前线程休眠指定的时间
*/
public class SleepDemo {
public static void main(String[] args) {
new Thread(() -> {
long startTime = System.currentTimeMillis();
sleep(2000);
System.out.printf("%s线程耗时:%d%s", Thread.currentThread().getName(), System.currentTimeMillis() - startTime, "ms");
System.out.println("");
}, "t").start();
long startTime = System.currentTimeMillis();
sleep(3000);
System.out.printf("%s线程耗时:%d%s", Thread.currentThread().getName(), System.currentTimeMillis() - startTime, "ms");
}
private static void sleep(long millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

yield 和 sleep 的区别:

  • yield 只是对 CPU 调度器的一个提示,如果生效了,会导致线程上下文的切换;
  • sleep 会导致当前线程暂停指定的时间,没有 CPU 时间片的消耗;
  • yield 会使线程由 RUNNING 状态进入 RUNNABLE 状态;
  • sleep 会使线程短暂 block,之后在给定的时间内释放 CPU 资源;
  • yield 不能保证一定生效,而 sleep 几乎百分百的按照指定时间进行休眠(最终休眠时间要以系统的定时器和调度器的精度为准)
  • yield 无法捕获中断信号,而 sleep 被另一个线程打断则能捕获到中断信号

5. interrupt

线程interrupt是一个非常重要的API,也是经常使用的方法,如果在调用:

  1. Object类的 wait(),wait(long)或wait(long,int)方法或join(),join(long),join(long,int)的方法时阻塞了此线程,此类的sleep(long)或sleep(long,int)方法,则其中断状态将被清除,并将收到InterruptedException
  2. InterruptibleChannel 的 I/O 操作,则该通道将被关闭,该线程的中断状态将被设置,并且该线程将收到java.nio.channels.ClosedByInterruptException
  3. Selector 的 wakeup 方法,则将设置该线程的中断状态,并且它将立即从选择操作中返回(可能具有非零值),就像调用选择器的唤醒方法一样。

与之相关的API还有几个。

方法:

public void interrupt(); // 中断阻塞
public static boolean interrupted(); // 判断当前线程是否被中断,该方法会直接擦除掉线程的标识
public boolean isInterrupted(); // 判断当前线程是否被中断,仅做判断不影响标识
// interrupted 和 isInterrupted 方法都是调用本地方法 isInterrupted() 来实现,该方法中的参数 ClearInterrupted 主要用来控制是否擦除线程 interrupt 的标识,该标识被擦除后,后续的所有判断都将会是 false
private native boolean isInterrupted(boolean ClearInterrupted);

案例:

/**
* 新建一个线程 t,休眠 1分钟
* 主线程休眠 2秒钟之后,对线程 t进行打断,控制台输出:interrupt,程序结束
*/
public class InterruptDemo {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
try {
Thread.sleep(60 * 1000);
} catch (InterruptedException e) {
System.out.println("interrupt");
}
}, "t");
t.start();
Thread.sleep(2000);
t.interrupt();
}
}

在线程内部中存在一个名为 interrupt flag 的标识,如果一个线程被 interrupt,那么它的flag将被设置,在源码中我们也可以看到有对应的描述。

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();
}

但是如果当前线程正在执行可中断方法被阻塞时,调用interrupt方法将其中断,反而会导致flag被清楚,关于这点在后面的文章中还会详细介绍。

7. join

Thread 的 join 方法同样也是一个非常重要的方法,使用它的特性可以实现很多比较强的功能,在 Thread 类中给我们提供了三个不同的 方法,具体如下。

方法:

public final void join(); // 永久等待该线程生命周期结束
public final synchronized void join(long millis); // 设置最长等待毫秒值,为 0 则永久等待
public final synchronized void join(long millis, int nanos); // 设置最长等待毫秒值与纳秒值,为 0 则永久等待

案例:

/**
* 创建两个线程 1 和 2 并分别启动,在控制台进行输出。
* 同时main线程调用这两个线程的方法,这时你会发现线程 1 和 2 会交替的输出直到两个线程都运行完毕
* 此时main线程才开始循环输出,如果将 join 方法注释掉,则三个线程会同时进行输出
*/
public class JoinDemo {
public static void main(String[] args) throws InterruptedException {
List<Thread> list = IntStream.range(1, 3).mapToObj(JoinDemo::getThread).collect(Collectors.toList());
list.forEach(Thread::start);
for (Thread thread : list) {
thread.join();
}
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "-" + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private static Thread getThread(int name){
return new Thread(() -> {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "-" + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, String.valueOf(name));
}
}

总结

在本篇文章中,我们学习了 Thread 的一些较常见的 API,Thread 的 API 是掌握高并发编程的基础,非常有必要熟练掌握!

今天的文章到这里就结束了,小伙伴们有什么建议或者意见请联系我改进哦,你们的支持是我最大的动力!!!
本文由博客群发一文多发等运营工具平台 OpenWrite 发布
版权声明
本文为[该昵称无法识别]所创,转载请带上原文链接,感谢
https://segmentfault.com/a/1190000039070551

  1. 【计算机网络 12(1),尚学堂马士兵Java视频教程
  2. 【程序猿历程,史上最全的Java面试题集锦在这里
  3. 【程序猿历程(1),Javaweb视频教程百度云
  4. Notes on MySQL 45 lectures (1-7)
  5. [computer network 12 (1), Shang Xuetang Ma soldier java video tutorial
  6. The most complete collection of Java interview questions in history is here
  7. [process of program ape (1), JavaWeb video tutorial, baidu cloud
  8. Notes on MySQL 45 lectures (1-7)
  9. 精进 Spring Boot 03:Spring Boot 的配置文件和配置管理,以及用三种方式读取配置文件
  10. Refined spring boot 03: spring boot configuration files and configuration management, and reading configuration files in three ways
  11. 精进 Spring Boot 03:Spring Boot 的配置文件和配置管理,以及用三种方式读取配置文件
  12. Refined spring boot 03: spring boot configuration files and configuration management, and reading configuration files in three ways
  13. 【递归,Java传智播客笔记
  14. [recursion, Java intelligence podcast notes
  15. [adhere to painting for 386 days] the beginning of spring of 24 solar terms
  16. K8S系列第八篇(Service、EndPoints以及高可用kubeadm部署)
  17. K8s Series Part 8 (service, endpoints and high availability kubeadm deployment)
  18. 【重识 HTML (3),350道Java面试真题分享
  19. 【重识 HTML (2),Java并发编程必会的多线程你竟然还不会
  20. 【重识 HTML (1),二本Java小菜鸟4面字节跳动被秒成渣渣
  21. [re recognize HTML (3) and share 350 real Java interview questions
  22. [re recognize HTML (2). Multithreading is a must for Java Concurrent Programming. How dare you not
  23. [re recognize HTML (1), two Java rookies' 4-sided bytes beat and become slag in seconds
  24. 造轮子系列之RPC 1:如何从零开始开发RPC框架
  25. RPC 1: how to develop RPC framework from scratch
  26. 造轮子系列之RPC 1:如何从零开始开发RPC框架
  27. RPC 1: how to develop RPC framework from scratch
  28. 一次性捋清楚吧,对乱糟糟的,Spring事务扩展机制
  29. 一文彻底弄懂如何选择抽象类还是接口,连续四年百度Java岗必问面试题
  30. Redis常用命令
  31. 一双拖鞋引发的血案,狂神说Java系列笔记
  32. 一、mysql基础安装
  33. 一位程序员的独白:尽管我一生坎坷,Java框架面试基础
  34. Clear it all at once. For the messy, spring transaction extension mechanism
  35. A thorough understanding of how to choose abstract classes or interfaces, baidu Java post must ask interview questions for four consecutive years
  36. Redis common commands
  37. A pair of slippers triggered the murder, crazy God said java series notes
  38. 1、 MySQL basic installation
  39. Monologue of a programmer: despite my ups and downs in my life, Java framework is the foundation of interview
  40. 【大厂面试】三面三问Spring循环依赖,请一定要把这篇看完(建议收藏)
  41. 一线互联网企业中,springboot入门项目
  42. 一篇文带你入门SSM框架Spring开发,帮你快速拿Offer
  43. 【面试资料】Java全集、微服务、大数据、数据结构与算法、机器学习知识最全总结,283页pdf
  44. 【leetcode刷题】24.数组中重复的数字——Java版
  45. 【leetcode刷题】23.对称二叉树——Java版
  46. 【leetcode刷题】22.二叉树的中序遍历——Java版
  47. 【leetcode刷题】21.三数之和——Java版
  48. 【leetcode刷题】20.最长回文子串——Java版
  49. 【leetcode刷题】19.回文链表——Java版
  50. 【leetcode刷题】18.反转链表——Java版
  51. 【leetcode刷题】17.相交链表——Java&python版
  52. 【leetcode刷题】16.环形链表——Java版
  53. 【leetcode刷题】15.汉明距离——Java版
  54. 【leetcode刷题】14.找到所有数组中消失的数字——Java版
  55. 【leetcode刷题】13.比特位计数——Java版
  56. oracle控制用户权限命令
  57. 三年Java开发,继阿里,鲁班二期Java架构师
  58. Oracle必须要启动的服务
  59. 万字长文!深入剖析HashMap,Java基础笔试题大全带答案
  60. 一问Kafka就心慌?我却凭着这份,图灵学院vip课程百度云