多线程系列(二) -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 ...
随机推荐
- docker 安装 ElasticSearch 和 Kibana 及ik 中文分词器
本文为博主原创,未经允许不得转载: 1. 使用 docker 下载 elasticsearch 7.6.1 docker pull elasticsearch:7.6.1 2. 启动 elastic ...
- 通过设置 Chrome 解决开发调用跨域问题
转载请注明出处: 项目采用的是前后端分离的方式,前端本地访问方式是 localhost:8080,访问本地后台服务时,通过 localhost:9000 进行访问 本地后端服务.在本地通过chrome ...
- B2033 A*B 问题
A*B 问题 题目描述 输入两个正整数 \(A\) 和 \(B\),求 \(A \times B\) 的值.注意乘积的范围和数据类型的选择. 输入格式 一行,包含两个正整数 \(A\) 和 \(B\) ...
- [转帖]RAC环境下误操作将数据文件添加到本地存储
https://www.cnblogs.com/jyzhao/p/7986729.html 今天碰到个有意思的事情,有客户在Oracle RAC环境,误操作将新增的数据文件直接创建到了其中一个节点的本 ...
- [转帖]oracle 11.2.0.4 rac集群等待事件enq: TM - contention
近期,一金融客户oracle 11.2.0.4 rac集群delete不当导致等待事件enq: TM - contention严重引起大范围会话堆积,记录的相关分析工作如下. 1.登录集群任意节点,查 ...
- [转帖]备份与恢复工具 BR 简介
https://docs.pingcap.com/zh/tidb/v4.0/backup-and-restore-tool BR 全称为 Backup & Restore,是 TiDB 分布式 ...
- [转帖]【基础】HTTP、TCP/IP 协议的原理及应用
https://juejin.cn/post/6844903938232156167 前言 本文将持续记录笔者在学习过程中掌握的一些 HTTP .TCP/IP 的原理,以及这些网络通信技术的一些应用场 ...
- [转帖]yum 下载全量依赖 rpm 包及离线安装(终极解决方案)
简介 通常生产环境由于安全原因都无法访问互联网.此时就需要进行离线安装,主要有两种方式:源码编译.rpm包安装.源码编译耗费时间长且缺乏编译环境,所以一般都选择使用离线 rpm 包安装. 验证环境 C ...
- iptables 命令学习
iptables 命令学习 摘要 Linux 早起版本使用netfilter进行数据包过滤. 最新的版本开始改用 ebpf的方式进行内核编程式的包过滤. netfilter 可以理解为内核态的一个处理 ...
- Spring Boot接口设计
项目文件结构 编写示例代码 添加lombok的依赖 新建DemoController,用于提供RESTful接口.增加相关注解:@RestController,@RequestMapping(&quo ...