线程基础和等待唤醒机制

news2024/11/26 11:45:13

一、基础

1、进程和线程

  1. 进程:进程是用来加载指令、管理内存、管理IO的,操作系统会以进程为单位分配系统资源(cpu、内存等资源),进程是资源分配的最小单位
  2. 线程:线程是操作系统cpu调度的最小单位,也称为轻量级线程(Lightweight Process, LWP)

c和c++是可以只有进程不需要线程,但是java开启jvm进程必须有线程,这是由实现机制决定的,例如nginx只有多进程的概念,有些程序只能启动一个进程(电脑管家、百度网盘),程序是一组指令的包装,当运行指令时创建进程,开始分配系统资源给进程,而线程是依赖与进程存在,进程可以没有线程,但是线程如果脱离进程只是抢占到了cpu时间片,做不了什么事

2、进程间的通信方式

  1. 管道:分为匿名管道(pipe)及命名管道(named pipe),匿名管道可用于具有亲缘关系的父子进程间的通信,命名管道除了具有管道所具有的功能外,它还允许无亲缘关系进程间的通信
  2. 信号(signal):信号是在软件层次上对中断机制的一种模拟,它是比较复杂的通信方式,用于通知进程有某事件发生,一个进程收到一个信号与处理器收到一个中断请求效果上可以说是一致的
  3. 消息队列(message queue):消息队列是消息的链接表,它克服了上两种通信方式中信号量有限的缺点,具有写权限的进程可以按照一定的规则向消息队列中添加新信息,对消息队列有读权限的进程则可以从消息队列中读取信息
  4. 共享内存(shared memory):可以说这是最有用的进程间通信方式,它使得多个进程可以访问同一块内存空间,不同进程可以及时看到对方进程中对共享内存中数据的更新,这种方式需要依靠某种同步操作,如互斥锁和信号量等
  5. 信号量(semaphore):主要作为进程之间及同一种进程的不同线程之间的同步互斥手段
  6. 套接字(socket):这是一种更为一般的进程间通信机制,它可用于网络中不同机器之间的进程间通信,应用非常广泛。同一机器中的进程还可以使用Unix domain socket(比如同一机器中mysql中的控制台mysql shell和mysql服务程序的连接),这种方式不需要经过网络协议栈,不需要打包拆包、计算校验、维护序号和应答等,比如纯粹基于网络的进程间通信肯定效率更高

3、cpu核心数和线程数的关系

 线程是cpu调度的最小单位。理论上一个cpu核心只能运行一个线程,核心数比线程数也就是1:1的关系,但intel引入超线程技术后产生了逻辑处理器的概念,使核心数和线程数形成1:2的关系,在java中提供了Runtime.getRuntime().availableProcessors(),可以获取当前系统的cpu核心数,注意这个核心线程数指的是逻辑处理器的个数

4、上下文切换(Context switch)

上下文切换是指CPU(中央处理单元)从一个进程或线程到另一个进程或线程的切换

上下文是CPU寄存器和程序计数器在任何时间点的内容

  • 寄存器是CPU内部的一小部分非常快的内存(相对于CPU内部的缓存和CPU外部较慢的RAM主内存),它通过提供对常用值的快速访问来加快计算机程序的执行。
  • 程序计数器是一种专门的寄存器,它指示CPU在其指令序列中的位置,并保存着正在执行的指令的地址或下一条要执行的指令的地址,这取决于具体的系统。

上下文切换可以更详细地描述为内核(即操作系统的核心)对CPU上的进程(包括线程)执行以下活动: 

  1. 暂停一个进程的处理,并将该进程的CPU状态(即上下文)存储在内存中的某个地方
  2. 从内存中获取下一个进程的上下文,并在CPU的寄存器中恢复它
  3.  返回到程序计数器指示的位置(即返回到进程被中断的代码行)以恢复进程。

要尽量避免上下文切换

5、并发和并行

  • 并发:指在同一时刻只能有一条指令执行,但是基于cpu时间片轮转机制,多个进程指令交替快速执行,在宏观上多个进程看似是同时执行的
  • 并行:指在同一时刻,多条指令在多个处理器上同时执行

并行只在多处理器系统中存在,并发无论是单处理器和多处理器系统中都存在

二、线程创建的几种方式

1、启动一个main线程jvm总共有多少个线程

public class OnlyMain {

    public static void main(String[] args) {
        // java虚拟机线程系统的管理接口
        ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
        // 不需要获取同步的monitor和synchronizer信息,仅仅获取线程和线程堆栈信息
        ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(false, false);
        // 遍历线程信息 仅打印线程id和线程名称信息
        for(ThreadInfo threadInfo : threadInfos) {
            System.out.println("["+threadInfo.getThreadId()+"]"+threadInfo.getThreadName());
        }
    }
}

打印结果:
[6]Monitor Ctrl-Break //监控Ctrl-Break中断信号的
[5]Attach Listener //内存dump,线程dump,类信息统计,获取系统属性等
[4]Signal Dispatcher // 分发处理发送给JVM信号的线程
[3]Finalizer // 调用对象finalize方法的线程
[2]Reference Handler //清除Reference的线程
[1]main //main线程,用户程序入口

 java支持多线程

2、线程的创建

Thread是java对线程的唯一抽象实现,Runnable只是对任务(业务逻辑)的抽象。Thread可以接受任意一个Runnable的实例并执行

/**
 * 线程的创建方式
 */
public class NewThread {

    private static class UseThread extends Thread {
        @Override
        public void run() {
            try {
                Thread.sleep(1000);
                System.out.println("I am extended Thread");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    private static class UseRunnable implements Runnable {
        @Override
        public void run() {
            System.out.println("I am implements Runnable");
        }
    }

    public static void main(String[] args) {
        UseThread useThread = new UseThread();
        useThread.start();

        UseRunnable useRunnable = new UseRunnable();
        new Thread(useRunnable).start();
        System.out.println("main end");
    }
}

 Thread源码中写了只有两种创建方式,至于FutureTask也算Runnable,线程池的话只能算线程的复用,而不能说是线程的创建方式

3、Callable、Future和FutureTask

上面的线程都只是异步执行任务没有返回值,当如果需要有返回值时就需要用到Callable

 

创建一个Thread需要传一个实现Runnable实例化的对象,而FutureTask对象既实现了Runnable接口,而且它的构造方法可以传入一个Callable的接口,这时候只需要创建实现Callable接口的对象,实现自己的逻辑代码,new一个FutureTask的构造方法接收实现Callable的实例对象,FutureTask的实例对象放到Thread对象构造方法中调start()方法,完成线程启动,后面线程启动完如果需要返回值,可以通过FutureTask对象的get()方法获取

public class UseFuture {

    private static class UseCallable implements Callable<Integer> {

        private int sum;

        @Override
        public Integer call() throws Exception {
            System.out.println("Callable子线程开始计算!");
            for(int i=0 ;i<5000;i++){
                if(Thread.currentThread().isInterrupted()) {
                    System.out.println("Callable子线程计算任务中断!");
                    return null;
                }
                sum=sum+i;
                System.out.println("sum="+sum);
            }
            System.out.println("Callable子线程计算结束!结果为: "+sum);
            return sum;
        }
    }

    public static void main(String[] args) throws InterruptedException, ExecutionException {
        UseCallable useCallable = new UseCallable();
        //包装
        FutureTask<Integer> futureTask = new FutureTask<>(useCallable);
        Random r = new Random();
        new Thread(futureTask).start();

        Thread.sleep(1);
        if(r.nextInt(100)>50){
            System.out.println("Get UseCallable result = "+futureTask.get());
        }else{
            System.out.println("Cancel................. ");
            futureTask.cancel(true);
        }
    }
}

三、线程的中断机制

线程有启动必然就有停止,如何停止这是一个问题,是强制停止,还是根据标识停止

1、强制停止

Thread类中stop()方法会强制停止线程,释放对象锁,比如一个累加操作,执行一半突然被强制结束,导致数据不一致,suspend()方法调用后不会释放占有的资源,而是进入睡眠状态,这样容易导致死锁,这些方法都有@Deprecated注解,标明已弃用

2、中断标志位

Thread类中的interrupt()、interrupted()、isInterrupted()方法可以合理按照中断标志位来执行自己的逻辑代码

 

  •  interrupt()方法正如注释中写的,只是设置中断标志位
  • interrupted()方法是判断当前线程的中断标志位是否为true,重置中断标志位为false
  • isInterrupted()方法是判断线程的中断标志位是否为true,不会清除线程标志位
public class HasInterruptException {

    private static class UseThread extends Thread {

        public UseThread(String name) {
            super(name);
        }

        @Override
        public void run() {
            // 如果设置了中断标志位就不会进入while循环 正常结束了
            while (!isInterrupted()) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    System.out.println(Thread.currentThread().getName()
                            +" in InterruptedException interrupt flag is "
                            +isInterrupted());
                    interrupt();
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()
                        + " I am extends Thread.");
            }
            //while 循环结束后才会打印本句
            System.out.println(Thread.currentThread().getName()
                    +" interrupt flag is "+isInterrupted());
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread endThread = new UseThread("HasInterruptEx");
        endThread.start();
        Thread.sleep(500);
        endThread.interrupt();
    }
}

最好不要用自定义标识位来中断线程,正如上面例子,如果线程发生了阻塞,调用sleep,只能等下次自定义标识判断,在jdk内部调用sleep()方法会抛出InterruptedException异常,内部是有检测中断标志位,如果自定义标识位耗费性能

3、run()方法和start()方法

在我们的java代码中,Thread只是一个实例对象,只有调用start()方法时才会真的与线程挂钩,run()方法的调用只是普通方法调用其实是一样的,一个线程start()方法只能调用一次,而run()方法可以调用多次

public class StartAndRun {
    public static class ThreadRun extends Thread{

        @Override
        public void run() {
            System.out.println("I am "+Thread.currentThread().getName());
        }
    }

    public static void main(String[] args) {
        ThreadRun threadRun = new ThreadRun();
        threadRun.setName("threadRun");
        threadRun.start();
//        threadRun.run();
        // threadRun.run();
    }
}

如果是调用start()方法则是打印I am threadRun,如果是调用run()方法则是打印I am main,由此可见,调用start()方法则是真的创建一个线程,而run()方法则是main线程对普通方法的调用

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

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

相关文章

用Linux模拟实现进度条

1.设置文件&#xff0c;以及创建makefile 2.make 的相关用法 make存在的目的就是为了在文件多的时候&#xff0c;gcc 文件名&#xff0c;你可能要输入很多次&#xff0c;但是make的存在&#xff0c;一句make指令就可以完成了。 process [生成文件] : 与之有关的文件。下一行就…

Sparse Input Novel View Synthesis

文章目录 1.《Vision transformer for nerf-based view synthesis from a single input image》【WACV2023】摘要动机方法实验 2.《SparseFusion: Distilling View-conditioned Diffusion for 3D Reconstruction》【CVPR23】动机Related workApproach总结 3.《NerfDiff: Single…

NetSuite ERP顾问的进阶之路

目录 1.修养篇 1.1“道”是什么&#xff1f;“器”是什么&#xff1f; 1.2 读书这件事儿 1.3 十年计划的力量 1.3.1 一日三省 1.3.2 顾问损益表 1.3.3 阶段课题 2.行为篇 2.1协作 2.2交流 2.3文档管理 2.4时间管理 3.成长篇 3.1概念能力 3.1.1顾问的知识结构 …

TCP缓冲区和4次挥手调优

目录 如何修改TCP缓冲区才能兼顾并发数量与传输速度&#xff1f; 四次挥手性能调优 1,为什么建立连接是三次握手&#xff0c;而关闭连接需要四次挥手呢? 2.四次挥手的流程,注意5个状态 3.主动方优化 4,被动方调优 最后 如何修改TCP缓冲区才能兼顾并发数量与传输速度&…

NUC972开发板学习过程

1、搭建linux环境的过程&#xff0c;设置交叉编译器的环境变量过程中会出现各种问题&#xff1b; 解决方法&#xff1a; 第一步、一定要Ubuntu系统换源&#xff0c;换源后sudo apt-get update&#xff1b; 第二步、sudo apt-get install lib32stdc6 第三步、使用 vim编辑器…

创新指南|连锁经营,先从单店盈利模型做起

随着数字化时代的到来&#xff0c;数据成为了企业创新的重要资源。有价值的数据的涌现和发展为企业提供了前所未有的机会。数据为创新带来新动力&#xff0c;创造了新产品和服务、产生了新的商业模式&#xff0c;并带来了新的创业机会。数据驱动的创新已成为许多企业创新的重要…

4.1.tensorRT基础(1)-概述

目录 前言1. tensorRT基础概述2. tensorRT补充知识2.1 什么是tensorRT&#xff1f;2.2 tensorRT特性2.3 tensorRT工作流程2.4 常见方案2.5 tensorRT库文件 总结 前言 杜老师推出的 tensorRT从零起步高性能部署 课程&#xff0c;之前有看过一遍&#xff0c;但是没有做笔记&#…

springboot+ElasticSearch+Logstash+Kibana实现日志采集ELK

ElasticSearchLogstashKibana日志管理 一、什么是ELK? ELK是Elasticsearch、Logstash、Kibana的简称&#xff0c;这三者是核心套件&#xff0c;但并非全部。一般情况下我们可以把日志保存在日志文件当中&#xff0c;也可以把日志存入数据库当中。但随着业务量的增加&#xf…

2.多线程-初阶(上)

文章目录 1. 认识线程&#xff08;Thread&#xff09;1.1 概念1.2 第一个多线程程序1.3 创建线程1.3.1方法1 继承 Thread 类1.3.2方法2 实现 Runnable 接口 1.4 多线程的优势-增加运行速度1.5 PCB、PID、进程和线程之间的关系 2. Thread(/θred/) 类及常见方法2.1 Thread 的常见…

《Maven实战》读后感

目录 一、一些思考1、为什么平时编写的Java项目也叫做Maven项目&#xff1f;2、平常的Java项目目录为什么长这样&#xff0c;可以改变目录结构吗&#xff1f;3、对于Maven项目来说&#xff0c;Maven能帮我们做什么&#xff1f;4、为什么一定需要Maven私服&#xff0c;不要行不行…

easyrecovery数据恢复软件2023免费版下载

easyrecovery数据恢复软件2023免费版下载是一款操作简单、功能强大数据恢复软件,通过easyrecovery可以从硬盘、光盘、U盘、数码相机、手机等各种设备中恢复被删除或丢失的文件、图片、音频、视频等数据文件。 EasyRecovery数据恢复软件是一款功能强大的数据恢复软件&#xff0c…

卡尔曼滤波的理解

看了B站up主DR_CAN讲的卡尔曼滤波&#xff08;链接&#xff09;。up讲的非常好&#xff0c;强烈推荐&#xff0c;看完终于明白了卡尔曼滤波的奥秘。下面是我对其中内容的注解&#xff0c;或者说自己的理解。大部分推导省略了&#xff0c;但保留了算法的思想脉络。 引入 首先看…

Verilog基础之十七、锁相环PLL

目录 一、前言 1.1 背景 1.2 PLL结构 二、工程设计 2.1 PLL IP核配置 2.2 设计代码 2.3 测试代码 2.4 仿真结果 2.5 常见问题 一、前言 1.1 背景 若将一个FPGA工程看做一个人体&#xff0c;时钟的重要性丝毫不亚于心脏对于人体的重要性&#xff0c;时钟的每一个周期对…

支付、购物车、搜索、文件上传、登录、还款、订单测试怎么做?

支付功能怎么测试&#xff1a;1、从功能方面考虑&#xff1a; 1&#xff09;、正常完成支付的流程&#xff1b; 2&#xff09;、支付中断后继续支付的流程&#xff1b; 3&#xff09;、支付中断后结束支付的流程&#xff1b; 4&#xff09;、单订单支付的流程&#xff1b; 5&am…

【无标题】(前沿)

Java编程语言 目前为止最流行的 是Java编程语言 但是编程与语言有很多中php。phyone。 c c. c# java html. css javascript vue() 说到计算机有很多同学会说&#xff0c;就有很多人会说35的节点&#xff0c;我问一下同学们现在哪一个行业&#xff0c;是没有35岁的节点&#x…

7.5 SpringBoot 拦截器Interceptor实战 统一角色权限校验

文章目录 前言一、定义注解annotation二、拦截角色注解1. 在拦截器哪里拦截&#xff1f;2. 如何拦截角色注解&#xff1f;3. 角色如何读取?4. 最后做角色校验 三、应用&#xff1a;给管理员操作接口加注解四、PostMan测试最后 前言 在【7.1】管理员图书录入和修改API&#xf…

c语言指针进阶(二)

目录 引言 函数指针数组 指向函数指针数组的指针 回调函数 引言 大家好&#xff0c;我是c语言boom成家宝&#xff0c;今天博主带来的依然是指针的进阶讲解。上一篇博客博主有介绍指针&#xff0c;数组指针&#xff0c;指针数组&#xff0c;以及函数指针的概念以及应用&…

【Azure】Azure成本管理:规划、监控、计算和优化成本 [文末送书]

开篇先来一个不是总结的总结&#xff1a;平衡成本与性能始终是一个重大挑战。&#xff08;此处省略各种场景的解释&#xff09; 文章目录 前言一、Azure 成本管理工具1.1 什么是成本管理1.2 成本管理的主要功能 二、Azure 中可能影响成本的因素2.1 影响成本的因素2.1.1 资源类型…

leetcode|math|9.172.69.50.

9. Palindrome Number to_string 就行 172. Factorial Trailing Zeroes 不能直接乘起来&#xff0c;会overflow&#xff01;&#xff01; 166! 就是要找166乘到1一共有几个5。5&#xff0c;10&#xff0c;15&#xff0c;25...都算。166/5就是算一共有几个5。但是25其实贡献了…

【周末闲谈】感受AI时代魅力,创意无界限

i 个人主页&#xff1a;【&#x1f60a;个人主页】 系列专栏&#xff1a;【❤️周末闲谈】 文章目录 前言人工智能的应用领域问题求解逻辑推理与定理证明自然语言处理智能信息检索技术专家系统 人工智能的三大短板展望未来从专用智能向通用智能发展从人工智能向人机混合智能发展…