多线程系列(二) -Thread类使用详解
一、简介
在之前的文章中,我们简单的介绍了线程诞生的意义和基本概念,采用多线程的编程方式,能充分利用 CPU 资源,显著的提升程序的执行效率。
其中java.lang.Thread是 Java 实现多线程编程最核心的类,学习Thread类中的方法,是学习多线程的第一步。
下面我们就一起来看看,创建线程的几种方式以及Thread类中的常用方法。
二、创建线程的方式
在 JDK 1.8 版本中,创建线程总共有四种方式:
- 继承 Thread 类
- 实现 Runnable 接口
- 使用 Callable 和 Future 创建线程
- 使用 JDK 8 的 Lambda 创建线程
2.1、通过继承 Thread 创建线程
通过继承Thread类来创建线程是最简单的一种方法,继承类重写run()方法,然后通过线程对象实例去调用start()方法即可启动线程。
public class MyThread extends Thread{
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "在运行!");
}
}
MyThread thread = new MyThread();
thread.start();
2.2、通过实现 Runnable 接口创建线程
通过实现Runnable接口来创建线程也是最简单的一种方法,同时也是最常用的一种方式。
开发者只需要实现Runnable接口,然后通过一个Thread类来启动。
public class MyThread implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "在运行!");
}
}
Thread thread = new Thread(new MyThread());
thread.start();
2.3、使用 Callable 和 Future 创建线程
相比通过实现Runnable接口来创建线程,使用Callable和Future组合来创建线程可以实现获取子线程执行结果,弥补了调用线程没有返回值的情况,可以看做是Runnable的一个补充,Callable和Future是 JDK1.5 版本中加入的。
public class MyThread implements Callable<String> {
@Override
public String call() throws Exception {
System.out.println(Thread.currentThread().getName() + "在运行!");
return Thread.currentThread().getName();
}
}
Callable<String> callable = new MyThread();
FutureTask<String> ft = new FutureTask<>(callable);
new Thread(ft).start();
// 通过阻塞方式获取线程执行结果
System.out.println(ft.get());
2.4、使用 JDK 8 的 Lambda 创建线程
Lambda 表达式,是从 JDK1.8 版本开始加入的,可以看作成通过实现Runnable接口创建线程的一种简写。
new Thread(()-> System.out.println(Thread.currentThread().getName() + "在运行!")).start();
2.5、创建线程几种方式的对比
以上四种方式都可以创建线程,使用继承Thread类的方式创建线程时,编写简单,如果需要访问当前线程,无需使用Thread.currentThread()方法,直接使用this即可获得当前线程。
采用实现Runnable、Callable接口的方式创建线程时,线程类只是实现了 Runnable或Callable接口,同时还可以继承其他类,最后通过Thread类来启动线程。它也是最常用的一种创建线程方式,通过接口方式来编程,可以实现代码更加统一。
其实通过继承Thread类创建线程的方式,本质上也可以看成实现了Runnable接口的一个实例,打开源码Thread,你会发现这一点。
public class Thread implements Runnable {
//省略...
}
需要特别注意的地方是:真正启动线程的是start()方法而不是run()方法,单独调用run()方法和调用普通的成员方法一样,不能启动线程。
三、Thread 常用方法介绍
Thread 类常用的方法主要有三大块:
- 构造方法
- 实例方法
- 静态方法
3.1、构造方法
在 JDK 中,Thread 类提供了如下几个常用的构造方法来创建线程。
| 方法 | 描述 |
|---|---|
| Thread() | 创建一个默认设置的线程实例,线程名称采用自增ID命名 |
| Thread(Runnable target) | 创建一个包含可执行对象的线程实例 |
| Thread(Runnable target, String name) | 创建一个包含可执行对象,指定名称的线程实例 |
| Thread(String name) | 创建一个指定名称的线程实例 |
| Thread(ThreadGroup group, String name) | 创建一个指定线程组,线程名称的线程实例 |
| Thread(ThreadGroup group, Runnable target) | 创建一个指定线程组,包含可执行对象的线程实例 |
| Thread(ThreadGroup group, Runnable target, String name) | 创建一个指定线程组,包含可执行对象,指定线程名称的线程实例 |
| Thread(ThreadGroup group, Runnable target, String name, long stackSize) | 创建一个指定线程组,包含可执行对象,指定名称以及堆栈大小的线程实例 |
其中Thread(Runnable target)构造方法最常见。
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
});
thread.start();
其次Thread(Runnable target, String name)构造方法,可以指定线程名称。
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}, "thread-demo");
thread.start();
同时,还支持指定线程组来创建线程。
// 创建一个线程组实例
ThreadGroup tg = new ThreadGroup("线程组1");
// 创建一个线程实例
Thread thread = new Thread(tg,new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getThreadGroup().getName() + ":" + Thread.currentThread().getName());
}
}, "thread-demo");
thread.start();
如果不显式指定线程组,JVM 会将创建的线程归到当前线程所属的线程组中。
关于线程组的相关知识,我们会在后期的系列文章中进行讲解。
3.2、实例方法
在 Java 中,实例方法只有实例对象才能调用,也就是new出来的对象或者反射出来的对象,类是无法直接调用的。
在 JDK 中,Thread 类提供了如下几个常用的实例方法来操作线程。
| 方法 | 描述 |
|---|---|
| public void start() | 启动线程 |
| public void run() | 线程进入可运行状态时,jvm 会调用该线程的 run 方法;单独调用 run 方法,不能启动线程 |
| public final void setName(String name) | 设置线程名称 |
| public final void setPriority(int priority) | 设置线程优先级,默认5,取值1-10 |
| public final void setDaemon(boolean on) | 设置线程为守护线程或用户线程,默认是用户线程 |
| public final void join(long millisec) | 挂起线程 xx 毫秒,参数可以不传 |
| public void interrupt() | 当线程受到阻塞时,调用此方法会抛出一个中断信号,让线程退出阻塞状态 |
| public final boolean isAlive() | 测试线程是否处于活动状态 |
下面我们依次来看看它们之间的用法。
3.2.1、start()
start()方法,简单的说就是启动线程,至于什么时候能运行,需要等待获取 CPU 时间片,然后调用线程对象的run()方法,产生一个异步执行的效果。
样例代码如下:
public class ThreadA extends Thread {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
String time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS").format(new Date());
System.out.println(time + " 当前线程:" + Thread.currentThread().getName() + ",正在运行");
}
}
}
public class ThreadB extends Thread {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
String time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS").format(new Date());
System.out.println(time + " 当前线程:" + Thread.currentThread().getName() + ",正在运行");
}
}
}
public class ThreadTest {
public static void main(String[] args) {
ThreadA threadA = new ThreadA();
ThreadB threadB = new ThreadB();
threadA.start();
threadB.start();
}
}
运行结果:
2023-08-30 15:51:43:331 当前线程:Thread-1,正在运行
2023-08-30 15:51:43:331 当前线程:Thread-1,正在运行
2023-08-30 15:51:43:332 当前线程:Thread-0,正在运行
2023-08-30 15:51:43:332 当前线程:Thread-1,正在运行
2023-08-30 15:51:43:332 当前线程:Thread-0,正在运行
2023-08-30 15:51:43:332 当前线程:Thread-1,正在运行
2023-08-30 15:51:43:332 当前线程:Thread-0,正在运行
2023-08-30 15:51:43:332 当前线程:Thread-1,正在运行
2023-08-30 15:51:43:333 当前线程:Thread-0,正在运行
2023-08-30 15:51:43:333 当前线程:Thread-0,正在运行
结果很明显,CPU 什么时候执行线程的run()方法具有不确定,同时执行线程顺序也具有不确定性,这是采用多线程异步执行程序的一个主要特征。
3.2.2、run()
如果单独调用run()方法,不能启动线程,会像调用普通的成员方法一样,我们可以将上面例子中的threadA.start()改成threadA.run(),再看看结果如何。
public class ThreadTest {
public static void main(String[] args) {
ThreadA threadA = new ThreadA();
ThreadB threadB = new ThreadB();
threadA.run();
threadB.run();
}
}
运行结果:
2023-08-30 16:14:50:983 当前线程:main,正在运行
2023-08-30 16:14:50:984 当前线程:main,正在运行
2023-08-30 16:14:50:985 当前线程:main,正在运行
2023-08-30 16:14:50:985 当前线程:main,正在运行
2023-08-30 16:14:50:985 当前线程:main,正在运行
2023-08-30 16:14:50:986 当前线程:main,正在运行
2023-08-30 16:14:50:986 当前线程:main,正在运行
2023-08-30 16:14:50:986 当前线程:main,正在运行
2023-08-30 16:14:50:987 当前线程:main,正在运行
2023-08-30 16:14:50:987 当前线程:main,正在运行
结果很明显,单独调用Thread类实例run()方法,是没有任何异步效果的,全部被主线程执行。
3.2.3、setName()
setName()方法,简而言之就是设置线程名称,如果不手动设置,创建线程的时候 JDK 会给一个默认的线程名称,从 0 开始依次自增。
开发者可以通过getName()方法获取线程名称,也可以通过getId()获取当前线程的唯一标记,这个值用户无法手动设置,由Thread类自动生成。
样例代码如下:
public class ThreadA extends Thread {
@Override
public void run() {
long threadId = Thread.currentThread().getId();
String threadName = Thread.currentThread().getName();
System.out.println("threadId:" + threadId + ",threadName:" + threadName);
}
}
public class ThreadTest {
public static void main(String[] args) {
ThreadA threadA = new ThreadA();
threadA.setName("thread-a");
threadA.start();
}
}
运行结果:
threadId:10,threadName:thread-a
3.2.4、setPriority()
setPriority()方法的作用是设置线程的优先级,取值范围:1~ 10,与此对应的还有getPriority()方法,用于获取线程的优先级。优先级越高,拥有优先获取 CPU 执行的优势。
换句话说,当有两个线程在等待 CPU 执行时,优先级高的线程越容易被 CPU 选择执行。
样例代码如下:
public class ThreadA extends Thread {
@Override
public void run() {
String threadName = Thread.currentThread().getName();
int priority = Thread.currentThread().getPriority();
System.out.println("threadName:" + threadName + ",priority:" + priority);
}
}
public class ThreadB extends Thread {
@Override
public void run() {
String threadName = Thread.currentThread().getName();
int priority = Thread.currentThread().getPriority();
System.out.println("threadName:" + threadName + ",priority:" + priority);
}
}
public class ThreadTest {
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
ThreadA threadA = new ThreadA();
ThreadB threadB = new ThreadB();
threadA.start();
threadB.start();
}
}
}
运行结果:
threadName:Thread-0,priority:5
threadName:Thread-1,priority:5
threadName:Thread-2,priority:5
threadName:Thread-3,priority:5
threadName:Thread-4,priority:5
threadName:Thread-5,priority:5
threadName:Thread-6,priority:5
threadName:Thread-7,priority:5
threadName:Thread-8,priority:5
threadName:Thread-9,priority:5
线程默认优先级为 5,如果不手动指定,那么线程优先级具有继承性,比如线程 A 启动线程 B,那么线程 B 的优先级和线程 A 的优先级相同。
如果我们手动设置优先级,再看看结果如何。
public class ThreadTest {
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
ThreadA threadA = new ThreadA();
ThreadB threadB = new ThreadB();
threadA.setPriority(10);
threadA.start();
threadB.setPriority(1);
threadB.start();
}
}
}
运行结果:
threadName:Thread-0,priority:10
threadName:Thread-1,priority:10
threadName:Thread-2,priority:10
threadName:Thread-3,priority:10
threadName:Thread-4,priority:1
threadName:Thread-5,priority:10
threadName:Thread-6,priority:1
threadName:Thread-7,priority:1
threadName:Thread-8,priority:1
threadName:Thread-9,priority:1
将线程实例threadB的优先级调整到最高,拥有优先被 CPU 执行的优势。
在实测过程中,可能有的同学感觉效果并不明显,如果你的电脑 CPU 是多核的,线程数量较少的情况,可能会被多个 CPU 并行执行,具体执行环境取决于 CPU 。
需要特别注意的是:设置优先级只是很大程度上让某个线程尽可能获得比较多的执行机会,操作系统不能保证设置了优先级高的线程就一定会先运行或得到更多的 CPU 时间,具体执行哪一个线程,最终还是由 CPU 来决定。
另外有些 linux 操作系统是不区分优先级的,它把所有优先级都视为 5。
setPriority()方法在实际的开发中,使用的并不多见。
3.2.5、setDaemon()
在 Java 中线程分为两种,一种是用户线程,一种是守护线程。
守护线程是一种特殊的线程,它的作用是为其他线程的运行提供便利的服务,比如垃圾回收线程,就是最典型的守护线程。
当 JVM 检测到应用程序中的所有线程都只有守护线程时,它将退出应用程序,因为没有存在的必要,服务的对象都没了,当然就需要销毁了。
开发者可以通过使用setDaemon()方法,传递true作为参数,使线程成为一个守护线程,同时可以使用isDaemon()方法来检查线程是否是守护线程。
样例代码如下:
public class ThreadA extends Thread {
@Override
public void run() {
try {
while (true){
String threadName = Thread.currentThread().getName();
boolean isDaemon = Thread.currentThread().isDaemon();
System.out.println("threadName:" + threadName + ",isDaemon:" + isDaemon);
Thread.sleep(500);
}
} catch (Exception e){
e.printStackTrace();
}
}
}
public class ThreadTest {
public static void main(String[] args) throws InterruptedException {
ThreadA threadA = new ThreadA();
threadA.setDaemon(true);
threadA.start();
Thread.sleep(3000);
System.out.println("主线程方法执行完毕!");
}
}
运行结果:
threadName:Thread-0,isDaemon:true
threadName:Thread-0,isDaemon:true
threadName:Thread-0,isDaemon:true
threadName:Thread-0,isDaemon:true
threadName:Thread-0,isDaemon:true
threadName:Thread-0,isDaemon:true
主线程方法执行完毕!
需要特别注意的是:创建守护线程时,setDaemon(true)方法必须在线程start()方法之前,否则会抛异常。
3.2.6、join()
join()方法的作用是让调用此方法的主线程被阻塞,仅当该方法执行完成以后,才能继续运行。
从概念上感觉很抽象,我们看一下例子!
public class ThreadA extends Thread {
@Override
public void run() {
try {
String time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
System.out.println(time + " 当前线程:" + Thread.currentThread().getName() + ",正在运行");
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class ThreadTest {
public static void main(String[] args) throws InterruptedException {
ThreadA threadA = new ThreadA();
threadA.start();
// 让执行这个方法的线程阻塞(指的是主线程,不是threadA线程)
threadA.join();
String time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
System.out.println(time + " 主线程方法执行完毕!");
}
}
运行结果:
2023-08-31 12:46:06 当前线程:Thread-0,正在运行
2023-08-31 12:46:09 主线程方法执行完毕!
从运行结果可以得出一个结论,主线程main调用threadA.join()方法时,会进入阻塞状态,直到线程实例threadA的run()方法执行完毕,主线程main从阻塞状态变成可运行状态。
此例中主线程main会无限期阻塞直到threadA.run()方法执行完毕。
比如某个业务场景下,主线程main的执行时间是 1s,子线程的执行时间是 10s,同时主线程依赖子线程执行完的结果,此时让主线程执行join()方法进行适度阻塞,可以实现此目标。
3.2.7、interrupt()
interrupt()方法的作用是当线程受到阻塞时,调用此方法会抛出一个中断信号,让线程退出阻塞状态,如果当前线程没有阻塞,是无法中断线程的。
与此对应的还有isInterrupted()方法,用于检查线程是否已经中断,但不清除状态标识。
我们先看一个例子!
public class ThreadA extends Thread {
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
String time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS").format(new Date());
System.out.println(time + " 当前线程:" + Thread.currentThread().getName() + ",count:" + i);
}
}
}
public class ThreadTest {
public static void main(String[] args) throws InterruptedException {
ThreadA threadA = new ThreadA();
threadA.start();
Thread.sleep(50);
// 检查线程是否中断,没有尝试终止线程
if(!threadA.isInterrupted()){
threadA.interrupt();
}
}
}
运行结果:
2023-08-31 14:46:55:053 当前线程:Thread-0,count:0
2023-08-31 14:46:55:054 当前线程:Thread-0,count:1
...
2023-08-31 14:46:55:839 当前线程:Thread-0,count:9999
如果当前线程没有阻塞,调用interrupt()起不到任何效果。
下面我们对ThreadA类在尝试改造一下,让它每执行一次停顿 1 秒,内容如下:
public class ThreadA extends Thread {
@Override
public void run() {
try {
for (int i = 0; i < 10; i++) {
String time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS").format(new Date());
System.out.println(time + " 当前线程:" + Thread.currentThread().getName() + ",count:" + i);
Thread.sleep(1000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class ThreadTest {
public static void main(String[] args) throws InterruptedException {
ThreadA threadA = new ThreadA();
threadA.start();
Thread.sleep(2000);
// 检查线程是否中断,没有尝试终止线程
if(!threadA.isInterrupted()){
threadA.interrupt();
}
}
}
运行结果:
2023-08-31 14:51:19:792 当前线程:Thread-0,count:0
2023-08-31 14:51:20:798 当前线程:Thread-0,count:1
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at com.example.thread.ThreadA.run(ThreadA.java:22)
很明显,当线程处于阻塞状态时,调用interrupt()方法,可以让线程退出阻塞,起到终止线程的效果。
3.2.8、isAlive()
isAlive()方法的作用是检查线程是否处于活动状态,只要线程启动且没有终止,方法返回的就是true。
看一下例子!
public class ThreadA extends Thread {
@Override
public void run() {
System.out.println("当前线程:" + Thread.currentThread().getName() + ",isAlive:" + Thread.currentThread().isAlive());
}
}
public class ThreadTest {
public static void main(String[] args) throws InterruptedException {
ThreadA threadA = new ThreadA();
System.out.println("begin == " + threadA.isAlive());
threadA.start();
Thread.sleep(1000);
System.out.println("end == " + threadA.isAlive());
}
}
运行结果:
begin == false
当前线程:Thread-0,isAlive:true
end == false
从运行结果上可以看出,线程启动前isAlive=false,线程运行中isAlive=true,线程运行完成isAlive=false。
3.3、静态方法
在 JDK 中,Thread 类还提供了如下几个常用的静态方法来操作线程。
| 方法 | 描述 |
|---|---|
| public static Thread currentThread() | 返回对当前正在执行的线程对象的引用 |
| public static void yield() | 暂停当前正在执行的线程对象,并执行其他线程 |
| public static void sleep(long millisec) | 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响 |
| public static boolean holdsLock(Object x) | 当且仅当当前线程在指定的对象上保持监视器锁时,才返回 true |
| public static void dumpStack() | 将当前线程的堆栈跟踪打印至标准错误流 |
下面我们依次来看看它们之间的用法。
3.3.1、currentThread()
currentThread()方法的作用是返回当前正在执行线程对象的引用,在上文中有所介绍。
下面我们再来看看几个例子!
public class ThreadA extends Thread {
static {
System.out.println("静态块打印的线程名称:" + Thread.currentThread().getName());
}
public ThreadA() {
System.out.println("构造方法打印的线程名称:" + Thread.currentThread().getName());
}
@Override
public void run() {
System.out.println("run()方法打印的线程名称:" + Thread.currentThread().getName());
}
}
public class ThreadTest {
public static void main(String[] args) throws InterruptedException {
ThreadA threadA = new ThreadA();
threadA.start();
}
}
运行结果:
静态块打印的线程名称:main
构造方法打印的线程名称:main
run()方法打印的线程名称:Thread-0
从运行结果可以看出,线程类的构造方法、静态块是被主线程main调用的,而线程类的run()方法才是用户线程自己调用的。
再来看看另一个例子!
public class ThreadA extends Thread {
public ThreadA() {
System.out.println("构造方法打印 Begin...");
System.out.println("Thread.currentThread打印的线程名称:" + Thread.currentThread().getName());
System.out.println("this.getName打印的线程名称:" + this.getName());
System.out.println("构造方法打印 end...");
}
@Override
public void run() {
System.out.println("run()方法打印 Begin...");
System.out.println("Thread.currentThread打印的线程名称:" + Thread.currentThread().getName());
System.out.println("this.getName打印的线程名称:" + this.getName());
System.out.println("run()方法打印 end...");
}
}
public class ThreadTest {
public static void main(String[] args) throws InterruptedException {
ThreadA threadA = new ThreadA();
System.out.println("===============");
threadA.start();
}
}
运行结果如下:
构造方法打印 Begin...
Thread.currentThread打印的线程名称:main
this.getName打印的线程名称:Thread-0
构造方法打印 end...
===============
run()方法打印 Begin...
Thread.currentThread打印的线程名称:Thread-0
this.getName打印的线程名称:Thread-0
run()方法打印 end...
从运行结果可以看出,Thread.currentThread方法返回的未必是Thread本身,而是当前正在执行线程对象的引用,这和通过this.XXX()返回的对象是有区别的。
3.3.2、yield()
yield()方法的作用是暂停当前执行的线程对象,并执行其他线程。这个暂停会放弃 CPU 资源,并且放弃 CPU 的时间不确定,有可能刚放弃,就获得 CPU 资源了,也有可能放弃好一会儿,才会被 CPU 执行。
相关例子如下。
public class ThreadA extends Thread {
private String name;
public ThreadA(String name) {
this.name = name;
}
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(name + ":" + i);
if ("t1".equals(name)) {
System.out.println(name + ":" + i +"......yield.............");
Thread.yield();
}
}
}
}
public class ThreadTest {
public static void main(String[] args) throws InterruptedException {
ThreadA threadA1 = new ThreadA("t1");
ThreadA threadA2 = new ThreadA("t2");
threadA1.start();
threadA2.start();
}
}
运行结果:
t2:0
t1:0
t2:1
t2:2
t2:3
t2:4
t1:0......yield.............
t1:1
t1:1......yield.............
t1:2
t1:2......yield.............
t1:3
t1:3......yield.............
t1:4
t1:4......yield.............
从运行结果上可以看出,调用yield()方法可以让线程放弃 CPU 资源,循环次数越多,越明显。
3.3.3、sleep()
sleep()方法的作用是在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。这个正在执行的线程指的是Thread.currentThread()返回的线程。
根据 JDK API 的说法,该线程不丢失任何监视器的所属权,换句话说就是不会释放锁,如果sleep()代码上下文被加锁了,锁依然在,只是 CPU 资源会让出给其他线程。
相关例子如下。
public class ThreadA extends Thread {
@Override
public void run() {
try {
String begin = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS").format(new Date());
System.out.println(begin + " 当前线程:" + Thread.currentThread().getName());
Thread.sleep(3000);
String end = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS").format(new Date());
System.out.println(end + " 当前线程:" + Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class ThreadTest {
public static void main(String[] args) throws InterruptedException {
ThreadA threadA = new ThreadA();
threadA.start();
}
}
运行结果如下:
2023-08-31 18:06:41:459 当前线程:Thread-0
2023-08-31 18:06:44:464 当前线程:Thread-0
3.3.4、holdsLock()
holdsLock()方法表示当且仅当当前线程在指定的对象上保持监视器锁时,才返回 true,简单的说就是检测一个线程是否拥有锁。
相关例子如下。
public class ThreadA extends Thread {
private String lock = "lock";
@Override
public void run() {
System.out.println("当前线程:" + Thread.currentThread().getName() + ",Holds Lock = " + Thread.holdsLock(lock));
synchronized (lock){
System.out.println("当前线程:" + Thread.currentThread().getName() + ",Holds Lock = " + Thread.holdsLock(lock));
}
}
}
public class ThreadTest {
public static void main(String[] args) throws InterruptedException {
ThreadA threadA = new ThreadA();
threadA.start();
}
}
运行结果如下:
当前线程:Thread-0,Holds Lock = false
当前线程:Thread-0,Holds Lock = true
关于线程锁,我们会在后期的文章中进行分享介绍。
3.3.5、dumpStack()
dumpStack()方法的作用是将当前线程的堆栈跟踪打印至标准错误流。此方法仅用于调试。
相关例子如下。
public class ThreadA extends Thread {
@Override
public void run() {
System.out.println("当前线程:" + Thread.currentThread().getName());
Thread.dumpStack();
}
}
public class ThreadTest {
public static void main(String[] args) throws InterruptedException {
ThreadA threadA = new ThreadA();
threadA.start();
}
}
运行结果如下:
当前线程:Thread-0
java.lang.Exception: Stack trace
at java.lang.Thread.dumpStack(Thread.java:1336)
at com.example.thread.ThreadA.run(ThreadA.java:16)
Thread.dumpStack会将当前线程的堆栈跟踪信息打印出控制台。
四、小结
本文主要围绕线程类Thread相关的常用方法进行详解,内容难免有所遗漏,欢迎网友留言指出。
五、参考
多线程系列(二) -Thread类使用详解的更多相关文章
- Java多线程系列二——Thread类的方法
Thread实现Runnable接口并实现了大量实用的方法 public static native void yield(); 此方法释放CPU,但并不释放已获得的锁,其它就绪的线程将可能得到执行机 ...
- Velocity魔法堂系列二:VTL语法详解
一.前言 Velocity作为历史悠久的模板引擎不单单可以替代JSP作为Java Web的服务端网页模板引擎,而且可以作为普通文本的模板引擎来增强服务端程序文本处理能力.而且Velocity被移植到不 ...
- Zookeeper系列二:分布式架构详解、分布式技术详解、分布式事务
一.分布式架构详解 1.分布式发展历程 1.1 单点集中式 特点:App.DB.FileServer都部署在一台机器上.并且访问请求量较少 1.2 应用服务和数据服务拆分 特点:App.DB.Fi ...
- Solr系列二:solr-部署详解(solr两种部署模式介绍、独立服务器模式详解、SolrCloud分布式集群模式详解)
一.solr两种部署模式介绍 Standalone Server 独立服务器模式:适用于数据规模不大的场景 SolrCloud 分布式集群模式:适用于数据规模大,高可靠.高可用.高并发的场景 二.独 ...
- Maven系列二setting.xml 配置详解
文件存放位置 全局配置: ${M2_HOME}/conf/settings.xml 用户配置: ${user.home}/.m2/settings.xml note:用户配置优先于全局配置.${use ...
- Python入门之面向对象编程(二)python类的详解
本文通过创建几个类来覆盖python中类的基础知识,主要有如下几个类 Animal :各种属性.方法以及属性的修改 Dog :将方法转化为属性并操作的方法 Cat :私人属性讲解,方法的继承与覆盖 T ...
- Java 虚拟机系列二:垃圾收集机制详解,动图帮你理解
前言 上篇文章已经给大家介绍了 JVM 的架构和运行时数据区 (内存区域),本篇文章将给大家介绍 JVM 的重点内容--垃圾收集.众所周知,相比 C / C++ 等语言,Java 可以省去手动管理内存 ...
- 《手把手教你》系列基础篇(九十七)-java+ selenium自动化测试-框架设计篇-Selenium方法的二次封装和页面基类(详解教程)
1.简介 上一篇宏哥介绍了如何设计支持不同浏览器测试,宏哥的方法就是通过来切换配置文件设置的浏览器名称的值,来确定启动什么浏览器进行脚本测试.宏哥将这个叫做浏览器引擎类.这个类负责获取浏览器类型和启动 ...
- C#多线程详解(一) Thread.Join()的详解
bicabo C#多线程详解(一) Thread.Join()的详解 什么是进程?当一个程序开始运行时,它就是一个进程,进程包括运行中的程序和程序所使用到的内存和系统资源.而一个进程又是由多个线程 ...
- MP实战系列(十二)之封装方法详解(续二)
继续MP实战系列(十一)之封装方法详解(续一)这篇文章之后. 此次要讲的是关于查询. 查询是用的比较多的,查询很重要,好的查询,加上索引如鱼得水,不好的查询加再多索引也是无济于事. 1.selectB ...
随机推荐
- Shell 脚本编程学习
本文为博主原创,转载请注明出处: 目录: 1. shell 变量 2. 运算符 3. if 语句 4.for 循环 5.while 语句 6. case 语法 7.跳出循环:continue 与 br ...
- zookeeper源码(03)启动流程
本文将从启动类开始详细分析zookeeper的启动流程: 加载配置的过程 集群启动过程 单机版启动过程 启动类 org.apache.zookeeper.server.quorum.QuorumPee ...
- 他凌晨1:30给我开源的游戏加了UI|模拟龙生,挂机冒险
一.前言 新年就要到了,祝大家新的一年: 龙行龘龘, 前程朤朤! 白泽花了点时间,用 800 行 Go 代码写了一个控制台的小游戏:<模拟龙生>,在游戏中你将模拟一条新生的巨龙,开始无尽的 ...
- 【css】 text-align 居中导航
原理 :利用 inline-block 将 导航 作为 文本 , 被外层具有 text-align 属性的导航盒子包含 .从而实现居中效果 1. html 结构 <header> < ...
- Jrebel与Xrebel教学
简介 JRebel和XRebel是两个非常有用的工具,可以显著提升Java开发人员的生产力和应用程序性能. JRebel是一个强大的Java开发工具,它允许开发人员在不重新启动应用程序的情况下进行代码 ...
- [转帖]加速拥抱支持开源生态 | OceanBase 开源版3.1.1正式发布
https://www.oceanbase.com/news/accelerated-embrace-and-support-of-open-source-ecosystem-oceanbase-op ...
- [转帖]PostgreSQL数据库的版本历史及关键变化
https://cloud.tencent.com/developer/article/2311843 举报 PostgreSQL是一个强大的开源关系型数据库,它的发展历程充满了创新和卓越的设计.让我 ...
- [转帖]JVM 输出 GC 日志导致 JVM 卡住,我 TM 人傻了
https://www.jianshu.com/p/51380e04eab1 最近,我们升级了 Java 17.后来,我们的 k8s 运维团队为了优化我们的应用日志采集, 将我们所有 pod (你可以 ...
- [转帖]RHEL/CentOS 7的systemd target及其中的multi-user.target
在RHEL/CentOS 6中,使用SysV init和Upstart,通过预定义一组Runlevels(从0到6)表示不同的执行模式. [root@myhost app]# ll /etc/rc.d ...
- [转帖] TiDB 产品体系介绍
https://www.modb.pro/db/521269#:~:text=%E4%BC%81%E4%B8%9A%E7%89%88%E5%92%8C%E7%A4%BE%E5%8C%BA%E7%89% ...