java 线程的常用操作基本上都在java.lang.Thread 类中进行了定义,基础的操作可以下图进行概括:

接下来我们对每一项基本操作进行详细说明:
在java编程规范中要求: 创建线程或线程池时,需要指定有意义的线程名称,方便出现bug的时候回溯.
在Thread 中可以通过两种方式设置线程名称,方式一是通过构造函数Thread(…)初始化设置线程名称,方式二是通过 setName()方法设置线程名称. 想要获取线程名称就只能通过getName()方法进行获取.
设置线程名称和获取线程名称的示例如下
package com.th.thread;
import com.th.util.ThreadUtil;
/**
* @ClassName: ThreadNameDemo
* @Description:
* @Author: 唐欢
* @Date: 2022/8/15 14:29
* @Version 1.0
*/
public class ThreadNameDemo {
//每个线程的执行轮次
private static final int MAX_TURN =3;
// 异步执行目标类
static class RunTarget implements Runnable{
//实现Runnable接口
@Override
public void run() {
//实现run()方法
for (int turn=0 ;turn运行结果如下:

在设置线程名称的时候,需要注意一下三点:
sleep 的作用是让当前正在执行的线程休眠,让CPU 去执行其他的任务, 从线程状态说,就是从执行状态变成限时阻塞状态.sleep()方法会抛出InterruptExcetion 异常, sleep()示例如下:
package com.th.thread;
import com.th.util.ThreadUtil;
/**
* @ClassName: SleepDemo
* @Description:
* @Author: 唐欢
* @Date: 2022/8/15 15:27
* @Version 1.0
*/
public class SleepDemo {
//睡眠时长5秒
public static final int SLEEP_GAP =5000;
//睡眠次数,值稍微大些方便使用jstack
public static final int MAX_TURN =50;
static class SleepThread extends Thread{
static int threadSeqNumber =1;
public SleepThread(){
super("sleepThread-"+threadSeqNumber);
threadSeqNumber++;
}
@Override
public void run() {
try{
for (int i=1;i运行结果如下:

使用jstack 查看运行情况如下:

通过jstack指令输出可以看到在进行线程DUMP 的时间点,所创建的线程都处于TIMED_WAITING(sleeping)状态.
总结:当线程sleep时间结束后,不一定会立即得到执行, 因为此时CPU 有可能正在执行其他的任务,此时sleep结束的线程就进入就绪状态,等待分配CPU时间片以便有机会执行.
Thread 的interrupt()方法的作用是将线程设置为中断状态.在以下两种情况下调用interrupt() 进行中断:
示例如下:
package com.th.thread;
import com.th.util.ThreadUtil;
/**
* @ClassName: InterruptDemo
* @Description:
* @Author: 唐欢
* @Date: 2022/8/16 15:34
* @Version 1.0
*/
public class InterruptDemo {
// 睡眠时长
public static final int SLEEP_GAR =5000;
//睡眠次数
public static final int MAX_TURN =50;
static class SleepThread extends Thread{
static int threadSeqNumber=1;
public SleepThread(){
super("sleepThread" + threadSeqNumber);
threadSeqNumber++;
}
@Override
public void run() {
System.out.println("["+Thread.currentThread().getName()+"] :"+getName()+"进入睡眠");
// 线程sleep会
try {
Thread.sleep(SLEEP_GAR);
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println("["+Thread.currentThread().getName()+"] :"+getName()+"发生被异常打断");
return;
}
System.out.println("["+Thread.currentThread().getName()+"] :"+getName()+"运行结束");
}
}
public static void main(String[] args) {
Thread thread1 = new SleepThread();
thread1.start();
Thread thread2 = new SleepThread();
thread2.start();
// 主线程等待2秒
ThreadUtil.sleepSeconds(2);
//打断线程1
thread1.interrupt();
// 主线程等待5秒
ThreadUtil.sleepSeconds(5);
//打断线程2,此时线程2已经终止
thread2.interrupt();
// 主线程等待1秒
ThreadUtil.sleepSeconds(1);
//打断线程2,此时线程2已经终止
System.out.println("["+Thread.currentThread().getName()+"] :"+"程序运行结束");
}
}
运行结果如下:

从运行结果可以看出,sleepThread1 大致sleep了2s后,被主线程打断,被打断的sleepThread1线程停止睡眠,并捕获到了InterruptException异常并抛出. sleepThread2 线程在sleep 7s后, 被主线程中断, 但是由于sleepThread2在被中断的时候已经执行结束了,所有对sleepThread2 没有产生实际性的影响.
调用join()方法为Thread 线程合并,合并的本质是线程A 需要在合并点等待,一直等到线程B执行完成,或者等待超时. 举个例子, 依赖的线程A 调用被依赖的线程B的join()方法, 在执行流程上将被依赖的线程B 合并到依赖线程A,依赖线程A 等待被依赖线程B 执行完成后,依赖线程A 再继续执行.执行流程如下图所示:

调用join()方法示例:
package com.th.thread;
/**
* @ClassName: JoinDemo
* @Description:
* @Author: 唐欢
* @Date: 2022/8/16 16:37
* @Version 1.0
*/
public class JoinDemo {
// 睡眠时长
public static final int SLEEP_GAR =5000;
//睡眠次数
public static final int MAX_TURN =50;
static class SleepThread extends Thread {
static int threadSeqNumber = 1;
public SleepThread() {
super("sleepThread" + threadSeqNumber);
threadSeqNumber++;
}
@Override
public void run() {
System.out.println("["+Thread.currentThread().getName()+"] :"+getName()+"进入睡眠");
// 线程sleep会
try {
Thread.sleep(SLEEP_GAR);
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println("["+Thread.currentThread().getName()+"] :"+getName()+"发生被异常打断");
return;
}
System.out.println("["+Thread.currentThread().getName()+"] :"+getName()+"运行结束");
}
}
public static void main(String[] args) {
Thread threadA =new SleepThread();
System.out.println("["+Thread.currentThread().getName()+"] :"+"启动threadA");
threadA.start();
try {
//合并线程A,不限时
threadA.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("["+Thread.currentThread().getName()+"] :"+"启动threadB");
// 启动第二天线程,并且进行限时合并,等待时间为1秒
Thread threadB = new SleepThread();
threadB.start();
try {
//合并线程B,不限时
threadB.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("["+Thread.currentThread().getName()+"] :"+"线程运行结束");
}
}
执行结果:

yield()方法的作用是让目前正在执行的线程放弃当前的执行,让出cpu的执行权限,使得cpu去执行其他的线程.处于让步状态的JVM 层面的线程状态仍然是RUNNABLE状态,从执行状态变成了就绪状态.示例如下:
package com.th.thread;
import com.th.util.Print;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.LockSupport;
/**
* @ClassName: YieldDemo
* @Description:
* @Author: 唐欢
* @Date: 2022/8/16 17:03
* @Version 1.0
*/
public class YieldDemo {
//执行次数
public static final int MAX_TURN = 100;
//执行编号
public static AtomicInteger index = new AtomicInteger(0);
// 记录线程的执行次数
private static Map metric = new HashMap<>();
//输出线程的执行次数
private static void printMetric() {
System.out.println("["+Thread.currentThread().getName()+"] :"+"metric = " + metric);
}
static class YieldThread extends Thread {
static int threadSeqNumber = 1;
public YieldThread() {
super("YieldThread-" + threadSeqNumber);
threadSeqNumber++;
metric.put(this.getName(), new AtomicInteger(0));
}
@Override
public void run() {
for (int i = 1; i < MAX_TURN && index.get() < MAX_TURN; i++) {
System.out.println("["+Thread.currentThread().getName()+"] :"+"线程优先级:" + getPriority());
index.incrementAndGet();
metric.get(this.getName()).incrementAndGet();
if (i % 2 == 0) {
//让步:出让执行的权限
Thread.yield();
}
}
//输出线程的执行次数
printMetric();
System.out.println("["+Thread.currentThread().getName()+"] :"+getName() + " 运行结束.");
}
}
public static void main(String args[]) {
Thread thread1 = new YieldThread();
thread1.setPriority(Thread.MAX_PRIORITY);
Thread thread2 = new YieldThread();
thread2.setPriority(Thread.MIN_PRIORITY);
Print.tco("启动线程.");
System.out.println("["+Thread.currentThread().getName()+"] :"+"启动线程.");
thread1.start();
thread2.start();
LockSupport.parkNanos(100*1000L*1000L*1000L);
}
}
执行结果如下:

从结果可以看出,程序一共启动了两个让步演示线程, 两个线程妹执行两次操作就让出cpu.但是两个线程的的优先级不同, 优先级高的执行次数比优先级低的执行次数多很多. 从而可得出,线程调用yield之后,操作系统在重新进行线程调度时偏向于执行机会让给优先级较高的线程.
总结:
java线程分为守护线程(后台线程)和用户线程, 在开发中, 实例属性daemon =false ,默认情况是用户线程,可以通过调用setDaemon()方法设置,true是守护线程, false则为用户线程, 还可以通过isDaemon 获取线程的守护状态.示例如下:
package com.th.thread;
import com.th.util.Print;
import java.util.concurrent.locks.LockSupport;
/**
* @ClassName: DaemonDemo
* @Description:
* @Author: 唐欢
* @Date: 2022/8/16 17:14
* @Version 1.0
*/
public class DaemonDemo {
public static final int SLEEP_GAP = 500; //每一轮的睡眠时长
public static final int MAX_TURN = 4; //用户线程执行轮次
//守护线程实现类
static class DaemonThread extends Thread {
public DaemonThread() {
super("daemonThread");
}
@Override
public void run() {
System.out.println("[" + Thread.currentThread().getName() + "]" + ":" +"--daemon线程开始.");
for (int i = 1; ; i++) {
System.out.println("[" + Thread.currentThread().getName() + "]" + ":" +"--轮次:" + i + "--守护状态为:" + isDaemon());
// 线程睡眠一会
LockSupport.parkNanos(SLEEP_GAP * 1000L * 1000L);
}
}
}
public static void main(String args[]) throws InterruptedException {
Thread daemonThread = new DaemonThread();
daemonThread.setDaemon(true);
daemonThread.start();
Thread userThread = new Thread(() ->
{
System.out.println("[" + Thread.currentThread().getName() + "]" + ":" +">>用户线程开始.");
for (int i = 1; i <= MAX_TURN; i++) {
System.out.println("[" + Thread.currentThread().getName() + "]" + ":" +">>轮次:" + i + " -守护状态为:" + Thread.currentThread().isDaemon());
LockSupport.parkNanos(SLEEP_GAP * 1000L * 1000L);
}
System.out.println("[" + Thread.currentThread().getName() + "]" + ":" +">>用户线程结束.");
}, "userThread");
userThread.start();
//主线程合入userThread,等待userThread执行完成
// userThread.join();
System.out.println("[" + Thread.currentThread().getName() + "]" + ":" +" 守护状态为:" + Thread.currentThread().isDaemon());
System.out.println("[" + Thread.currentThread().getName() + "]" + ":" +"运行结束.");
}
}
运行结果如下:

从运行的结果可以看出: main 线程也是一条用户线程.main 线程在创建和启动了daemonThread 和userThread 后,就提前结束了,虽然main线程结束了,但是两条线程还在继续执行,其中就有一条是用户线程,所以进程还不能结束. 当剩下的一条用户线程userThread的run()方法执行完成后,userThread 线程执行结束.这是,所有用户线程执行已经完成,jvm进程就随之退出了. 在JVM退出时,守护线程daemonThread 还没有结束,还在继续死循环的执行中,而jvm并不管这些,强行终止了所有守护线程的执行.
从以上的示例我们可以得出这样一个结论: 用户线程和JVM 进程是主动关系,如果用户线程全部终止,JVM虚拟机进程也随之终止; 守护线程和JVM进程是被动关系,如果JVM进程终止,所有的守护线程也随之终止,下图清晰的表达了它们之间的关系:

在开发过程中, 使用守护线程需要特别注意一下几点:
到此为止, Thread线程的的基本操作就介绍完成了,你学会了吗?