Java 多线程(01)

news2024/7/6 19:18:53

运行一个 Java 程序就是跑一个 Java 进程,该进程至少有一个线程即主线程,而 main 方法就是主线程的入口;

一、常见多线程名词解释

并发:一个 CPU 核心上,通过快速轮转调度的方式,执行多个进程,微观上 "有先有后";

并行:两个进程同时分别在两个 CPU 核心上执行,微观上 "同时进行";

进程:进程是资源分配的最小单位;

线程:⼀个线程就是⼀个 "执⾏流",每个线程之间都可以按照顺序执行自己的代码,线程是调度执行的最小单位;

终止线程:想要终止线程本质就是让 run 方法执行结束;

线程等待:让一个线程等待另一个线程执行结束,再继续执行;

线程安全:让多个线程同时执行同样的代码,出现的 bug;

进程和线程的区别:

1. 进程包含线程,一个进程可以有一个或者多个线程;

2. 进程和线程都是实现并发编程的,但线程更轻量,更高效;

3. 同一个进程的线程之间,共用同一份资源,如内存,硬盘等,即线程省去了申请资源的开销; 

4. 进程之间是有独立性的,同一个进程内的线程之间可能会相互影响(存在线程安全,线程异常等问题);

5. 进程是分配资源的最小单位,线程是调度执行的最小单位;

二、创建线程的几种方式

1. 继承 Thread(java.long包下) 类,重写 run 方法

(可通过单独定义一个类,或者使用匿名内部类),单独创建一个类不常用;

    public static void main(String[] args) {
        Thread t1 = new Thread(){
            @Override
            public void run() {
                System.out.println("通过匿名内部类创建线程");;
            }
        };
        t1.start();
    }

Thread 类:是 JVM 用来管理线程的⼀个类,每个线程都有⼀个唯⼀的 Thread 对象与之关联;

Thread 类中的 run 方法 是对 Runnable 的重写,run 方法描述了线程的入口,每个线程都是一个独立的执行流,一个线程从入口方法开始执行,即 run 方法,run 方法执行完毕,线程就会结束;

start 方法 用于开启线程,负责调用系统 API,系统创建线程,再调用 run 方法执行线程 ;直接调用 run 方法是不会创建线程的

常用构造方法如下:

常用其他方法如下:

2. 实现 Runnable(java.long包下) 接口,重写 run 方法;

 可通过单独创建一个类,或者匿名内部类,或者 lambda 表达式实现,后两者更为常用;

    public static void main(String[] args) {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("通过匿名内部类创建线程");
            }
        });
        Thread t2 = new Thread(() -> System.out.println("通过 lambda 表达式创建线程"));
    }

Runnable 接口表示一个 "可执行的任务";由于 Runnable 接口中没有 start 方法,start 方法是 Thread 类的,故只实现 Runnable 接口的类的对象不能通过直接调用 start 方法启动线程,要通过 Thread 类的构造方法实现;

3. 实现 Callable 接口(JUC,java.util.concurrent包下),重写 call 方法

可通过单独创建一个类,或者匿名内部类

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Callable<Integer> callable = new Callable<Integer>(){
            @Override
            public Integer call() throws Exception {
                return 1;
            }
        };
        FutureTask<Integer> futureTask = new FutureTask<>(callable);
        Thread t = new Thread(futureTask);
        t.start();
        int result = futureTask.get();
        System.out.println(result);
    }

上述代码的执行流程为:

1. 创建⼀个匿名内部类,实现 Callable 接⼝,Callable 带有泛型参数,泛型参数表示返回值的类型;

2. 重写 Callable 的 call 方法,并返回 1;

3. 把 callable 实例使用 FutureTask 包装⼀下,创建线程,线程的构造方法传入 FutureTask,此时新线程就会执行 FutureTask 内部的 Callable 的 call 方法,完成计算,计算结果会放到 FutureTask 对象中;

4. 在主线程中调用 futureTask.get() 方法能够阻塞等待新线程计算完毕,并获取到 FutureTask 中的 结果;

理解 Callable 接口:

Callable 和 Runnable 相对,都是描述⼀个 "任务",Callable 描述的是带有返回值的任务,Runnable 描述的是不带返回值的任务,Callable 通常需要搭配 FutureTask 来使用,FutureTask用来保存 Callable 的返回结果,因为 Callable 往往是在另⼀个线程中执行的,啥时候执行完并不确定,FutureTask 就可以负责这个等待结果出来的工作;

4. 使用线程池创建线程

使用线程池创建线程的优势:减少每次启动线程,销毁线程的损耗;

1. Executors 创建线程池的几种常见方式(其底层是使用 ThreadPoolExecutor 创建)

1)newFixedThreadPool:创建固定线程数的线程池(也为最大线程数);

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        AtomicInteger count = new AtomicInteger(0);
        ExecutorService service = Executors.newFixedThreadPool(3);
        service.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + " 执行第 " + count.incrementAndGet() + " 个任务");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        Future<?> submit = service.submit(new Callable<AtomicInteger>() {
            @Override
            public AtomicInteger call() throws Exception {
                System.out.println(Thread.currentThread().getName() + " 执行第 " + count.incrementAndGet() + " 个任务");
                int i = 10;
                while(i-- > 0) {
                    count.incrementAndGet();
                }
                return count;
            }
        });
        System.out.println(submit.get());
    }

上述代码中, 创建出固定包含 3 个线程的线程池,并通过 submit 方法,execute 方法分别分别执行了一个任务;那着两个方法有什么区别呢?


execute 方法只能执行 Runnable 任务,返回值为 void,因为 run 方法没有返回值;而 submit 方法即可以执行 Runnable 任务,也可以执行 Callable 任务,返回值为 Future,因为 Callable 时可以有返回值的;

当任务数超过线程池中的固定线程数会怎们样呢? 修改上述代码进行测试:

    public static void main(String[] args) {
        AtomicInteger count = new AtomicInteger(0);
        ExecutorService service = Executors.newFixedThreadPool(2);
        service.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + " 执行第 " + count.incrementAndGet() + " 个任务");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        service.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + " 执行第 " + count.incrementAndGet() + " 个任务");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        service.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + " 执行第 " + count.incrementAndGet() + " 个任务");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
    }

 

可以看出,固定线程数为 2,只有两个线程执行任务,第三个任务会等某个线程执行完任务空闲了才会执行;

2)newCachedThreadPool:创建线程数目动态增长的线程池;(即根据任务数控制线程数量)

    public static void main(String[] args) {
        AtomicInteger count = new AtomicInteger(0);
        ExecutorService service = Executors.newCachedThreadPool();
        service.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println("执行第 " + count.incrementAndGet() + " 个任务");
            }
        });
        service.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println("执行第 " + count.incrementAndGet() + " 个任务");
            }
        });
    }

但这并不一定意味着,来多少任务就开启多少线程,有可能某个线程的任务执行完毕,当任务来临时,就不会在创建新的线程,直接使用空闲线程执行任务,当所有线程都不空闲时,才会创建新的线程,即线程数是动态变化的;可以自己试试,多执行几个任务,查看线程名称是否有一样的,然后再试试多执行几个任务,并休眠几秒,再查看线程名;

3)newSingleThreadExecutor:创建只包含单个线程的线程池;

    public static void main(String[] args) {
        AtomicInteger count = new AtomicInteger(0);
        ExecutorService service = Executors.newSingleThreadExecutor();
        service.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + " 执行第 " + count.incrementAndGet() + " 个任务");
            }
        });
        service.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + " 执行第 " + count.incrementAndGet() + " 个任务");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });service.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + " 执行第 " + count.incrementAndGet() + " 个任务");
            }
        });
    }

可以看到线程名都是一样的,说明是同一个线程在执行不同的任务;

2. 通过 ThreadPoolExecutor 创建线程池

ThreadPoolExecutor 构造方法:

参数说明:

int corePoolSize:核心线程数,线程池的最少线程数;

int maximumPoolSize:最大线程数,线程池的最多线程数;

long keepAliveTime:没有任务是的最长存活时间;

TimeUnit unit:long keepAliveTime 的时间单位;

BlockingQueue<Runnable> workQueue:阻塞队列,用于存放线程池中的任务;

ThreadFactory threadFactory:线程工厂类,负责创建线程,并对线程的属性进行设置;

RejectedExecutionHandler handler:线程池拒绝策略,让任务数量上限之后,并且阻塞队列也满了,此时对新任务的处理方式;(关于线程池拒绝策略的详细讲解:点击这里)

如果将线程池比作一个公司的话,那么 corePoolSize 就指的是公司里的正式员工,maximumPoolSize 就是公司里所有员工(正式员工 + 实习生),线程的总数目会在 [corePoolSize, maximumPoolSize] 内变化,即员工最少的情况下公司都是正式工,员工最多的情况下公司的实习生都招满了;keepAliveTime 表示非核心线程,允许的最大空闲时间,unit 表示keepAliveTime 的时间单位,可以理解为公司业务空闲时实习生的最长摸鱼时间,(过了这个时间,实习生就会被裁掉,对应非核心线程就会自动销毁)

public class Demo2 {
    public static void main(String[] args) throws InterruptedException {
        ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 3, 10, TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(1), new ThreadPoolExecutor.AbortPolicy());
        MyRunnable runnable1 = new MyRunnable();
        MyRunnable runnable2 = new MyRunnable();
        MyRunnable runnable3 = new MyRunnable();
        MyRunnable runnable4 = new MyRunnable();
        MyRunnable runnable5 = new MyRunnable();
        executor.execute(runnable1);
        executor.execute(runnable2);
        executor.execute(runnable3);
        executor.execute(runnable4);

        executor.execute(runnable5);
    }
}

class MyRunnable implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + " 正在执行 " + System.currentTimeMillis());
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}

 上述代码创建了一个  核心线程数为 2,最大线程数为 3,非核心线程最长空闲时间为 8s,阻塞队列容量为 1,拒绝策略为直接抛出异常  的线程池;

然后创建了五个任务,前两个被执行的任务会使用核心线程来执行,第三个被执行的任务会使用非核心线程来执行,由于最大核心线程数为 3,此时已经达到上限,故第四个任务会被放入阻塞队列中,而阻塞队列的最大容量为 1,故此时阻塞队列也满了,第五个任务来临时,会执行拒绝策略,此处的拒绝策略为直接抛出异常;在某个线程执行完任务之后,就会继续执行阻塞队列中的任务;

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

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

相关文章

linux文件编程api: creat

1.基本信息 功能 创建新文件 头文件 #include<fcntl.h> 函数形式 int creat(const char *pathname, mode_t mode); 返回值 如果成功&#xff0c;则返回文件描述符号 如果失败&#xff0c;则返回-1 参数 pathname: 创建的文件名 mode: 新建文件时&#xff0c;文件权限…

Android LAME原生音频

前言 我想大家都做过录音的功能吧&#xff0c;首先想到的是不是MediaRecorder&#xff1f;今天我们不用MediaRecorder&#xff0c;而是使用LAME库自己编译音频编码模块&#xff0c;很明显&#xff0c;这个需要用到NDK。凡是涉及到音视频编解码这块的&#xff0c;都需要用到And…

BUG: VS Code C++输出中文乱码

BUG: VS Code C输出中文乱码 环境 Windows 11 VS Code 编辑器详情 在Windows 使用 cout 函数输出中文时出现乱码 问题的原因在cmd的显示编码和c程序编码的不同。cmd默认的是gbk编码&#xff0c;而VS Code 软件的CMD终端默认是utf-8编码&#xff0c;因而在输出中文文本时会出…

QQ名片满级会员展示生成HTML源码

源码介绍 QQ名片满级会员展示生成HTML源码&#xff0c;源码由HTMLCSSJS组成&#xff0c;双击html文件可以本地运行效果&#xff0c;也可以上传到服务器里面&#xff0c;保存素材去选择QQ个性名片-选择大图模板-把图上传照片墙即可 源码效果 源码下载 蓝奏云&#xff1a;http…

唯众云课堂:领航智慧教育,赋能职教未来,打造高效人才培养新平台

随着《中国智慧教育发展报告 2023》的发布&#xff0c;智慧教育被正式定义为数字教育发展的高级阶段。然而&#xff0c;各职院在智慧教育的发展道路上&#xff0c;往往面临着诸多挑战&#xff0c;如缺乏一体化教学平台、优质教学资源不足等。唯众凭借深厚的产业洞察与教育实践经…

Pytorch中的torch.save()文件保存格式探索以及mmdetection加载预训练模型参数对不齐和收到意外参数报错解决方案

使用mmdetection时遇到的问题比较多&#xff0c;首先要对自己要使用的预训练模型有一定的了解&#xff0c;并且懂得使用各种分类模型时不同的模型不同任务执行阶段需要参数上的对其。&#xff08;比如mask-rcnn和它的三个头之间的参数&#xff09;。 首先&#xff0c;谈谈torc…

基于SpringBoot设计模式之结构型设计模式·适配器模式

文章目录 介绍开始使用委托的适配器&#xff08;媒体播放器&#xff09;架构图定义被适配者定义需求接口定义适配者 使用继承的适配器&#xff08;手机充电接口&#xff09;架构图定义被适配者定义需求接口定义适配者 测试样例 总结优点缺点 介绍 在程序世界中&#xff0c;经常…

Python | Leetcode Python题解之第115题不同的子序列

题目&#xff1a; 题解&#xff1a; class Solution:def numDistinct(self, s: str, t: str) -> int:m, n len(s), len(t)if m < n:return 0dp [[0] * (n 1) for _ in range(m 1)]for i in range(m 1):dp[i][n] 1for i in range(m - 1, -1, -1):for j in range(n …

词法与语法分析器介绍

概述 词法和语法可以使用正则表达式和BNF范式表达&#xff0c;而最终描述文法含义的事状态转换图 Lex与YACC 词法分析器Lex 词法分析词Lex&#xff0c;是一种生成词法分析的工具&#xff0c;描述器是识别文本中词汇模式的程序&#xff0c;这些词汇模式是在特殊的句子结构中…

再见PS,Canva Create正式上线

再见&#xff0c;Photoshop&#xff01; Canva Create 正式上线&#xff0c;太疯狂了&#xff01;&#xff01; Canva是一款著名的免费在线AI图像生成器 构想你的创意&#xff0c;然后将其添加到你的设计中。使用最佳的AI图像生成器&#xff0c;观察你的文字和短语变换成美丽…

探索Java的DNA-JVM字节码深度解析

引言 在Java的世界里&#xff0c;JVM&#xff08;Java虚拟机&#xff09;是我们程序运行的心脏。而字节码&#xff0c;作为JVM的血液&#xff0c;携带着程序的执行指令。今天&#xff0c;我们将深入探索Java字节码的奥秘&#xff0c;一窥JVM如何将人类可读的代码转化为机器可执…

【香橙派AIpro】开箱测评

1.板子开箱 哟&#xff0c;看起来还不错哦&#xff01;&#xff01;&#xff01; 收货清单&#xff1a; 主板*1 1.5m数据线*1 充电头*1 1.1.充电头 近65W的充电头&#xff0c;不错不错。 1.2.主板 1.2.1.上面 哇噢&#xff0c;还送了2.4/5G的WiFi和蓝牙天线。 emm&#xf…

【NumPy】关于numpy.argsort()函数,看这一篇文章就够了

&#x1f9d1; 博主简介&#xff1a;阿里巴巴嵌入式技术专家&#xff0c;深耕嵌入式人工智能领域&#xff0c;具备多年的嵌入式硬件产品研发管理经验。 &#x1f4d2; 博客介绍&#xff1a;分享嵌入式开发领域的相关知识、经验、思考和感悟&#xff0c;欢迎关注。提供嵌入式方向…

计算机专业必考之计算机指令设计格式

计算机指令设计格式 例题&#xff1a; 1.设相对寻址的转移指令占3个字节&#xff0c;第一字节为操作码&#xff0c;第二&#xff0c;第三字节为相对偏移量&#xff0c; 数据在存储器以低地址为字地址的存放方式。 每当CPU从存储器取出一个字节时候&#xff0c;自动完成&…

通过Zerossl给IP申请免费SSL证书, 实现https ip访问

参考通过Zerossl给IP申请免费SSL证书 | LogDicthttps://www.logdict.com/archives/tong-guo-zerosslgei-ipshen-qing-mian-fei-sslzheng-shu

【Linux-LCD 驱动】

Linux-LCD 驱动 ■ Framebuffer 简称 fb■ LCD 驱动程序编写■ 1、LCD 屏幕 IO 配置■ 2、LCD 屏幕参数节点信息修改■ 3、LCD 屏幕背光节点信息■ 4、使能 Linux logo 显示 ■ 设置 LCD 作为终端控制台■ 1、设置 uboot 中的 bootargs■ 2、修改/etc/inittab 文件 ■ LCD 背光…

亚马逊高效广告打法及数据优化,亚马逊高阶广告打法课

课程下载&#xff1a;https://download.csdn.net/download/m0_66047725/89342733 更多资源下载&#xff1a;关注我。 课程内容&#xff1a; 001.1-亚马逊的广告漏斗和A9算法的升级变化.mp4 002.2-流量入口解析和广告的曝光机制.mp4 003.3-标签理论 .mp4 004.4-不同广告类…

小程序内使用路由

一:使用组件 1)创建组件 2)在需要的页面的json/app.json可实现局部使用和全局使用 在局部的话,对象内第一层,window配置也是第一层,而在全局配置也是在第一层,window在window对象内.第二层.内部执行遍历不一样. 3)页面使用 上述所写可实现在页面内使用组件.效果是页面内可以将…

预热 618,编程好书推荐——提升你的代码力

文章目录 &#x1f4cb;前言&#x1f3af;编程好书推荐&#x1f4d8; Java领域的经典之作&#x1f40d; Python学习者的宝典&#x1f310; 前端开发者的权威指南&#x1f512; 并发编程的艺术&#x1f916; JVM的深入理解&#x1f3d7; 构建自己的编程语言&#x1f9e0; 编程智…

SolidWorks教育版 学生使用的优势

在工程技术领域的学习中&#xff0c;计算机辅助设计软件&#xff08;CAD&#xff09;如SolidWorks已经成为学生掌握专业知识和技能的必要工具。SolidWorks教育版作为专为教育机构和学生设计的版本&#xff0c;不仅提供了与商业版相同的强大功能&#xff0c;还为学生带来了诸多独…