Java之线程详解(三)——多线程常用API、七种状态、优先级、Lock锁

2023-10-30

一、多线程常用API

  1. join() 方法

多线程环境下,如果需要确保某一线程执行完毕后才可继续执行后续的代码,就可以通过使用 join 方法完成这一需求设计。

eg:

 public class Thread01 {
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> System.out.println(Thread.currentThread().getName()+",线程执行"), "t1");
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    t1.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+",线程执行");
            }
        }, "t2");
        Thread t3 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    t2.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+",线程执行");
            }
        }, "t3");
        t1.start();
        t2.start();
        t3.start();
    }
}

除了无参的 join 方法以外, Thread 类还提供了有参 join 方法,public final synchronized void join(long millis)。
该方法的参数 long millis 代表的是毫秒时间。
方法作用:等待 millis 毫秒终止线程,假如这段时间内该线程还没执行完,也不会再继续等待。

  1. sleep()方法

sleep()可以让当前正在执行的线程进入休眠(暂时停止执行)指定的毫秒数,即当前线程会从“运行状态”进入到“超时等待状态”。sleep()会指定休眠时间,线程休眠的时间会大于/等于该休眠时间;在线程重新被唤醒时,它会由“超时等待状态”变成“就绪状态”,从而等待cpu的调度执行。

在服务器中,死循环while(true)方法会导致服务器死循环一直运行着,如果没有添加sleep()方法那么下面的代码会直接占用100%的cpu,导致别的应用没有执行权。这种情况叫做CPU空转如果加了sleep(),cpu的占用率会明显减低。

eg:

while(true){
 try {
       Thread.sleep(20);
      } catch (InterruptedException e) {
       e.printStackTrace();
   }
}
  1. yield() 方法

yield():主动释放cpu执行权。

eg:

public class Thread07 extends Thread{
    public Thread07(String name){
        super(name);
    }

    @Override
    public void run() {
        for (int i=0; i<50; i++){
            if(i == 30){
                System.out.println(Thread.currentThread().getName()+",释放cpu执行权");
                this.yield();//主动释放cpu执行权
            }
            System.out.println(Thread.currentThread().getName()+","+i);
        }
    }

    public static void main(String[] args) {
        new Thread07("t1").start();
        new Thread07("t2").start();

    }
}
  1. 守护线程与用户线程

(1)守护线程:是一种特殊的线程,在后台默默地完成一些系统性的服务,比如垃圾回收线程、JIT 线程都是守护线程。
(2)用户线程:可以理解为是系统的工作线程,它会完成这个程序需要完成的业务操作。如我们使用 Thread 创建的线程在默认情况下都属于用户线程。

通过 Thread.setDaemon(false) 方法可以设置为用户线程;
通过 Thread.setDaemon(true) 方法可以设置为守护线程;
如果不设置线程的属性,那么默认为用户线程,线程属性的设置要在线程启动之前。

守护线程是为用户线程服务的,当一个程序中的所有用户线程都执行完成之后程序就会结束运行,程序结束运行时不会管守护线程是否正在运行。
用户线程是独立存在的,不会因为其他用户线程退出而退出。

用户线程:
当主线程执行完毕后,用户线程还会继续执行。

public static void main(String[] args) throws InterruptedException {
    Thread thread = new Thread(() -> {
        while (true) {
            try {
                Thread.sleep(1000);
                System.out.println("我是用户线程......");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    });
    thread.start();

    Thread.sleep(3000);
    System.out.println("主线程执行完毕......");
}

守护线程:
当主线程执行完毕后,用户线程不会继续执行。

public static void main(String[] args) throws InterruptedException {
    Thread thread = new Thread(() -> {
        while (true) {
            try {
                Thread.sleep(1000);
                System.out.println("我是用户线程......");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    });
    // 设置为守护线程
    thread.setDaemon(true);
    thread.start();

    Thread.sleep(3000);
    System.out.println("主线程执行完毕......");
}
  1. 如果停止一个线程

(1)可以使用Thread.stop()方法,但不推荐使用此方法,虽然它确实可以停止一个正在运行的线程,但是这个方法是不安全的,而且是被弃用作废的。因为Thread.stop()方法会直接把线程停止,这样就没有给线程足够的时间来处理想要在停止前保存数据的逻辑,任务戛然而止,会导致出现数据完整性等问题。
(2)停止一个线程还可以使用Thread.interrupt()方法,但这个方法不会终止一个正在运行的线程,还需要加入一个判断才可以完成线程的停止。
(3)使用退出标志使线程正常退出。

eg:

Thread.interrupt()方法停止:

public class Thread05 {
    static class MyThread extends Thread {
        public void run() {
            try {
                while (true) {
                    if (isInterrupted()) {
                        throw new InterruptedException();
                    }
                }
            } catch (InterruptedException e) {
                System.out.println("由于异常退出线程");
                e.printStackTrace();
            } finally {
                System.out.println("线程的finally");
            }
        }
    }

    public static void main(String[] args) {
        try {
            MyThread thread = new MyThread();
            thread.start();
            Thread.sleep(2000);
            thread.interrupt();
            while(true){   //主线程将一直运行
            }
        } catch(Exception e){
            e.printStackTrace();
        }
    }
}

使用退出标志使线程正常退出:

public class Thread06 implements Runnable{
    private static volatile boolean canceled = false;

    @Override
    public void run() {
        int i = 0;
        while (!canceled&&i<=1000){
            i++;
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+" "+i+" 线程执行中...");
        }
        System.out.println(Thread.currentThread().getName()+" 线程关闭...");
    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new Thread06());
        thread.start();
        Thread.sleep(10);
        canceled = true;
    }
}
  1. join()、wait()、sleep()方法之间的区别

Thread.sleep(long):

sleep(long)是Thread的静态方法;
使当前所在线程进入阻塞;
只是让出CPU,并没有释放锁;
由于睡眠时间结束后不一定立即被CPU调度,因此线程休眠的时间可能大于传入的参数;
如果被终端则抛出InterruptedException。

Object.wait():

wait()是Ojbect的静态方法;
让出CPU,释放对象锁;
在调用前需要先拥有对象锁,所以一般在synchronized中同步块使用;
使该线程进入该对象的监视器的等待队列;
如果不指定等待时长,需要手动notify/notifyAll唤醒。

join():

join()是Thread的非静态方法;
join等于synchronized+Object.wait();
线程合并,调用线程会进入阻塞状态,需要等待被调用线程结束后才可以执行;
应用场景:当一个线程必须等待其他线程执行完毕才能继续执行。

二、多线程的七种状态

1、初始(NEW):刚被创建,还没运行(未执行线程的start()方法)。
2、就绪状态(READY):线程在可运行线程池中,但未获得CPU执行权,和RUNNING并称运行。
3、运行中状态(RUNNING):线程执行并获得CPU执行权,和READY并称运行。
4、阻塞(BLOCK):等待其他线程释放锁的状态。
5、等待(WAITING):需要其他线程做出一些约定好的动作,或被唤醒(通知或中断)。
6、超时等待(TIME_WAITING):和等待的不同点在于可以在指定的时间自行醒来。
7、终止(TERMENATED):线程已经执行完毕。

1、new Thread(),该线程为初始状态;
2、调用start()方法会使得该线程开始执行,进入就绪状态;
3、当CPU调度执行该线程,则该线程进入运行中状态;
4、当run代码执行完毕,则该线程进入终止状态;
5、当在运行状态时,调用yield()方法,则该线程暂停执行并释放CPU执行权,进入就绪状态;
6、当在运行状态时,调用 wait()、join() 等方法,则该线程进入等待状态,并释放CPU执行权,当调用notify()、notifyAll() 等方法,重新进入就绪状态;
7、当在运行状态时,执行时一直没有获取到锁,则该线程进入阻塞状态,获取到锁重新进入就绪状态;
8、当在运行状态时,调用sleep(long)、wait(long)、join(long)等方法,则该线程进入超时等待状态,当调用notify()、notifyAll() 等方法,重新进入就绪状态。

三、多线程的优先级

线程优先级用数字表示,范围从1~10:
Thread.MIN_PRIORITY = 1;
Thread.MAX_PRIORITY = 10;
Thread.NORM_PRIORITY = 5;
默认为5

设置优先级:setPriority(int)
获取优先级:getPriority()

public class Thread08 {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            int t1Count = 0;
            for (; ;) {
                System.out.println(Thread.currentThread().getName() + "--" + t1Count++);
            }
        },"t1");
        Thread t2 = new Thread(() -> {
            int t2Count = 0;
            for (; ;) {
                System.out.println(Thread.currentThread().getName() + "--" + t2Count++);
            }
        },"t2");

        t1.setPriority(Thread.MAX_PRIORITY);
        t2.setPriority(Thread.MIN_PRIORITY);
        t1.start();
        t2.start();
    }
}

四、Lock锁

  1. 常用方法
方法 描述
void lock() 获得锁。如果锁不可用,则当前线程将被禁用以进行线程调度,并处于休眠状态,直到获取锁。
void unlock() 释放锁。
void lockInterruptibly() 获取锁,如果可用则立即返回。如果锁不可用,那么当前线程将被禁用以进行线程调度,并且处于休眠状态,和lock()方法不同的是在锁的获取中可以中断当前线程。
boolean tryLock() 只有在调用时才可以获得锁。如果可用,则获取锁,并立即返回值为true;如果锁不可用,则此方法将立即返回值为false 。
boolean tryLock(long time, TimeUnit unit) 超时获取锁,当前线程在以下三种情况下会返回: 1. 当前线程在超时时间内获得了锁;2.当前线程在超时时间内被中断;3.超时时间结束,返回false

注意:

如果采用Lock,必须主动去释放锁,在发生异常时,不会自动释放锁。因此一般来说,使用Lock必须在try{}catch{}块中进行,并且将释放锁的操作放在finally块中进行,以保证锁一定被被释放,防止死锁的发生。
tryLock()方法表示用来尝试获取锁,如果获取成功,则返回true,如果获取失败(即锁已被其他线程获取),则返回false,也就说这个方法无论如何都会立即返回。在拿不到锁时不会一直在那等待。
tryLock(long time, TimeUnit
unit)方法和tryLock()方法是类似的,只不过区别在于这个方法在拿不到锁时会等待一定的时间,在时间期限之内如果还拿不到锁,就返回false,如果如果一开始拿到锁或者在等待期间内拿到了锁,则返回true。

eg:

public class Lock01 implements Runnable{
    private  int count = 100;
    private final ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true) {
            try {
                Thread.sleep(30);
                lock.lock();//开启锁
                if (count > 0) {
                    System.out.println(Thread.currentThread().getName() + ",--" + count--);
                }else{
                    break;
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();//关闭锁,一般都将unlock写入到finally块中
            }
        }
    }

	public static void main(String[] args) {
        Lock01 lock01 = new Lock01();
        Thread t1 = new Thread(lock01,"a");
        Thread t2 = new Thread(lock01,"b");
        t1.start();
        t2.start();
    }
}
  1. Condition接口
    Condition接口的常见方法:
    | 方法 | 描述 |
    |–|–|
    | void await() | 相当于Object类的wait方法 |
    | boolean await(long time, TimeUnit unit) | 相当于Object类的wait(long timeout)方法 |
    | signal() | 相当于Object类的notify方法 |
    | signalAll() | 相当于Object类的notifyAll方法 |

eg:

public class Lock01 implements Runnable{
    private  int count = 100;
    private final ReentrantLock lock = new ReentrantLock();
    private final Condition condition = lock.newCondition();

    @Override
    public void run() {
        while (true) {
            try {
                lock.lock();//开启锁
                condition.signal();//主动唤醒线程
                if (count > 0) {
                    System.out.println(Thread.currentThread().getName() + ",--" + count--);
                    condition.await();//主动释放锁  同时当前线程变为阻塞状态
                }else{
                    break;
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();//关闭锁,一般都将unlock写入到finally块中
            }
        }
    }

    public static void main(String[] args) {
        Lock01 lock01 = new Lock01();
        Thread t1 = new Thread(lock01,"a");
        Thread t2 = new Thread(lock01,"b");
        t1.start();
        t2.start();
    }
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Java之线程详解(三)——多线程常用API、七种状态、优先级、Lock锁 的相关文章

  • 用于解析和构建逻辑表达式的 Java 库

    我正在寻找一个 Java 开源库来解析和构建类似 SQL 的表达式 例如评估表达式的有效性 例如 a x or y and b z 另外我想要一个用于构建或扩展表达式的 API 就像是 Expression exp new Expressi
  • 从文本文件中读取阿拉伯字符

    我完成了一个项目 在该项目中我读取了用记事本编写的文本文件 我的文本文件中的字符是阿拉伯语 文件编码类型是UTF 8 当在 Netbeans 7 0 1 中启动我的项目时 一切似乎都正常 但是当我将项目构建为 jar 文件时 字符以这种方式
  • 将构造函数作为参数传递给方法

    我是java新手 开始研究构造函数 我看到一些构造函数作为参数传递给方法的示例 请告诉我当构造函数作为参数传递给方法时会发生什么 或者建议我一些链接 我可以在其中获得有关使用构造函数的足够知识 根据您需要传递构造函数的目的 您可以考虑传递供
  • 在 Java 正则表达式中获取多个模式的重叠匹配

    我有同样的问题这个链接 https stackoverflow com questions 18751486 matching one string multiple times using regex in java 但有多种模式 我的正
  • 使类只能从特定类实例化

    假设我有 3 节课class1 class2 and class3 我怎样才能拥有它class1只能通过实例化class2 class1 object new class1 但不是 class3 或任何其他类 我认为它应该与修饰符一起使用
  • 如何在url请求中发送数组

    我的要求如下 我想给出演员姓名 开始日期 结束日期并获取他在该时期出演的所有电影 因此 我的服务请求是这样的 http localhost 8080 MovieDB GetJson name Actor startDate 20120101
  • Java 卡布局。多张卡中的一个组件

    一个组件 例如JLabel 在多张卡中使用CardLayout 目前看来该组件仅出现在它添加到的最后一张卡上 如果有办法做到这一点 我应该吗 这是不好的做法吗 或者有其他选择吗 你是对的 它只出现在 添加到的最后一张卡 中 但这与CardL
  • 以有效的方式从 Map 中删除多个键?

    我有一个Map
  • JUnit5 平台启动器 API - 如果没有至少一个测试引擎,则无法创建启动器

    我正在尝试升级我们的自动化测试套件的测试能力以接受 JUnit5 测试并遵循JUnit 平台启动器 API 说明 https junit org junit5 docs current user guide launcher api我收到错
  • 这个等待通知线程语义的真正目的是什么?

    我刚刚遇到一些代码 它使用等待通知构造通过其其他成员方法与类中定义的线程进行通信 有趣的是 获取锁后 同步范围内的所有线程都会在同一锁上进行定时等待 请参见下面的代码片段 随后 在非同步作用域中 线程执行其关键函数 即 做一些有用的事情1
  • JavaFX使节点覆盖父节点边框颜色

    我有一个如下所示的节点 仅使用 css 我希望标签覆盖其父边框颜色 因此标签下方的边框颜色部分变得不可见 我用来制作这个边框的CSS代码 fx border color black fx border width 3 fx border r
  • 在java中将字符串日期转换为美国格式

    我有下面的代码 其中日期为字符串类型 我必须将其设置为美国格式 所以下面我已经展示了它 private static final SimpleDateFormat usOutputDate new SimpleDateFormat MM d
  • Struts 1 到 Spring 迁移 - 策略

    我有一个legacy银行应用程序编码为Struts 1 JSP现在的要求是迁移后端 目前为 MVC to Springboot MVC 后续UI JSP 将迁移到angular Caveats 1 后端不是无状态的 2 会话对象中存储了大量
  • 如何在 JPA 和 Hibernate 中将数据库生成的列值定义为只读字段?

    使用 MariaDB 10 2 可以定义日期时间的默认值 例如创建和最后修改 我应该如何将此列作为只读字段访问 因为这个值应该只在数据库的控制之下 并且不应该从代码中修改 但我想在代码中读取这个属性 这很简单 只需设置insertable
  • 拆分/标记化/扫描字符串并注意引号

    Java中是否有默认 简单的方法来分割字符串 但要注意引号或其他符号 例如 给定以下文本 There s a man that live next door in my neighborhood and he gets me down Ob
  • 如何以编程方式创建 CardView

    我正在开发一个 Android 应用程序Java Android Studio 我想在活动中创建CardView以编程方式 我想将以下属性设置为CardView layout width wrap content layout row 0
  • java中使用多线程调用同一类的不同方法

    我有一个类 如下所示 具有三种方法 public class MyRunnable implements Runnable Override public void run what code need to write here to c
  • 如何从 JavaFX 中的另一个控制器类访问 UI 元素?

    我有一个使用 NetBeans 8 编写的 JavaFX Java 8 应用程序 没有SceneBuilder 我的应用程序有一个主窗口 该窗口有自己的 FXML 文件 primary fxml 和自己的控制器类 FXMLPrimaryCo
  • Axis2 错误:要输出的文本中的空白字符 (0x4) 无效

    我创建了一个 Java 客户端 使用 Axis2 1 7 6 作为代码生成器与 SOAP Web 服务进行交互 问题在于客户端的某些输入抛出异常并显示以下消息 org apache axis2 AxisFault Invalid white
  • Errors/BindingResult 参数应在模型属性、@RequestBody 或 @RequestPart 参数之后立即声明

    我通过剖析示例应用程序来自学 Spring 然后到处添加代码来测试我在剖析过程中开发的理论 在测试添加到 Spring 应用程序中的一些代码时 我收到以下错误消息 An Errors BindingResult argument is ex

随机推荐

  • 10.网络爬虫—MongoDB详讲与实战

    网络爬虫 MongoDB详讲与实战 MongoDB MongoDB安装 创建数据目录 1 数据库操作 2 集合操作 3 文档操作 4 索引操作 5 聚合操作 6 备份与恢复 MongoDB增删改查 mongodb集合的增删改查 数据插入到表
  • 知识点记录-abase是什么

    Abase Abase是一套支持redis协议的分布式KV存储系统 是字节跳动自研的一套数据库系统 对在线业务发挥着核心作用 支持redis协议 高可用 低延迟 高扩展的kv存储 常用于redis的大容量持久化场景 为什么采用Abase r
  • 在react项目中调用百度地图API的BMap后报错解决方案

    我这里是使用了react typescript技术栈 在 public index html文件中引入百度地图项目的文件 就相当于是全局导入了 但是并不能直接使用相关的API 需要在使用的文件中这样声明一下 然后就可以直接使用了 const
  • stm32专题二十九:Flash 读写保护

    设置Flash的读写保护 其实就是操作内部Flash的选项字节 选项字节在内部Flash的主存储页之后 由于是Flash 不能像内存RAM一样随意写入 由于Flash的写入特性 只能将 1 写成 0 而如果要确保写入数据的绝对正确 则需要先
  • pytorch:nn.ReLU(inplace=True) 中的参数 inplane 的作用

    pytorch 的激活函数nn Relu 有一个参数 inplace 其作用是 该nn Relu 函数计算得到的输出是否更新传入的输出 正常情况下 一个卷积操作 首先经过卷积层 接着是正则化 Normalize 最后经过激活函数 而输入激活
  • Excel多行转置为一列

    原始数据如下 想把上述数据变成下边这个样子 面板模型数据录入需要用到下边这个形式的数据 步骤 1 先把所有原始数据转置 2 再在153 19下的单元格输入 C2 输入完之后 16 85就会在153 19下的单元格处了 再先横向 后纵向下拉即
  • 解决IDEA安装Python插件,下载失败的方法

    一 问题 通过file gt settings gt Plugins 安装python时 会提示下载失败 可以采用以下方法解决 二 解决办法 1 在help about中查看IDEA版本 作者的是181 4445 2 在https plug
  • Java架构直通车——结合源码理解PageHelper

    PageHelper实现方式 PageHelper首先将前端传递的参数保存到page这个对象中 接着将page的副本存放入ThreadLoacl中 这样可以保证分页的时候 参数互不影响 接着利用了mybatis提供的拦截器 取得Thread
  • XXL-JOB任务分片

    文章目录 任务类型 任务配置 路由策略 阻塞处理策略 单个任务和集群任务 单机多任务分片 集群分片 任务类型 单个任务 一个任务实例便可完成 单机单任务 单机模式下任何路由模式都只有一个实例执行 集群单任务 由路由策略 广播模式除外 选择其
  • 内网穿透-外远程连接中的RabbitMQ服务

    文章目录 前言 1 安装erlang 语言 2 安装rabbitMQ 3 内网穿透 3 1 安装cpolar内网穿透 支持一键自动安装脚本 3 2 创建HTTP隧道 4 公网远程连接 5 固定公网TCP地址 5 1 保留一个固定的公网TCP
  • GIS项目研发总结

    海珠区慢性防治系统 项目研发阶段性总结 作者 华境聪 本次任务完成时间 2018年12月22日 2019年1月16日 完成模块功能 前台 图层管理 医院 社康 药店 后台 修改资料 密码 数据管理 一 功能实现 图层管理主要包括医院 社康
  • Strtus2漏洞复现

    文章目录 安装docker 第一步 安装docker 第二步 检查docker安装是否成功 第三步 安装docker compose 第四步 下载漏洞库 第五步 启动 关闭漏洞 漏洞列表 S2 048远程执行代码漏洞 漏洞简介 漏洞原理 影
  • MyBatis整体架构分析:mysql跳过错误执行日志

    缓存维护方案一 如果是一读 线程B 一写 线程A 操作 先操作缓存 再操作数据库 流程图如下所示 1 线程A发起一个写操作 第一步del cache 2 线程A第二步写入新数据到DB 3 线程B发起一个读操作 cache miss缓存失效了
  • python retrying retry

    在写业务时 有个业务需要在return某种情况下 进行重试 代码类似于下面 from retrying import retry def verify self try return self verify 1 except RetryEr
  • Java学习笔记(十三)

    面向对象和面向过程的区别 面向过程 优点 性能比面向对象高 因为类调用时需要实例化 开销比较大 比较消耗 资源 比如单片机 嵌入式开发 Linux Unix 等一般采用面向过程开发 性能是最重要的因素 缺点 没有面向对象易维护 易复用 易扩
  • [网络安全]upload-labs Pass-02 解题详析

    读者可参考 订阅专栏 Upload Labs靶场攻防实战 Antsword蚁剑 该题涉及蚁剑工具的利用 操作可参考 网络安全 AntSword 蚁剑 实战解题详析 入门 网络安全 DVWA之File Upload AntSword 蚁剑 攻
  • echarts柱状图个数多,横坐标名称过长显示不全解决方法

    当echarts柱状图个数多 横坐标名称过长时横坐标名称显示不全 网上并没有搜到太好的方法 于是自己加工了下 将横坐标名称显示前六位 当鼠标放到上面的时候显示全名 下面是示例代码 可以直接拷贝测试 代码解读 HTML div class m
  • 新的换脸模型FaceShifter论文解读

    论文地址 https arxiv org pdf 1912 13457 pdf 前言 本文介绍了一种pipline模型 用以抽取多维度的图片特征信息 并自适应的完成FaceSwap任务 整体模型分类两个部分 第一部分基于AAD Adapti
  • 网络复习——路由器&交换机工作原理

    1 路由器的工作原理 路由器的工作原理 3层设备 当数据包进入路由器时 路由器先查看数据包中的2层报头目标MAC地址 广播 解封装到3层 当路由器收到数据包时 如果目标MAC为全F 那么路由器无法判断广播与自己有无关系 需要借助3层 组播
  • Java之线程详解(三)——多线程常用API、七种状态、优先级、Lock锁

    一 多线程常用API join 方法 多线程环境下 如果需要确保某一线程执行完毕后才可继续执行后续的代码 就可以通过使用 join 方法完成这一需求设计 eg public class Thread01 public static void