美文网首页
JAVA 多线程与高并发学习笔记(二)——线程基本操作

JAVA 多线程与高并发学习笔记(二)——线程基本操作

作者: 简单一点点 | 来源:发表于2022-06-20 20:34 被阅读0次

    Java 的线程类 Thread 定义了线程一些重要的操作方法。

    线程的 sleep 操作

    sleep 的作用是让当前正在执行的线程休眠,让 CPU 去执行其它的任务。线程状态会进入阻塞状态。sleep 方法是静态方法,包括:

    public static void sleep(long millis) throws InterruptException;
    
    public static void sleep(long millis, int nanos) throws InterruptException;
    

    下面看一个例子:

    static class SleepThread extends Thread {
            static int threadSeqNo = 1;
    
            public SleepThread() {
                super("SleepThread-" + threadSeqNo);
                threadSeqNo++;
            }
    
            public void run() {
                try {
                    for(int i = 0; i < 50; i++) {
                        System.out.println(Thread.currentThread().getName() + ", 睡眠轮次: " + i);
                        Thread.sleep(5000);
                    }
                } catch (InterruptedException e) {
                    System.out.println(Thread.currentThread().getName() + "发生异常中断。");
                }
                System.out.println(Thread.currentThread().getName() + "运行结束。");
            }
        }
    
        public static void main(String[] args) {
            for(int i = 0; i < 5; i++) {
                Thread thread = new SleepThread();
                thread.start();
            }
            System.out.println(Thread.currentThread().getName() + "运行结束。");
        }
    }
    

    运行以上程序,使用 Jstack 查看线程状态。

    PS D:\MyLanguage\java> jps
    16896
    6048 SleepDemo
    17732 Launcher
    7004 Jps
    PS D:\MyLanguage\java> jstack 6048
    2022-06-18 22:43:02
    Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.60-b23 mixed mode):
    
    "DestroyJavaVM" #17 prio=5 os_prio=0 tid=0x0000000003282800 nid=0x41cc waiting on condition [0x0000000000000000]
       java.lang.Thread.State: RUNNABLE
    
    "SleepThread-5" #16 prio=5 os_prio=0 tid=0x000000000f725000 nid=0x4f98 waiting on condition [0x00000000207bf000]
       java.lang.Thread.State: TIMED_WAITING (sleeping)
            at java.lang.Thread.sleep(Native Method)
            at SleepDemo$SleepThread.run(SleepDemo.java:15)
    
    "SleepThread-4" #15 prio=5 os_prio=0 tid=0x000000000f722800 nid=0x2e4c waiting on condition [0x00000000206bf000]
       java.lang.Thread.State: TIMED_WAITING (sleeping)
            at java.lang.Thread.sleep(Native Method)
            at SleepDemo$SleepThread.run(SleepDemo.java:15)
    
    "SleepThread-3" #14 prio=5 os_prio=0 tid=0x000000000f721000 nid=0x4e18 waiting on condition [0x00000000205bf000]
       java.lang.Thread.State: TIMED_WAITING (sleeping)
            at java.lang.Thread.sleep(Native Method)
            at SleepDemo$SleepThread.run(SleepDemo.java:15)
    
    "SleepThread-2" #13 prio=5 os_prio=0 tid=0x000000000f71c000 nid=0x4764 waiting on condition [0x00000000204be000]
       java.lang.Thread.State: TIMED_WAITING (sleeping)
            at java.lang.Thread.sleep(Native Method)
            at SleepDemo$SleepThread.run(SleepDemo.java:15)
    
    "SleepThread-1" #12 prio=5 os_prio=0 tid=0x000000000f71b800 nid=0x2c4c waiting on condition [0x00000000203be000]
       java.lang.Thread.State: TIMED_WAITING (sleeping)
            at java.lang.Thread.sleep(Native Method)
            at SleepDemo$SleepThread.run(SleepDemo.java:15)
    ...
    

    可以看到创建的 5 个线程都在睡眠状态。

    线程的 interrupt 操作

    Java 提供了 stop 方法终止正在运行的线程,但是这个方法不被建议使用,因为中断一个线程是很危险的行为。

    推荐的方式是使用 interrupt 方法,此方法的本质不是中断一个线程,而是将线程设置为中断状态。它的作用:

    • 如果此线程处于阻塞状态,那么会立刻退出阻塞(一般是 Object.wait()Thread.join()Thread.sleep()三种方法之一),并抛出 InterruptException 异常。
    • 如果此线程正处于运行状态,线程就不受任何影响,继续运行,仅仅是将线程的中断标记设置为 true。程序可以通过 isInterrupted() 方法查看自己是否应该中断并执行退出操作。

    下面看个例子。

    public class InterruptDemo {
    
        static class SleepThread extends Thread {
            static int threadNo = 1;
            public SleepThread() {
                super("sleepThread-" + threadNo);
                threadNo++;
            }
    
            public void run() {
                try {
                    System.out.println(Thread.currentThread().getName() + " 进入睡眠.");
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    System.out.println(Thread.currentThread().getName() + " 发生被异常打断.");
                    return;
                }
                System.out.println(Thread.currentThread().getName() + " 运行结束.");
            }
        }
    
        public static void main(String[] args) throws InterruptedException {
            Thread thread1 = new SleepThread();
            thread1.start();
            Thread thread2 = new SleepThread();
            thread2.start();
    
            Thread.sleep(2000);
            thread1.interrupt();
    
            Thread.sleep(5000);
            thread2.interrupt();
    
            Thread.sleep(1000);
            System.out.println("程序运行结束.");
        }
    
    }
    

    输出如下:

    thread-interrupt.png

    线程的 join 操作

    合并(join)比较难以解释,一般是主线程等待子线程执行完成之后在运行主线程。

    Threadjoin 实例方法有3个版本:

    // 将当前线程变为 TIME_WAITING,直到被合并线程执行结束
    public final void join() throw InterruptedException;
    
    // 将当前线程变为 TIME_WAITING,直到被合并线程执行结束,或者等待被合并线程执行 millis 的时间
    public final synchronized void join(long millis) throws InterruptedException;
    
    // 将当前线程变为 TIME_WAITING,直到被合并线程执行结束,或者等待被合并线程执行 millis + nanos 的时间
    public final synchronized void join(long millis, int nanos) throws InterruptedException;
    

    使用 join() 方法需要注意的地方:

    • join() 方法需要使用被合并线程的句柄,当前线程会进入 TIME_WAITING 等待状态,让出 CPU。
    • 如果设置了执行时间,并不能保证当前线程会在这个时间之后变为 RUNNABLE 。
    • 如果主动方合并线程在等待时被中断,会抛出 InterruptedException 异常。

    下面看一个例子:

    public class SleepDemo {
    
        static class SleepThread extends Thread {
            static int threadSeqNo = 1;
    
            public SleepThread() {
                super("SleepThread-" + threadSeqNo);
                threadSeqNo++;
            }
    
            public void run() {
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    System.out.println(Thread.currentThread().getName() + "发生异常中断。");
                }
                System.out.println(Thread.currentThread().getName() + "运行结束。");
            }
        }
    
        public static void main(String[] args) {
            Thread thread1 = new SleepThread();
            System.out.println("启动 Thread1.");
            thread1.start();
            try {
                thread1.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            Thread thread2 = new SleepThread();
            System.out.println("启动 Thread2.");
            thread2.start();
            try {
                thread2.join(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("线程运行结束.");
        }
    }
    

    输出如下,注意输出的顺序:

    thread-join.png

    注意没有时限和有时限的 thread.join() 方法的一个不同之处,没有时限的主线程会在子线程执行期间处于 WAITING 状态,而后者是处于 TIMED_WAITING 状态,它们都不会被分配 CPU 时间片。

    线程的 yield 操作

    线程的 yield (让步)操作的作用是让目前正在执行的线程放弃当前的执行,让出 CPU 的执行权限,使得 CPU 去执行其它的线程,处于让步状态的线程状态在 JVM 层面仍然是 RUNNABLE,从 操作系统层面上来说会从执行状态变成就绪状态。

    线程在 yield 时,线程放弃和重占 CPU 时间是不确定的,可能是刚刚放弃 CPU,马上又获取 CPU 执行权限,重新开始执行。

    yield() 方法是静态方法,它可以让当前正在执行的线程暂停,但不会阻塞该线程,只是让线程转入就绪状态。

    线程的 daemon 操作

    Java 种的线程分为两类,守护线程和用户线程,守护线程也称后台线程,专门指程序进程运行过程中,在后台提供某种通用服务的线程。比如Java种的GC线程。

    Thread 类中,实例属性 daemon属性表示当前线程是否为守护线程。通过方法 setDaemon 可以设置daemon属性,通过方法 isDaemon 可以获取daemon属性。

    用户线程和守护线程与 JVM 虚拟机进行终止的依赖不同。用户线程是主动关系,如果用户线程全部终止,JVM虚拟机进程也随之终止;守护进程是被动关系,如果JVM进程终止,所有守护线程也随之终止。

    使用守护线程时,有以下几点要注意:

    1. 守护线程必须在启动前将其守护状态设置为true,启动之后在设置 JVM 会抛出 InterruptedException
    2. 守护线程有被 JVM 终止的风险,所以在守护线程中尽量不去访问系统资源,如文件句柄、数据库连接等。
    3. 守护线程创建的线程也是守护线程。

    相关文章

      网友评论

          本文标题:JAVA 多线程与高并发学习笔记(二)——线程基本操作

          本文链接:https://www.haomeiwen.com/subject/gzgjvrtx.html