4. Java线程的一些基本信息:名称、状态、join、yield等

1. 线程名称

线程的名称

  1. 线程名称一般在启动前设置,但也允许为运行的线程设置名称
  2. 线程名称不能为null
  3. 允许两个线程有相同的名称,但是应该避免这种情况
  4. 如果没有为线程指定名称,系统会自动为线程设置名称,名称为Thread-编号的形式
  5. 为线程指定名称的方法有:
    1. 通过构造方法
    2. 通过setName()方法

接下来看一下部分源码:
6. 不指定名称的时候,构造方法会自动指定名称

/** 内部调用了四个参数的构造方法,
	第一个:指定group
	第二个:指定target
	第三个:线程名称
	第四个:指定stackSize
*/
public Thread() {
    this(null, null, "Thread-" + nextThreadNum(), 0);
}

public Thread(Runnable target) {
    this(null, target, "Thread-" + nextThreadNum(), 0);
}

//获取下一个编号
private static synchronized int nextThreadNum() {
    return threadInitNumber++;
}
  1. 可以设置线程名称的方法,设置前会对name进行null判断
// 其他设置name的构造方法都是通过这个构造方法设置的name属性
private Thread(ThreadGroup g, Runnable target, String name,
               long stackSize, AccessControlContext acc,
               boolean inheritThreadLocals) {
    // 如果name是null,抛出异常
    if (name == null) {
        throw new NullPointerException("name cannot be null");
    }
    this.name = name;
    ...
}

// set方法设置name
// 设置前会进行null判断,然后为线程设置name,如果线程正在运行,还会设置底层线程的name
public final synchronized void setName(String name) {
    checkAccess();
    if (name == null) {
        throw new NullPointerException("name cannot be null");
    }
    this.name = name;
    if (threadStatus != 0) {
        setNativeName(name);
    }
}

注意:
创建线程或线程池的时候,需要指定有意义的名称,方便出错的时候进行回溯,查找原因。

2. 线程的sleep操作

让本线程进行休眠,让CPU去执行其他的线程。
从线程状态来说,就是从RUNNABLE 变成了 TIMED_WAITING
可以使用jstack 工具查看线程的相关信息。

例如:执行如下代码:

public class Test {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            try {
                Thread.sleep(100000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "A线程");
        thread.start();
        // 等待异步线程启动
        Thread.sleep(10);
        System.out.println(thread.getState());
    }
}

在程序运行过程中,可以使用命令查看线程的状态,当然,首先需要获取到对应的JVM进程的id。可以使用命令 jps 查看程序运行时候的id。
例如:

D:\Even\jdk-17.0.1\bin>jps
9648 Jps
10468 Test
8484 Launcher
10232

根据结果可以看到,Test的进程id是 10468
然后根据进程id使用jstack 查看线程的信息

jstack是jdk自带的一个工具

省略部分信息后输出如下:

"A线程" #15 prio=5 os_prio=0 cpu=0.00ms elapsed=14.07s tid=0x00000280ffd46000 nid=0x2420 waiting on condition  [0x00000081808ff000]
   java.lang.Thread.State: TIMED_WAITING (sleeping)
        at java.lang.Thread.sleep(java.base@17.0.1/Native Method)
        at com.wang.thread.Test.lambda$main$0(Test.java:7)
        at com.wang.thread.Test$$Lambda$14/0x0000000800c01200.run(Unknown Source)
        at java.lang.Thread.run(java.base@17.0.1/Thread.java:833)

可以看到线程的状态为:TIMED_WAITING (sleeping)


3. interrupt操作

为什么不推荐使用线程的stop()方法?
因为 stop() 方法会将线程强制结束,不管线程现在是什么状态。
但是如果线程正持有某把锁,就可能导致锁无法释放。在操作数据库就可能导致数据库状态不一致等情况。所以不推荐使用stop() 方法

interrupt()方法只是将线程标记成为了中断状态,并不会停止线程的运行。
当调用interrupt()方法的时候,有两个作用:

  1. 如果线程处于阻塞状态,调用这个方法会退出阻塞,并且抛出InterruptedException异常
  2. 如果线程处于运行中,则会继续运行,仅仅是中断标记被置为true,程序可以使用isInterrupted()方法检查自己的中断标记是否为true,以此判断自己是否被中断,并执行推出操作。

线程睡眠时被打断代码:

public class TestInterrupt {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            try {
                Thread.sleep(10000);
                System.out.println("异步线程执行完成!");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        thread.start();
        Thread.sleep(100);
        thread.interrupt();
        System.out.println("主线程执行完毕");
    }
}

运行结果:

主线程执行完毕
java.lang.InterruptedException: sleep interrupted
	at java.base/java.lang.Thread.sleep(Native Method)
	at com.wang.thread.TestInterrupt.lambda$main$0(TestInterrupt.java:7)
	at java.base/java.lang.Thread.run(Thread.java:833)

从运行结果中可以看出,线程在阻塞时被打断之后,阻塞之后的代码不会被执行,异步线程会被结束。

若是正常执行的代码,不会被强行结束,测试代码:

public class TestInterrupt {
    public static void main(String[] args) throws InterruptedException {
		Thread thread = new Thread(() -> {
            for (int i = 0; i < Integer.MAX_VALUE; i++) {
                System.out.println(i);
            }
        });
        thread.start();
        Thread.sleep(10);
        // 线程正常运行时,不会被中断,不受影响
        thread.interrupt();
        System.out.println("主线程执行完毕");
    }
}

运行结果是:程序会将等待所有的结果输出之后再结束。

程序中使用isInterrupted()可以检查线程是否被中断了,如果被中断执行退出操作。
例如:

Thread thread = new Thread(() -> {
    for (int i = 0; i < Integer.MAX_VALUE; i++) {
        System.out.println(i);
        // 检查线程是否被中断,若被中断,则执行退出操作
        if (Thread.currentThread().isInterrupted()) {
            System.out.println("异步线程被中断,将会退出");
            return;
        }
    }
});

当线程被中断的时候,将会执行if() 中的操作,执行退出操作


4. join操作

假设当前线程叫做“A线程”,A线程对B线程执行join操作。也就是在A线程的代码块中调用b.join()。可以理解成:A线程执行完 b.join() 之后就会进入 WAITING 状态,等待B线程执行完毕之后,A线程才会恢复成RUNNABLE状态,然后继续执行。
也可以使用 join(final long millis)join(long millis, int nanos) 指定等待时间,如果B线程在等待时间内没有执行完成,A线程会在等待时间到达之后,继续向下执行。

可以查看下join的部分源码,可以发现join操作其实是通过wait方法实现等待的。
例如:

// join是一个同步方法
public final synchronized void join(final long millis)
throws InterruptedException {
    if (millis > 0) {
        if (isAlive()) {
            final long startTime = System.nanoTime();
            long delay = millis;
            do {
                // 调用wait方法进行等待
                wait(delay);
            } while (isAlive() && (delay = millis -
                    TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime)) > 0);
        }
    } else if (millis == 0) {
        while (isAlive()) {
            // 调用wait方法进行等待
            wait(0);
        }
    } else {
        throw new IllegalArgumentException("timeout value is negative");
    }
}

所以使用join方法导致的阻塞,在阻塞期间被interrupt()也会抛出InterruptedException异常。


5. yield操作

yield操作是让当前线程让出CPU时间片的执行权,让操作系统重新调度一次线程。
yield操作并不会让当前线程进入阻塞状态,而是会进入就绪状态,在Thread的状态中,仍然是RUNNABLE状态。

总结如下:

  1. yield操作并不会阻塞线程
  2. yield操作并不能保证当前线程立即让出时间片,切换到就绪状态
  3. yield操作完成之后,线程会对CPU时间片再次进行抢占,刚yield的线程也会参与抢占

6. daemon操作

Java中的线程分为两种:用户线程守护线程
在Thread类中,使用属性 daemon标记是否是守护线程

// 是否是守护线程,默认不是守护线程
// 不过在创建线程时,会根据当前线程的daemon状态设置本线程的状态. 可以在构造方法中看到
private boolean daemon = false;

private Thread(ThreadGroup g, Runnable target, String name,
                   long stackSize, AccessControlContext acc,
                   boolean inheritThreadLocals) {
    // 省略了部分代码
	Thread parent = currentThread();
	this.daemon = parent.isDaemon();
}

可以通过setDaemon(boolean on)方法设置deamon的状态,使用isDaemon()获取daemon的状态。

守护线程与用户线程

守护线程是在后台默默工作的,JVM进程关闭后,所有的守护线程都会被关闭。
用户线程是主要的线程,如果全部的用户线程都运行完毕了,JVM进程也会随之关闭。

JVM进程会等待用户线程的执行,只要还有一个用户线程没有执行完毕,理论上JVM就不会关闭。JVM关闭的时候,所有的守护线程都会被关闭,不管守护线程是否运行结束。

要点

  1. 线程是否是守护线程,需要在线程启动前进行设置,否则会抛出InterruptedException
  2. 守护线程存在被JVM虚拟机强制终止的风险,所以守护线程中尽量不要操作一些系统资源
  3. 用户线程创建的线程依然是用户线程,守护线程创建的线程依然是守护线程

7. 线程状态总结

  • NEW:线程被创建出来了,但是还没有执行start()
  • RUNNABLE:正在运行或可以运行的时候
    • 调用start()方法
    • sleep()结束后
    • join()结束后
    • 等待用户输入结束后
    • 抢到对象锁之后
  • BLOCKED:等待获取锁或IO阻塞的时候成为BLOCKED状态
  • WAITING:无限时的等待的时候,成为WAITING状态
    • Object.wait() 方法,使用Object.notify()Object.notifyAll()唤醒
    • Thread.join() 方法,被合并的线程执行完毕之后唤醒
    • LockSupport.park() 方法,使用LockSupport.unpark(Thread)唤醒
  • TIMED_WAITING:限时等待的时候成为TIMED_WAITING状态
    • Thread.sleep(time) 方法,睡眠时间结束后唤醒
    • Object.wait(time) 方法,使用Object.notify()Object.notifyAll()主动唤醒,或等待时间到达指定的等待时间
    • LockSupport.parkNanos(time)LockSupport.parkUntil(time)方法,使用LockSupport.unpark(Thread)方法唤醒,或到线程停止时限结束
  • TERMINATED:线程任务执行结束或出现异常停止,变成TERMINATED状态
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值