多线程基础部分

news2024/9/25 23:22:03

多线程基础部分

  • 1. 线程与进程的关系
    • 1.1 多线程启动
    • 1.2 线程标识
      • 1.2.1 Thread与Runnable
    • 1.3 线程状态
  • 2.线程池入门
  • 2.1 ThreadPoolExecutor
    • 2.2 创建线程池
    • 2.3 关闭线程池
  • 创建线程的几种方法
  • 参考

1. 线程与进程的关系

1个进程包含1个或多个线程。

1.1 多线程启动

线程有两种启动方式:实现Runnable接口;继承Thread类并重写run()方法。(这个是没有线程池的,后面会说到线程通过线程池实现的方式)

执行进程中的任务时才会产生线程,因此需要一种描述任务的方式,这可以由Runnable接口来提供。要想定义任务,需要实现Runnable接口并且重写run()方法,然后再将Runnable的实现对象作为参数传递给Thread类。

在这里插入图片描述
还可以采用继承Thread类并且重写run方法,然后调用start()启动线程。
在这里插入图片描述
通常情况下,实现Runnable接口然后启动线程是一个更好的选择,这可以提高程序的灵活性和扩展性,并且用Runnable接口描述任务也更容易理解。在后面的线程池调用中,也使用Runnable表示要执行的任务。

需要特别注意的是,执行start()方法的顺序不代表线程启动的顺序,在下面示例的ThreadTest中,我们按照顺序调用了8个线程的start()方法,但是线程的执行顺序并没有规律,而且每次运行的结果可能都不一样。

代码:
在这里插入图片描述
执行结果:
在这里插入图片描述
为什么会出现这样的运行结果呢?这主要是因为任务的执行靠CPU,而处理器采用分片轮询方式执行任务,所有的任务都是抢占式执行模式,也就是说任务是不排序的。可以设置任务的优先级,优先级高的任务可能会优先执行(多数时候是无效的)。任务被执行前,该线程处于自旋等待状态。

1.2 线程标识

Thread类用于管理线程,如设置线程优先级、设置Daemon属性、读取线程名字和ID、启动线程任务、暂停线程任务、中断线程等。
为了管理线程,每个线程在启动后都会生成一个唯一的标识符,并且在其生命周期内保持不变。当线程被终止时,该线程ID可以被重用。而线程的名字更加直观,但是不具有唯一性。

在这里插入图片描述
在这里插入图片描述
Name:Thread-0
id :8

1.2.1 Thread与Runnable

Runnable接口表示线程要执行任务。当Runnable中的run()方法执行时,表示线程在激活状态,run()方法一旦执行完毕,即表示任务完成,则线程将被停止。

1.3 线程状态

线程对象在不同的运行时期存在着不同的状态,在Thread类中通过一个内部枚举类State保存状态信息,了解线程状态对于并发编程非常重要。
在这里插入图片描述
Java中的线程存在6种状态,分别是NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED。我们可以通过Thread类中的Thread.getState()方法获取线程在某个时期的线程状态。在给定的时间点,线程只能处于一种状态。

  1. NEW状态
    在这里插入图片描述

  2. RUNNABLE状态
    在这里插入图片描述

  3. BLOCKED状态
    BLOCKED为阻塞状态,表示当前线程正在阻塞等待获得监视器锁。当一个线程要访问被其他线程synchronized锁定的资源时,当前线程需要阻塞等待。
    代码测试如下,在主函数中分别启动了两个线程,它们都需要获得object对象的监视器锁后执行任务。第一个线程启动后,首先获得了object监视器锁。由于在run()方法中使用了死循环while(true),因此第一个线程获得了object监视锁后不会释放,这导致第二个线程长期处于阻塞等待状态。
    第一个线程的状态经历了NEW→RUNNABLE状态的变化;第二个线程的状态经历了NEW→RUNNABLE→BLOCKED等几个状态的变化
    在这里插入图片描述
    在这里插入图片描述
    测试结果如下:

Thread-0:状态:NEW
Thread-1:状态:NEW
Thread-0:状态:RUNNABLE
Thread-1:状态:RUNNABLE
Thread-0:状态:RUNNABLE
Thread-1:状态:BLOCKED
主线程:main:状态:RUNNABLE

4.WAITING状态
// todo

2.线程池入门

线程池与数据库连接池非常相似,目的是提高服务器的响应能力。线程池可以设置一定数量的空闲线程,这些线程即使在没有任务时仍然不会释放。线程池也可以设置最大线程数,防止任务过多,压垮服务器。

2.1 ThreadPoolExecutor

ThreadPoolExecutor是应用最广的底层线程池类,它实现了Executor和ExecutorService接口。在如下类的描述中,列举了ThreadPoolExecutor的构造函数和最常用的几个方法。
在这里插入图片描述

2.2 创建线程池

下面创建一个线程池,通过调整线程池构造函数的参数来了解线程池的运行特性。把核心线程数设置为3,最大线程数设置为8,阻塞队列的容量设置为5。
(1)当要执行的任务数小于核心线程数时,直接启动与任务数相同的工作线程。

package com.company;

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class ThreadTest {
    public static void main(String[] args) {
        BlockingQueue<Runnable> bq = new LinkedBlockingQueue<>(5);

        ThreadPoolExecutor pool = new ThreadPoolExecutor(3, 8, 2000, TimeUnit.MILLISECONDS, bq);

        for (int i = 0; i < 2; i++) {
            pool.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getId() + " is running...");
                    try {
                        Thread.sleep(800);
                    } catch
                    (Exception e) {
                    }
                }
            });
        }

        pool.shutdown();
    }
}

(2)当任务数量大于核心线程数时,超过核心线程数的任务会自动加入阻塞队列中,直到把阻塞队列装满。
调整任务数量为5:for(int i=0;i<5;i++) {…},观察程序运行结果如下。前面的3个任务启动了3个线程并加入线程池,后面的两个任务加入阻塞队列,等待前面的3个任务执行完毕。等前面3个任务完成后,程序会从阻塞队列中取出后面两个任务,然后仍然使用核心线程执行。因此会发现执行最后两个任务的线程号与前面的相同

8 is running…
10 is running…
.9 is running…
10 is running…
.8 is running…

(3)继续增加任务数量为10:for(int i=0;i<10;i++) {…},观察程序的运行结果如下。仔细观察会发现一共启动了5个线程。为什么线程池中的工作线程为5呢?

原因如下:核心线程数为3,因此前面的3个任务会启动3个工作线程。阻塞队列数量为5,因此第4、5、6、7、8这5个任务会自动加入阻塞队列。这时阻塞队列已满,第9、10两个任务会再启动两个新线程。注意:现在的工作线程数量一共为5,小于线程池设置的最大线程数8

9 is running…
11 is running…
.12 is running…
.8 is running…
10 is running…
.9 is running…
11 is running…
.10 is running…
.12 is running…
.8 is running…

(4)继续增加任务数量为15:for(int i=0;i<15;i++) {…},观察程序的运行结果可以发现,当任务数大于“最大线程数+阻塞队列容量”时,会抛出RejectedExecutionException(拒绝执行任务)异常。当前线程池的设置参数,最大容量是8+5=13,当任务数超过13时,都会被拒绝

8 is running…
10 is running…
.12 is running…
.9 is running…
13 is running…
.11 is running…
.14 is running…
.15 is running…
.Exception in thread “main” java.util.concurrent.RejectedExecution
Exception:Task com.icss.pool.MyPool$1@1c7c054 rejected from…
12 is running…
.9 is running…
8 is running…
13 is running…
.10 is running…

2.3 关闭线程池

调用ThreadPoolExecutor的shutdown()方法或shutdownNow()方法,可以关闭线程池
在这里插入图片描述
shutdownNow()与shutdown()的主要区别是:shutdownNow()可以把已提交但是未执行的任务主动取消,并返回未执行的任务列表。

// Executor接口 TODO

创建线程的几种方法

创建线程一共有哪几种方法?

  1. 继承Thread类创建线程
  2. 实现Runnable接口创建线程
  3. 使用Callable和Future创建线程
  4. 使用线程池例如用Executor框架

第一个:
继承Thread类创建线程,首先继承Thread类,重写run()方法,在main()函数中调用子类实实例的start()方法。

public class ThreadDemo extends Thread {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + " run()方法正在执行");
    }
}

public class TheadTest {
    public static void main(String[] args) {
        ThreadDemo threadDemo = new ThreadDemo();     
        threadDemo.start();
        System.out.println(Thread.currentThread().getName() + " main()方法执行结束");
    }
}

结果:(谁前谁后不一定)
main main()方法执行结束
Thread-0 run()方法正在执行

第二个:
实现Runnable接口创建线程:首先创建实现Runnable接口的类RunnableDemo,重写run()方法;创建类RunnableDemo的实例对象runnableDemo,以runnableDemo作为参数创建Thread对象,调用Thread对象的start()方法。

public class RunnableDemo implements Runnable {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + " run()方法执行中");
    }

}
public class RunnableTest {
    public static void main(String[] args) {
        RunnableDemo  runnableDemo = new RunnableDemo ();
        Thread thread = new Thread(runnableDemo);
        thread.start();
        System.out.println(Thread.currentThread().getName() + " main()方法执行完成");
}

第三个:
使用Callable和Future创建线程:

  1. 创建Callable接口的实现类CallableDemo,重写call()方法。
  2. 以类CallableDemo的实例化对象作为参数创建FutureTask对象
  3. 以FutureTask对象作为参数创建Thread对象。
  4. 调用Thread对象的start()方法。
import java.util.concurrent.Callable;

public class CallableDemo implements Callable<Integer> {
    @Override
    public Integer call(){
        System.out.println(Thread.currentThread().getName() + " call()方法执行中");
        return 0;
    }
}

import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class CallableTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        FutureTask<Integer> futureTask = new FutureTask<Integer>(new CallableDemo());
        Thread thread = new Thread(futureTask);
        thread.start();
        System.out.println("返回结果 " + futureTask.get());
        System.out.println(Thread.currentThread().getName() + " main()方法执行完成");
    }
}


DEMO:
		// 组装考试成绩sheet信息
        FutureTask<List<List<String>>> scoreSheetTask = new FutureTask<>(() ->
                generateScoreAnswerSheet(courseName, courseSerial, scheduleTitle, scheduleSerial, studentInfoMap, studentExamList, checkList)
        );
        Thread scoreSheetThread = new Thread(scoreSheetTask);
        scoreSheetThread.start();
        // 组装考试明细-首次回答sheet的信息 考试明细-最高分数sheet的信息
        FutureTask<HashMap<String, List<List<String>>>> answerSheetTask = new FutureTask<>(() ->
                generateAnswerSheet(studentInfoMap, examList, examContentList, checkList)
        );
        Thread answerSheetThread = new Thread(answerSheetTask);
        answerSheetThread.start();
        // 组装能力评估明细sheet的信息
        FutureTask<List<List<String>>> assessmentSheetTask = new FutureTask<>(() ->
                generateAssessmentAnswerSheet(courseName, courseSerial, scheduleTitle, scheduleSerial, studentInfoMap, studentAssessmentList, checkList)
        );
        Thread assessmentSheetThread = new Thread(assessmentSheetTask);
        assessmentSheetThread.start();

第四个:
使用线程池例如用Executor框架: Executors可提供四种线程池,分别为:
newCachedThreadPool:创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
newFixedThreadPool :创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
newScheduledThreadPool :创建一个定长线程池,支持定时及周期性任务执行。
newSingleThreadExecutor :创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序执行。

下面以创建一个定长线程池为例进行说明

public class ThreadDemo extends Thread {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "正在执行");
    }
}

class TestFixedThreadPool {
    public static void main(String[] args) {
        //创建一个可重用固定线程数的线程池      
        ExecutorService pool = Executors.newFixedThreadPool(2);
        //创建实现了Runnable接口对象,Thread对象当然也实现了Runnable接口       
        Thread t1 = new ThreadDemo();
        Thread t2 = new ThreadDemo();
        Thread t3 = new ThreadDemo();
        Thread t4 = new ThreadDemo();
        Thread t5 = new ThreadDemo();
        //将线程放入池中进行执行        
        pool.execute(t1);
        pool.execute(t2);
        pool.execute(t3);
        pool.execute(t4);
        pool.execute(t5);
        //关闭线程池       
        pool.shutdown();
    }
}

result:
		pool-1-thread-1正在执行
		pool-1-thread-1正在执行
		pool-1-thread-2正在执行
		pool-1-thread-1正在执行
		pool-1-thread-2正在执行

参考

1.书籍《Java多线程与线程池技术详解》肖海鹏 牟东旭
2.牛客并发编程部分

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

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

相关文章

骨传导耳机是怎么传声的、骨传导耳机的优点是什么

要说这两年最火的蓝牙耳机是哪款&#xff0c;大火的骨传导耳机绝对可以名列前茅&#xff0c;那可真是运动健身、需长时佩戴耳机党的神器&#xff01;如果你是搞运动的、健身的&#xff0c;或者是需要长时间佩戴耳机上网课的学生党&#xff0c;那一副靠谱的骨传导耳机绝对是必不…

LVGL学习笔记7 - GD32平台优化

目录 1. 颜色深度 2. 更新disp_init 3. 更新disp_flush 4. 改为IPA更新数据 5. 死机问题 学习过程中发现GD32平台的显示效果不佳&#xff0c;而且会出现死机的问题&#xff0c;需要优化一下平台代码。 1. 颜色深度 修改颜色深度为32bit。 #define LV_COLOR_DEPTH 32 2.…

时序引擎架构和实例演练

一、时序引擎介绍 开务数据库时序引擎是一款功能丰富、高性能的时序引擎&#xff0c;专为物联网、工业互联网、数字能源、金融等场景设计并优化。它能让大量设备、数据采集器每天产生的高达 TB 甚至 PB 级的数据得到高效实时的处理&#xff0c;对业务的运行状态进行实时的监测、…

银行卡数据标签的列举与使用

银行卡三要素&#xff1a;银行卡号、姓名、身份证号&#xff0c;银行卡四要素是指银行卡号、姓名、身份证号、手机号。对于从事信贷风控的小伙伴来讲&#xff0c;并不陌生。 银行卡信息的应用可能更熟悉的是客户信息核验&#xff0c;也就是针对信贷客户审批额度发放之前&#x…

SpringCloud系列(七)最详细最全面详述统一网关 Gateway

有道词典上对 Gateway 有大门口, 门道, 通道以及计算机术语中的网关之意, 其实对于网关这个概念是很好理解的, 例如有这样高档的小区车库, 当开车经过闸口的时候会识别你的车牌号, 识别成功后会自动将你的车库门打开; 其实计算机中的网关也是如此, 在 Spring Cloud 中网关的实现…

【1 - 决策树 - 原理部分】菜菜sklearn机器学习

课程地址&#xff1a;《菜菜的机器学习sklearn课堂》_哔哩哔哩_bilibili 第一期&#xff1a;sklearn入门 & 决策树在sklearn中的实现第二期&#xff1a;随机森林在sklearn中的实现第三期&#xff1a;sklearn中的数据预处理和特征工程第四期&#xff1a;sklearn中的降维算法…

LOAM和SSL-SLAM

今天来水两个激光SLAM的相关框架的学习笔记。 一、LOAM 首先介绍scan-to-scan map-to-map scan-to-map之间的关系&#xff1a; 1.scan-to-scan匹配 即两帧激光雷达数据之间的匹配&#xff0c;目的是求得从起始帧A到目标帧B的相对平移量与旋转矩阵。目前来说scan-toscan中&a…

Elasticsearch搜索引擎

The Elastic Stack, 包括 Elasticsearch【搜索&#xff0c;分析】、 Kibana【可视化】、 Beats 和 Logstash【数据的搜集】&#xff08;也称为 ELK Stack&#xff09;。能够安全可靠地获取任何来源、任何格式的数据&#xff0c;然后实时地对数据进行搜索、分析和可视化。 Elati…

安装压缩包版mysql

一、mysql-8.0.21-winx64.zip解压 二、在解压后的目录下添加data目录 三、配置环境变量 win7&#xff1a; ​ 我的电脑–>属性–>高级系统设置–>高级–>环境变量 ​ 在下面系统变量中 ​ 新建 ​ 变量名&#xff1a;MYSQL_HOME ​ 变量值&#xff1a;E:\MySQL\my…

常用的接口安全性保障手段

http接口有哪些安全问题 数据被抓包窃取数据被恶意篡改数据被爬取泄漏Token授权机制 用户使用用户名密码登录后服务器给客户端返回一个Token&#xff08;通常是UUID&#xff09;&#xff0c;并将Token-UserId以键值对的形式存放在缓存服务器中。服务端接收到请求后进行Token验…

UG NX二次开发(C#)-曲线-NXOpen.Curve初探

系列文章目录 `` 例如:第一章 初探NXOpen.Curve类 文章目录 系列文章目录1.前言2.NXOpen.Curve2. NXOpen.Curve包含的子类3.曲线类型的获取4.将曲线对象转换为子类类型1.前言 介绍下NXOpen.Curve类、Curve类型的获取、一些创建曲线的封装方法(包括直线、样条曲线、圆锥曲线…

OSM数据内容解析

OSM数据内容解析 数据简介 OpenStreetMap&#xff08;简称OSM&#xff0c;中文是公开地图&#xff09;&#xff0c;这是一个网上地图协作计划&#xff0c;目标是创造一个内容自由且能让所有人编辑的世界地图。是一款由网络大众共同打造的免费开源、可编辑的地图服务。 OSM采…

成功实施APS生产排程系统,必须具备哪些条件?

在许多生产管理者眼中&#xff0c;生产作业计划是不重要的&#xff0c;如果我们只停留在小加工作坊的规模&#xff0c;大脑就能把一个月的订单、物料、资源记得清清楚楚&#xff0c;那么生产计划排程的必要性确实不太大&#xff0c;但事实上&#xff0c;随着生产规模的扩大&…

JDK1.8中HashMap的resize()方法详解

JDK1.8中HashMap的resize()方法详解 文章目录JDK1.8中HashMap的resize()方法详解[toc]一、概述二、源码解析三、元素迁移四、小结在学习本文之前&#xff0c;默认大家已经有了HashMap源码的前置知识。 「集合底层」深入浅出HashMap底层源码 一、概述 resize()方法的代码比较长…

OpenHarmony#深入浅出学习eTs#(四)登陆界面UI

本项目Gitee仓地址&#xff1a;深入浅出eTs学习: 带大家深入浅出学习eTs (gitee.com) 一、明确目标 经过前面两章的学习&#xff0c;大家对Super Visual应该有了一个较为简单的认识&#xff0c;这一章就把前面的知识点串一下&#xff0c;使用Ark UI(Super Visual)赖模仿一个Q…

浅谈权限系统在多利熊业务应用

作者 | 百度智能小程序团队 导读 本文首先引入多利熊业务介绍&#xff0c;引出多利熊业务建设权限系统的痛点&#xff0c;接着分别从权限系统模型、权限系统设计以及多利熊业务业务应用方面详细探讨了具体的方案和设计&#xff0c;最后对权限系统设计思考&#xff0c;对数据维度…

linux连接器脚本前奏-基于x86(一)

从今天开始进入正文,和讲解liteos一样,我们先从连接器脚本开讲。我们知道连接器脚本描述了编译输出程序的布局,那么linux内核编译输出的布局是怎么样的呢?听我慢慢道来,关于连接器脚本的大概使用用途,可以参见 liteos链接器脚本一 liteos链接器脚本二 这里先说明一下对于…

Python进行异步请求,实现多开任务

前言 本文是该专栏的第5篇,后面会持续分享python的各种干货知识,值得关注。 在工作中,你可能或多或少会接到这样一个任务需求。 给你一个任务队列,需要你进行多任务去实现处理,尤其在爬虫项目或者是使用selenium,pyppeteer等任务中比较常见,至于多线程和多进程那些,笔…

OpenCL 是什么

OpenCL 创建Program对象|极客笔记 文章目录 OpenCL标准什么是OpenCL OpenCL全称为Open Computing Language&#xff08;开放计算语言&#xff09;&#xff0c;先由Apple设计&#xff0c;后来交由Khronos Group维护&#xff0c;是异构平台并行编程的开放的标准&#xff0c;也是…

antd 时间类组件的国际化 locale 设置不生效 解决方案汇总

antd 时间类组件的国际化 locale 设置不生效&#xff0c;踩坑之路和解决办法 问题 如图所示&#xff0c;antd 时间类组件中英文混合显示&#xff1a; 初始配置代码如下&#xff1a; import ./index.css; import ./global.less;import { ConfigProvider } from antd; import…