线程生命周期、线程通讯

news2025/1/11 11:50:40

一、生命周期

        有关线程生命周期就要看下面这张图,围绕这张图讲解它的方法的含义,和不同方法间的区别。

   1、yield()方法

    yield()让当前正在运行的线程回到就绪,以允许具有相同优先级的其他线程获得运行的机会。但是,实际中无法保证yield()达到让步的目的,因为,让步的线程可能被线程调度程序再次选中。

    同时yield()不会放弃锁资源,所以有可能会出现死锁。

   2、wait和sleep方法的区别

1)第一个很重要的区别就是,wait方法必须正在同步环境下使用,比如synchronized方法或者同步代码块。如果你不在同步条件下使用,会抛出IllegalMonitorStateException异常。另外,sleep方法不需要再同步条件下调用,你可以任意正常的使用。

2)第二个区别是,wait方法用于和定义于Object类的,而sleep方法操作于当前线程,定义在java.lang.Thread类里面。

3)第三个区别是,调用wait()的时候方法会释放当前持有的锁,而sleep方法不会释放任何锁。

   3、wait和sleep方法使用场景

     (1)wait方法定义在Object类里面,所有对象都能用到,一般wait()和notify()方法或notifyAll使用于线程间的通信。

     (2)sleep()方法用于暂停当前线程的执行。

   4、join方法()

         thread.Join把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行的线程。

 比如在线程B中调用了线程A的Join()方法,直到线程A执行完毕后,才会继续执行线程B。

       这里有个待思考的案例?应该是自己对join没有理解透,留在以后再来回顾。

/*
 * 有关join第一个疑惑就是我在t2.join()后,发现它是无效的,同样还是交叉输出。
 */
class ThreadTesterA implements Runnable {

    private int counter;

    public void run() {
        while (counter <= 10) {
            System.out.print("Counter = " + counter + " ");
            counter++;
        }
        System.out.println();
    }
}
class ThreadTesterB implements Runnable {
    private int i;
    public void run() {
        while (i <= 10) {
            System.out.print("i = " + i + " ");
            i++;
        }
        System.out.println();
    }
}

public class ThreadTester {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(new ThreadTesterA());
        Thread t2 = new Thread(new ThreadTesterB());
        t1.start();
        t2.start();
        t2.join(); //无效
}
}

5、stop方法

    线程启动完毕后,在运行可能需要终止,Java提供的终止方法只有一个stop,但是不建议使用此方法,因为它有以下三个问题:

   1)stop方法是过时的。

      从Java编码规则来说,已经过时的方式不建议采用.

   2)stop方法会导致代码逻辑不完整

     stop方法是一种"恶意" 的中断,一旦执行stop方法,即终止当前正在运行的线程,不管线程逻辑是否完整,这是非常危险的.

   3)stop方法会破坏原子逻辑

      多线程为了解决共享资源抢占的问题,使用了锁的概念,避免资源不同步,但是正是因为此原因,stop方法却会带来更大的麻烦,它会丢弃所有的锁,导致原子逻辑受损

二、线程通讯小案例

1、如何让两个线程依次执行?

    题目:假设有两个线程,一个是线程 A,另一个是线程 B,两个线程分别依次打印 1-3 三个数字即可。我们希望 B 在 A 全部打印完后再开始打印。

关键方法:join()

//题目:假设有两个线程,一个是线程 A,另一个是线程 B,我们希望 B 在 A 全部打印完后再开始打印。
public class TestJoin {
    public static void main(String[] args) {        
        demo2(); 
    }
    
    private static void demo2() {
        Thread A = new Thread(new Runnable() {
            @Override
            public void run() {
                printNumber("A");
            }
        });
        Thread B = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("B 开始等待 A");
                try {
                    A.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                printNumber("B");
            }
        });
        B.start();
        A.start();
    }
    
    private static void printNumber(String threadName) {
        int i=0;
        while (i++ < 3) {
            System.out.println(threadName + "print:" + i);
        }
    }
}
/*运行结果
 * B 开始等待 A
 * Aprint:1
 * Aprint:2
 * Aprint:3
 * Bprint:1
 * Bprint:2
 * Bprint:3
 */

2、如何让两个线程按照指定方式有序交叉运行呢?

题目:假设有两个线程,一个是线程 A,另一个是线程 B,两个线程分别依次打印 1-3 三个数字即可。我们希望 A和B交替打印

关键方法:wait()和notify()或者notifyAll()

public class Main {
    int i = 1;   //i和istrue作为多线程的共享数据
    boolean istrue = false;

    public static void main(String[] args) {
        Main main = new Main();
        ThreadA a = new ThreadA(main);
        ThreadB b = new ThreadB(main);
        Thread threada = new Thread(a);
        Thread threadb = new Thread(b);
        threada.start();
        threadb.start();

    }}

class ThreadA implements Runnable {
    Main main;

    public ThreadA(Main main) {
        this.main = main;
    }

    public void run() {
        while (main.i <= 10) {
            synchronized (main) { // 必须要用一把锁对象,这个对象是main
                if (!main.istrue) {
                    try {
                        main.wait(); // 操作wait()函数的必须和锁是同一个
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                } else {
                    System.out.println("奇数:" + main.i);
                    main.i++;
                    main.istrue = false;
                    main.notifyAll();
                }
            }}}}

class ThreadB implements Runnable {
    Main main;

    public ThreadB(Main main) {
        this.main = main;
    }

    public void run() {
        while (main.i <= 10) {
            synchronized (main) { // 必须要用一把锁对象,这个对象是main
                if (main.istrue) {
                    try {
                        main.wait(); // 操作wait()函数的必须和锁是同一个
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                } else {
                    System.out.println("偶数:" + main.i);
                    main.i++;
                    main.istrue = true;
                    main.notifyAll();
                }
            }}}}

//梳理下流程
//首先传入一个 A 和 B 共享的对象锁main;
//当 A 得到锁后,直接交出锁的控制权,进入 wait 状态;
//对 B 而言,由于 A 最开始得到了锁,导致 B 无法执行;直到 A 调用wait() 释放控制权后, B 才得到了锁,同时输出:偶数:1,同时notifyAll让A又到就绪状态
//接下来A和B都有可能获得cpu时间碎片,当 A 得到锁后,那么打印奇数:2,如果B又获得cpu时间片,那么它会进入wait状态。
//就这样来去循环,最终就是交叉打印运行。

 运行结果

3、四个线程 A B C D,其中 D 要等到 A B C 全执行完毕后才执行,而且 A B C 是同步运行的。

关键对象:CountdownLatch对象

        最开始我们介绍了 thread.join(),可以让一个线程等另一个线程运行完毕后再继续执行,那我们可以在 D 线程里依次 join A B C,不过这也就使得 A B C 必须依次执行,而我们要的是这三者能同步运行。
或者说,我们希望达到的目的是:A B C 三个线程同时运行,各自独立运行完后通知 D;对 D 而言,只要 A B C 都运行完了,D 再开始运行。针对这种情况,我们可以利用 CountdownLatch 来实现这类通信方式。

/*CountdownLatch基本用法是:
 * 1)创建一个计数器,设置初始值,CountdownLatch countDownLatch = new CountDownLatch(3);
 * 2)在 等待线程 里调用 countDownLatch.await() 方法,进入等待状态,直到计数值变成 0;
 * 3)在 其他线程 里,调用 countDownLatch.countDown() 方法,该方法会将计数值减小 1;
 * 4)当 其他线程 的 countDown() 方法把计数值变成 0 时,等待线程 里的 countDownLatch.await() 立即退出,继续执行下面的代码。
 */

public class TestCountdownLatch {

    public static void main(String[] args) {
        runDAfterABC();
    }
    
    private static void runDAfterABC() {
        int worker = 3;
        CountDownLatch countDownLatch = new CountDownLatch(worker);
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("D开始工作前先等ABC工作完成");
                try {
                    
                    //因为worker初始值为3,所以在不等于0之前一直处于等待状态
                    countDownLatch.await();
                    System.out.println("ABC工作完成,D开始工作");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
        for (char threadName='A'; threadName <= 'C'; threadName++) {
            final String tN = String.valueOf(threadName);
            new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println(tN + "正在工作.....");
                    try {
                        Thread.sleep(100);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    System.out.println(tN + "完成工作......");
                    
                    //每调用一次worker值减一
                    countDownLatch.countDown();
                }
            }).start();
        }
    }    
}

 运行结果:

4、三个运动员各自准备,等到三个人都准备好后,再一起跑

关键对象:CyclicBarrier

      上面的 CountDownLatch 可以用来倒计数,但当计数完毕,只有一个线程的 await() 会得到响应,无法让多个线程同时触发。

为了实现线程间互相等待这种需求,我们可以利用 CyclicBarrier 数据结构。

/* CyclicBarrier 基本用法
 * 1)先创建一个公共 CyclicBarrier 对象,设置 同时等待 的线程数,CyclicBarrier cyclicBarrier = new CyclicBarrier(3);
 * 2)这些线程同时开始自己做准备,自身准备完毕后,需要等待别人准备完毕,这时调用 cyclicBarrier.await(); 即可开始等待别人;
 * 3)当指定的 同时等待 的线程数都调用了 cyclicBarrier.await();时,意味着这些线程都准备完毕好,然后这些线程才 同时继续执行。
 */
public class CyclicBarrierTest {

    public static void main(String[] args) {
        runABCWhenAllReady();
    }
    
private static void runABCWhenAllReady() {
    int runner = 3;
    CyclicBarrier cyclicBarrier = new CyclicBarrier(runner);

    for (char runnerName='A'; runnerName <= 'C'; runnerName++) {
        final String rN = String.valueOf(runnerName);
        new Thread(new Runnable() {
            @Override
            public void run() { 
                try {
                    System.out.println(rN + " 已经准备好,等待其它线程准备");
                    cyclicBarrier.await(); // 当前运动员准备完毕,等待别人准备好
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
                System.out.println(rN + "开始跑"); // 所有运动员都准备好了,一起开始跑
            }
        }).start();
    }
}
}

运行结果:

 5、子线程完成某件任务后,把得到的结果回传给主线程

 关键接口:Callable

public class CallableTest {

    public static void main(String[] args) {
        doTaskWithResultInWorker();
    }
    
private static void doTaskWithResultInWorker() {
    //看出 Callable 最大区别就是返回范型 V 结果
    Callable<Integer> callable = new Callable<Integer>() {
        
        //这里需要重写call方法,而不是run方法
        @Override
        public Integer call() throws Exception {
            System.out.println("Task starts");
            Thread.sleep(1000);
            int result = 0;
            for (int i=0; i<=100; i++) {
                result += i;
            }
            return result;
        }
    };
    //Callable需要把对象放入FutureTask对象中,在把FutureTask对象放入Thread中,就可以启动一个线程
    FutureTask<Integer> futureTask = new FutureTask<>(callable);
    new Thread(futureTask).start();
    try {     
        System.out.println("Result: " + futureTask.get());
    } catch (Exception e) {
        e.printStackTrace();
    } }
}
/*输出结果:
 * Task starts
 * Result: 5050
 */

       这里我们可以学到,通过 FutureTask 和 Callable 可以直接在主线程获得子线程的运算结果,只不过需要阻塞主线程。当然,如果不希望阻塞主线程,可以考虑利用 ExecutorService,把 FutureTask 放到线程池去管理执行。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/999326.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

第5章 【MySQL】InnoDB数据页结构

5.1 不同类型的页简介 页是InnoDB 管理存储空间的基本单位&#xff0c;一个页的大小一般是 16KB 。InnoDB 为了不同的目的而设计了许多种不同类型的 页 &#xff0c;比如存放表空间头部信息的页&#xff0c;存放 Insert Buffer信息的页&#xff0c;存放 INODE 信息的页&#x…

《低代码指南》——低代码+AI,智能化的构建数字应用

LCHub低代码社区:对低代码平台在AI领域的应用及其成果进行了深入阐述。他强调,在AI时代,工程化的重要性不容忽视,同时,复杂系统建设和数据模型驱动开发也占据了核心地位。 顾伟不仅深入探讨了低代码零代码平台在业界的不同分类和使用场景,还详细解读了低代码平台的功能架…

区块链实验室(22) - go-sdk访问Fisco的案例

在前面的案例中(区块链实验室(21) - Go语言采用SDK访问Fisco的案例)&#xff0c;go程序调用FISCO SDK的参数固化在程序中&#xff0c;现将其改造如下。 package mainimport ("flag""fmt""log""github.com/FISCO-BCOS/go-sdk/client"&…

Ubuntu22.04安装及显卡驱动问题

自己有window系统&#xff0c;想搞个ubuntu系统玩玩 一、Ubuntu22.04安装 首先去官网下载ubuntu系统&#xff0c;我下载的是22.04 https://cn.ubuntu.com/download/desktop 准备一个启动盘制作器Rufus&#xff0c;将下载好的镜像烤制到U盘 制作完u盘&#xff0c;进入bios更…

第27章 非阻塞IO实验

上个章节中我们学习了阻塞IO&#xff0c;阻塞IO是通过等待队列来实现的&#xff0c;那么如何让驱动实现非阻塞呢&#xff1f;带着疑问&#xff0c;让我们开始本章节非阻塞IO的学习吧&#xff01; 27.1 非阻塞IO简介 应用程序可以使用如下所示示例代码来实现阻塞访问&#xff…

浅谈综合管廊智慧运维管理平台应用研究

贾丽丽 安科瑞电气股份有限公司 上海嘉定 201801 摘要&#xff1a;为提升综合管廊运维管理水平&#xff0c;实现管理的数字化转型&#xff0c;采用综合监测系统、BIMGIS 可视化系统、智能机器人巡检、结构安全监测等技术&#xff0c;搭建实时监控、应急管理、数据分析等多功能…

1254. 统计封闭岛屿的数目

二维矩阵 grid 由 0 &#xff08;土地&#xff09;和 1 &#xff08;水&#xff09;组成。岛是由最大的4个方向连通的 0 组成的群&#xff0c;封闭岛是一个 完全 由1包围&#xff08;左、上、右、下&#xff09;的岛。 请返回 封闭岛屿 的数目。 示例 1&#xff1a; 输入&…

健身用哪种耳机好、健身运动耳机推荐

对于和我一样热爱健身和运动的人来说&#xff0c;音乐就像一种调动情绪的"兴奋剂"&#xff0c;在戴上耳机、聆听着动感的音乐时&#xff0c;我们能够感受到肌肉的收缩&#xff0c;完全沉浸在自己的世界中。这种状态让我们的训练状态达到巅峰&#xff0c;快乐倍增。因…

c++ 学习 之 类内成员变量和成员函数分开存储

正文 一个空的类占多少内存 看代码 #define CRT_SECURE_NO_WARNINGS #include<iostream> using namespace std;// 在c 中类内成员变量和成员函数分开存储 class Person {};void test() {Person p;// 空对象占的内存为 1 &#xff0c;是为了区分空对象占内存的位置cout…

多线程-阻塞队列

在这篇博客中我们接触的队列都是非阻塞队列&#xff0c;比如PriorityQueue、LinkedList&#xff08;LinkedList是双向链表&#xff0c;它实现了Dequeue接口&#xff09;。 使用非阻塞队列的时候有一个很大问题就是&#xff1a;它不会对当前线程产生阻塞&#xff0c;那么在面对类…

飞行动力学 - 第19节-part2-尾旋及改出 之 基础点摘要

飞行动力学 - 第19节-part2-尾旋及改出 之 基础点摘要 1. 尾旋2. 尾旋进入3. 尾旋改出4. 参考资料 1. 尾旋 尾旋是一种绕垂直轴自动旋转、下降的特殊失速现象。 特点&#xff1a; 尾旋半径半个翼展长度旋转速度120 度/秒 2. 尾旋进入 不同型号飞机&#xff0c;其尾旋特点不…

2023-简单点-什么是protobuf?

protobuf mother: 谷歌 作用 序列化 人话&#xff1a; 存储数据的一种结构 优势在&#xff1f; 类型安全 易用性好 序列化/反序列性能好 兼容性好 不仅可以定义结构体&#xff0c;还可以定义rpc服务接口 劣势在&#xff1f; 可读性较差&#xff1a;没有schema的情况下&a…

element的el-select给下拉框添加背景

第一步 :popper-append-to-body"false" <el-selectv-model"value"placeholder"请选择":popper-append-to-body"false"><el-optionv-for"item in options":key"item.value":label"item.label&quo…

web自动化测试工具之Selenium的使用

Selenium的使用 Selenium概述工作原理应用场景安装浏览器驱动 基本使用安装Selenium模块注意点使用分析代码实现 常见方法driver对象定位标签元素与获取标签对象获取文本内容与属性值 使用无界面浏览器使用pyantomjs驱动设置chrome启动参数 其他操作窗口切换ifrme切换设置User-…

ERR_PNPM_NO_GLOBAL_BIN_DIR Unable to find the global bin directory

错误提示 ERROR Unable to find the global bin directory Run "pnpm setup"to create it automatically, or set the global-bin-dir setting, or the PNPM HOME env variable.The global bin directory should be in the PATH.错误&#xff0c;找不到全局bin目录 …

Web跨域问题

目录 一、引言二、跨域问题1.同源策略2.跨域3.出现跨域问题的情况 三、解决方案1.普通web&#xff0c;使用Filter过滤器2.SpringBoot项目&#xff0c;使用CrossOrigin注解 四、示例 一、引言 在web开发的过程中&#xff0c;因为前后端的分离我们经常会遇到跨域问题&#xff0c…

私人问答网站搭建指南:Ubuntu+Cpolar+Tipas

文章目录 前言2.Tipask网站搭建2.1 Tipask网站下载和安装2.2 Tipask网页测试2.3 cpolar的安装和注册 3. 本地网页发布3.1 Cpolar临时数据隧道3.2 Cpolar稳定隧道&#xff08;云端设置&#xff09;3.3 Cpolar稳定隧道&#xff08;本地设置&#xff09; 4. 公网访问测试5. 结语 前…

Spring MVC 六 - DispatcherServlet处理请求过程

前面讲过了DispatcherServlet的初始化过程&#xff08;源码角度的DispatcherServlet的具体初始化过程还没说&#xff0c;先放一放&#xff09;&#xff0c;今天说一下DispatcherServlet处理请求的过程。 处理过程 WebApplicationContext绑定在当前request属性上&#xff08;属…

SpringMVC多文件上传

文章目录 一、文件上传1.1 导入pom依赖1.2 配置文件上传解析器1.3 设置文件上传表单1.4 实现文件上传 二、文件下载三、多文件上传四、JRebel的使用 一、文件上传 1.1 导入pom依赖 <commons-fileupload.version>1.3.3</commons-fileupload.version><dependency…

springboot第40集:架构师写的代码,那叫一个优雅

事务的隔离性上&#xff0c;从低到高可能产生的读现象分别是&#xff1a;脏读、不可重复读、幻读。 脏读指读到了未提交的数据。 不可重复读指一次事务内的多次相同查询&#xff0c;读取到了不同的结果。 幻读师不可重复读的特殊场景。一次事务内的多次范围查询得到了不同的结果…