• java EE初阶 — Thread类及常见方法


    1.Thread 常见的构造方法

    • Thread() - 创建线程对象
    • Thread(Runnable target) - 使用 Runnable 对象创建线程对象
    • Thread(String name) - 创建线程对象,并且命名
    • Thread(Runnable trget, String name) - 使用 Runnable 对象创建线程对象,并且命名
    • Thread(ThreadGroup group, Runnable target) - 线程可以被用来分组管理,分号的即为线程组,目前了解即可

    例子:

    Thread t1 = new Thread();
    Thread t2 = new Thread(new MyRunnable());
    Thread t3 = new Thread("名字");
    Thread t4 = new Thread(new MyRunnable(), "名字");
    
    • 1
    • 2
    • 3
    • 4
    package thread;
    
    public class ThreadDemo6 {
        public static void main(String[] args) {
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    while (true) {
                        System.out.println("hello world");
                    }
                }
            }, "MyRunnable");
            thread.start();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    2.Thread 几个常见的属性

    1、 ID(getid()) 是线程的唯一标识,不同线程不会重复。


    2、名称(getName()) 是构造方法里起的名字。


    3、状态(getState()) 表示线程当前所处的一个情况,(java 里的线程状态要比操作系统原生的状态更丰富一些)
    下面我们会进一步说明。


    4、 优先级(getPriority)可以获取,也可以设置,但是没什么作用。


    5、关于
    后台线程(isDaemon())
    ,需要记住一点:JVM会在一个进程的所有非后台线程结束后,才会结束运行。

    前台线程会阻止进程结束,前台线程工作没做完,进程是是无法结束工作的。
    后台线程不会阻止进程的结束,后台线程工作没做完,进程也是可以结束。

    代码手动创建线程的时候。默认都是前台
    包括 main 默认也是前台的,其他 JVM 自带的都是后台的。

    也可以使用setDaemon设置后台线程,也是守护线程

    package thread;
    
    public class ThreadDemo7 {
        public static void main(String[] args) {
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println("hello");
                }
            }, "MyRunnable");
            thread.setDaemon(true);
            thread.start();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    把 thread 设置成了后台线程/守护线程,此时进程是否结就与 thread 无关了。



    6、是否存活(isAlive),即简单的理解,为 run 方法是否运行结束了

    在这里插入图片描述
    如果光是创建一个 thread 变量,不调用 start 则在系统内核里不会有线程。

    创建变量就相当于是把一个任务梳理好了,而调用 start 就相当于是开始做任务。

    在真正调用 start 之前,调用 thread.isAlive ,就是false。
    调用 start 之后,isAlive 就是 true。

    例子:

    package thread;
    
    public class ThreadDemo8 {
        public static void main(String[] args) {
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println("hello java");
                }
            }, "Runnable");
            thread.start();
            while (true) {
                try {
                    Thread.sleep(1000);
                    System.out.println(thread.isAlive());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21


    isAlive 是在判断当前系统里面的这个线程是不是真的存在了。

    run 执行完了,内核里的PCB就释放了。
    操作系统里的线程就没了。
    但是 thread 这个对象还在,当引用不指向这个对象没被GC回收时,thread 就不存在了。

    总结:

    • 如果 thread 的 run 还没跑,isAlive 就是 false。
    • 如果 thread 的 run 正在跑,isAlive 就是 true。
    • 如果 thread 的 run 跑完了,isAlive 就是 false。


    isAlive为true的例子: run 里面的线程会在执行3秒以后销毁。

    package thread;
    
    public class ThreadDemo8 {
        public static void main(String[] args) {
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i = 0; i < 3; i++)  try {
                        System.out.println("hello java");
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }, "Runnable");
            thread.start();
            while (true) {
                try {
                    Thread.sleep(1000);
                    System.out.println(thread.isAlive());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26


    因为抢占式执行,hello java 在前还是 true 在前是不确定的。
    要看调度器结果,这是不可预期的。


    7、线程的中断问题(interrupted()),下面我们进一步说明。

    3.启动一个线程 - start()

    线程对象被创建出来并不意味着线程就开始运行了。

    调用 start 方法, 才真的在操作系统的底层创建出一个线程。

    就像是前面说的,创建对象相当于是梳理任务,而调用 start 则是开始执行任务。

    4.终止一个线程

    终止意思不是让线程立即就停止,而是通知线程应该要停止了。
    但是是否真的停止,取决于线程这里具体的代码的写法。

    4.1 使用标志位来控制线程是否要停止

    package thread;
    
    public class ThreadDemo9 {
        public static boolean flag = true;
    
        public static void main(String[] args) throws InterruptedException{
            Thread thread = new Thread(() -> {
                while (flag) {
                    System.out.println("hello");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
            thread.start();
    
            Thread.sleep(3000);//3秒后结束线程
            //主线程这里可以随时通过flag变量的取值,来操作 thread 线程是否结束。
            flag = false;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    这段代码执行后3秒钟会结束线程。


    自定义变量这种方式,不能及时响应。
    尤其是在 sleep 休眠时间比较久的时候。

    thread 线程之所以会结束,完全取决于 thread 线程内部的代码的 flag。
    通过 flag 来控制循环。

    这里只是告知线程要结束了,但是什么时候结束,都是取决于线程内部的代码是如何实现的

    4.2 使用 Thread 自带的标志位来进行判定

    package thread;
    
    public class ThreadDemo10 {
        public static void main(String[] args) throws InterruptedException{
            Thread thread = new Thread(() -> {
                while (!Thread.currentThread().isInterrupted()) {
                    System.out.println("hello juejin");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
            thread.start();
            Thread.sleep(3000);
            thread.interrupt();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    currentThread() 是Thread 类的静态方法
    通过这个方法可以获取到当前线程。
    哪个线程调用的这个方法,就是得到哪个线程的对象引用。

    isInterrupted() 是在 thread.run中被调用的。
    此处获取的线程就是 thread 线程。
    为 true 表示被终止,为 false 表示未被终止。
    这个方法的背后,就相当于是判断定一个 boolean 变量。

    interrupt() 是用来终止 thread 线程的。
    相当于是在设置这个 boolean 变量。

    如果线程在 sleep 中休眠,此时调用 interrupt会把线程唤醒。从 sleep 提前返回了。
    interrupt 会触发 sleep 内部的异常,导致 sleep 提前返回。


    可以看到在3秒钟执行结束后,抛了一个异常后又继续执行了。

    这是因为 interrupt会做两件事:

    • 把线程内部的标志位(boolean)给设置成true。
    • 如果线程在进行 sleep ,就会触发异常把 sleep 唤醒。
      但是 sleep 在唤醒的时候,还会做一件,就是把刚才设置的这个标志位再设置为 false 。(情况标志位)
      这就导致了当 sleep 的异常被 catch 完之后,循环还要继续执行。


    当然也可以在唤醒 sleep 之后就停止,在刚才的代码中加入一个 break 即可。


    可以看到抛丸异常就停止了。

    怎样终止线程、什么时候终止,主要还是看代码是怎样实现的。

    5.等待一个线程 - join()

    线程是一个随机调度的过程。
    等待线程做的事情,就是在控制两个线程的结束顺序。

    package thread;
    
    public class ThreadDemo11 {
        public static void main(String[] args) {
            Thread thread = new Thread(() -> {
                 for (int i = 0; i < 3; i++) {
                     System.out.println("hello thread");
                     try {
                         Thread.sleep(1000);
                     } catch (InterruptedException e) {
                         e.printStackTrace();
                         break;
                     }
                 }
            });
            thread.start();
            System.out.println("join之前");
    
            //此处的join就是让当前的main线程来等待thread线程指向结束(等待thread的run执行完)
            try {
                thread.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("join 之后");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    本身执行完 start 之后,thread 线程和 main 线程就并发执行分头行动。
    main 继续往下执行,thread 也会继续往下执行。

    join() 使线程发生阻塞,会一直阻塞到 thread 线程结束时,
    main 线程才会从join中恢复过来,才能继续执行下去,因此 thread 线程肯定是比 main 先结束的。



    main 线程等待 thread 执行结束后才会执行。

    如果执行 join 时,thread 已经结束了,join 不会阻塞,就会立即返回。



    join 还有另一种用法:

    public void join(long millis)

    此方法的含义是,指定等待的时间。
    这种方式的操作比较常见。

    上面的无参数的 join() 则会一直等待。

    6.获取当前线程引用

    public static Thread currentThread(); //返回当前线程对象的引用
    
    • 1

    在哪个线程调用,就能获取到哪个线程的实例。

    public class ThreadDemo {
        public static void main(String[] args) {
            Thread thread = Thread.currentThread();
            System.out.println(thread.getName());
       }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    7.休眠当前线程

    //休眠当前线程 millis毫秒
    public static void sleep(long millis) throws InterruptedException 
    //可以更高精度的休眠
    public static void sleep(long millis, int nanos) throws InterruptedException 
    
    • 1
    • 2
    • 3
    • 4

    让线程休眠,本质上就是让这个线程不参与调度了。(不去CPU上执行了)


    在操作系统里面有一个就绪队列和一个阻塞队列


    操作系统每次需要调度一个线程去执行,就从就队列中选一个就好了。

    PCB 是使用链表来组织的。(并不具体)
    实际的情况并不是一个简单的链表,其实这是个一系列以链表为核心的数据结构。

    线程 A 调用 sleep ,A 就会进入休眠状态。
    把 A 从上述链表中拿出来,放到另一个链表中。


    另一个链表里的PCB都是阻塞状态,暂时不参与 CPU 调度执行,是阻塞队列

    一旦线程进入阻塞状态,对应 PCB 就进入阻塞队列了,此时就暂时无法参与调度了。

    比如调用 sleep(1000) ,对应的线程 PCB 就要在阻塞队列中待1000ms这么长的时间。


    当这个 PCB 回到就绪队列后并不会被立即执行。
    因为虽然是 sleep(1000),但是实际上考虑到调度的开销,
    对应的线程是无法在唤醒之前之后立即执行的,实际上的时间间隔大概率要大于 1000ms。

  • 相关阅读:
    嵌入式设备文件系统构建——增加用户登录功能
    【Electron】electron与cljs的处理
    技术问题整理04
    二蛋赠书五期:《Python数据挖掘:入门、进阶与实用案例分析》
    将来已来-SoftwareDemo软件测试种子的埋下去-第二次重大情绪点的触及到
    使用java模拟文件管理系统,包含文件夹、文件的基本创建、删除、重命名、拷贝等操作
    ReactNative进阶(二十一)开源插件 react-native-device-info 获取设备信息
    如何做好建筑翻译呢
    力扣每日一题74:搜索二维矩阵
    动能方案 | 15693协议的读卡器应用 DP1363F 替代RC663
  • 原文地址:https://blog.csdn.net/m0_63033419/article/details/128060042