JavaEE初阶:多线程 - 编程

news2024/10/7 18:30:42

1.认识线程

我们在之前认识了什么是多进程,今天我们来了解线程。

一个线程就是一个 "执行流". 每个线程之间都可以按照顺讯执行自己的代码. 多个线程之间 "同时" 执行 着多份代码.

引入进程这个概念,主要是为了解决并发编程这样的问题。因为cpu进入了多核心的时代,要想进一步提高程序的执行速度,就需要充分的利用CPU的多核资源。

其实多进程编程,已经可以解决并发编程的问题了,它已经可以利用起来cpu多核资源了,但是问题是:
进程太重了(消耗资源多、速度慢)

创建一个进程,开销比较大。

销毁一个进程,开销也比较大。           

调度一个进程,开销还比较大。

说进程重,主要就是重在资源分配/回收上。

线程应运而生,线程也叫做"轻量级进程",
解决并发编程问题的前提下,让创建,销毁,调度的速度更快一些
线程为啥更"轻",把申请资源/释放资源的操作给省下了。

1.1 进程和线程的区别

进程是包含线程的。每个进程至少有一个线程存在,即主线程。

进程和进程之间不共享内存空间。同一个进程的线程之间共享同一个内存空间。

进程是系统分配资源的最小单位,线程是系统调度的最小单位。

光靠文字可能有点抽象,我们举个例子:

多进程:

 多线程:

在多进程中,启用了两套院子,那么启用的成本是比较大的,耗费的时间也是比较多的,但是在第二套中,院子和运输材料的通道都是公用的,那么就节省了成本。

在启动一个新的生产线时,就不需要重新启动一个院子,而是在原来的院子里启用,节省了许多的成本。

线程和进程的关系,是进程包含线程,
一个进程可以包含一个线程,也可以包含多个线程,但是不能没有。

对比下来,主要的优势在于:


只有第一个线程启动的时候,开销是比较大的,但是后续线程就省事了.,不论是启动还是关闭,耗费的资源都比启动/关闭一个进程要小。

同一个进程里的多个线程之间,共用了进程的同一份资源(主要指的是内存和文件描述符表)。这样这一部分资源就不需要重新启动或关闭。

操作系统,实际调度的时候,是以线程为单位进行调度的。

之前介绍的,,PCB里的状态,上下文,优先级,记账信息,都是每个线程有自己的。各自记录各自的但是同一个进程里的PCB之间, ,pid是一样的,内存指针和文件描述符表也是一样的。

那么既然线程这么好,可不可以无限制的在一个进程中增加线程呢?

并不可以,线程如果太多,核心数量有限,那么不少的开销就会浪费在线程调度上了,但是在多进程中就不会出现这样的状况。

线程模型,天然就是资源共享的.多线程争抢同一个资源(同一个变量)非常容易触发的.
进程模型,天然是资源隔离的.不容易触发.进行进程间通信的时候,多个进程访问同一个资源,可能会出问题.

 

1.2 多线程编程

本身关于线程的操作,操作系统提供的API,我们只需要学习Java提供的API就好了。

Java操作多线程,最核心的类 :Thread 

先在src下创建一个包,接着再创建一个类 

创建好主函数后,我们新建一个Thread的对象

Thread t = new Thread();

但是我们还需要一个类,新建一个Mythread类,并且重写run方法

class MyThread extends Thread{
    @Override
    public void run() {
            System.out.println("hello world");
    }
}

然后在main中,开始启动一个特殊的方法:

t.start;

完整的代码:

class MyThread extends Thread{
    @Override
    public void run() {
            System.out.println("hello world");
    }
}
public class ThreadDemo1 {
    public static void main(String[] args) {
        Thread t = new MyThread();
            t.start();
            System.out.println("hello main");
    }
}

这样一个代码,就新启动了一个线程,使得打印hello world和打印hello main是以完全不同的方式来完成的。

start这里的工作,就是创建了一个新的线程,新的线程负责执行重写过后的t.run。

具体的执行方法,就是start这个方法会调用操作系统的API,通过操作系统内核创建新线程的PCB,并且把要执行的指令交给PCB,当PCB被调度到CPU上执行的时候,也就执行到了线程run方法中的代码了。


通过具体的结果,,两个线程是同时进行的,并且可以看做是一次运行时无序,可能先打印world,也可能先打印main。

但是运行的时候不一定谁先谁后,
操作系统调度线程的时候,"抢占式执行",具体哪个线程先上,哪个线程后上,不确定,取决于操作系统调度器具体实现策略.
虽然有优先级,但是在应用程序层面上无法修改.
从应用程序(代码)的角度,看到的效果,就好像是线程之间的调度顺序是"随机"的一样.

内核里本身并非是随机.但是干预因素太多,并且应用程序这一层也无法感知到细节,就只能认为是随机的了。
为啥会有线程安全问题?罪魁祸首,万恶之源,就是这里的抢占式执行,随机调度。

start和run的区别

start是真正创建了一个线程(从系统这里创建的),线程是独立的执行流。


run 只是描述了线程要干的活是啥,如果直接再main中调用run,此时没有创建新线程,全是main线程一个人干活。相当于还是单线程。

可以使用jdk自带的工具jconsole查看当前的java进程中的所有线程. 


        


这里面就可以看到进程。同时进程中还有很多个线程。除了我们使用的,其他的都是JVM自带的

 

 1.3 多线程的五种创建方法

1.继承Thread,重写run方法

class MyThread extends Thread{
    @Override
    public void run() {
            System.out.println("hello world");
    }
}
public class ThreadDemo1 {
    public static void main(String[] args) {
        Thread t = new MyThread();
            t.start();
            System.out.println("hello main");
    }
}

也就是上面详细介绍的方法。

2.实现 Runnable 接口

class MyRunnable implements Runnable{
    @Override
    public void run() {
        System.out.println("hello thread");
    }
}
public class ThreadDemo2 {
    public static void main(String[] args) {
        Runnable runnable = new MyRunnable();
        Thread t = new Thread(runnable);
        t.start();
    }
}

Runnable 作用,是描述一个“要执行的任务”,然后把这个任务交给Thread来执行。

好处就是这样写可以解耦合,让线程和线程之间干的活要分开。

3.使用匿名内部类,继承 Thread

public class ThreadDemo3 {
    public static void main(String[] args) {
        Thread t = new Thread(){
            @Override
            public void run() {
                System.out.println("hello");
            }
        };
        t.start();
    }
}

这里面创建了一个Thread的子类,并且创建了子类的实例,让 t 引用指向该实例。

4.使用匿名内部类,实现 Runable

public class ThreadDemo4 {
    public static void main(String[] args) {
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("hello demo4");
            }
        });
        t.start();
    }
}

这个写法和2本质相同,只不过是把Runnable任务交给匿名内部类的语法。

此处是创建了一个类,实现Runnable,同时创建了类的实例,并且传给Thread的构造方法。

5.使用 Lambda 表达式(推荐)

public class ThreadDemo5 {
    public static void main(String[] args) {
        Thread t = new Thread(() -> {
            System.out.println("hello demo5");
        });
        t.start();
    }
}

使用lambda表达式来描述,直接把lambda传给Thread构造方法。

 

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

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

相关文章

JAVA集合-List

// 数组的缺点:每次使用都需要指定长度,掉率低,操作麻烦 // // 【java集合体系】:分类:6个接口,1个工具类 // 6个接口: 单列 :Collection,(父接口) // …

成功项目管理必备的五个关键因素

在任何组织中,良好的项目管理都是项目成功的首要标准。然而,一旦被淹没在无穷无尽的任务、持续的紧迫性和利益相关者相互冲突等情况中,管理好项目可能会让你感到力不从心。找出项目成功的关键因素并加以解决,就能避免一切失败。 …

浅谈学生宿舍安全用电管理存在的问题及防范措施

安科瑞 华楠 摘要:2009年,由一个小小的“热的快”引起的学院女生火灾事件,再一次敲响了大学生安全用电的警钟。而近年来随着教育事业的蓬勃发展和教育体制的推进,我国高等教育总规模在逐年增加。而目前高等学校能源使用主要是以电…

产品帮助中心的制作方法,保姆级0-1完整的实操教程!

帮助中心是一个产品的重要用户自助服务模块,包括各类产品相关信息,用以帮助用户快速理解和使用产品功能。对于我们产品开发和管理者而言,设计帮助中心的初衷是解决用户困惑,怎么设计帮助中心,才能让其价值尽可能的大&a…

【C语言】每日一题(寻找数组的中心下标)

寻找数组的中心下标,链接奉上 方法 暴力循环前缀和 暴力循环 ​​​​​​​思路: 依旧是我们的老朋友,暴力循环。 1.可以利用外层for循环,循环变量为数组下标,在循环内分别求出下标左边与右边的sum 2.在边界时讨论&…

伙伴云CEO戴志康:让数据和业务更好的互通展现,数字化与可视化同步进行

近日,零代码平台伙伴云产品「页面」功能正式上线,在【小伙开麦】直播间,CEO戴志康和产品经理共同为在线观众展示「页面」的功能与使用技巧,让小伙伴们大呼过瘾!好玩、好用、好看的「页面」,到底有哪些亮点&…

动手学深度学习-pytorch版本(一):引言 预备知识

参考引用 动手学深度学习利用 Anaconda 安装 pytorch 和 paddle 深度学习环境 pycharm 安装 0. 环境安装 利用 Anaconda 安装 pytorch 和 paddle 深度学习环境 pycharm 安装 1. 引言 机器学习(machine learning,ML)是⼀类强⼤的可以从经…

苏宁API的详细介绍及应用领域分析

随着电子商务的快速发展,越来越多的企业开始关注并积极开发自己的API接口,以提供更加高效、便捷和个性化的服务。苏宁作为中国领先的零售企业之一,也不例外。 1. 苏宁API是什么? 苏宁API是苏宁开放平台提供的一组接口集合&#x…

c++ 获取输入

获取键盘输入 #include <iostream> #include <limits>bool enter_state(int &pos) {unsigned int userInput;std::cout << "[info] 请选择: 0.主驾, 1.副驾, 2.后排" << std::endl;std::cout << "[info] 请输入&#xff1a;…

网络系统架构演变

1.系统架构演变 随着互联网的发展&#xff0c;网站应用的规模不断扩大。需求的激增&#xff0c;带来的是技术上的压力。系统架构也因此不断的演进、升级、迭代。从单一应用&#xff0c;到垂直拆分&#xff0c;到分布式服务&#xff0c;到SOA&#xff0c;以及现在火热的微服务架…

BFS(广度优先搜索)和DFS(深度优先搜索)的相关介绍解析

文章目录 DFS 和 BFSBFS 的应用一&#xff1a;层序遍历BFS 的应用二&#xff1a;最短路径最短路径例题讲解 DFS简介DFS原理分类与分析1. DFS连通性模型2. DFS思路应用-穷举求解问题 DFS&#xff08;深度优先搜索&#xff09;和 BFS&#xff08;广度优先搜索&#xff09;就像孪生…

java:反射

java代码运行的三个阶段 第一阶段&#xff1a;源代码编译为了字节码&#xff08;.class文件&#xff09; 第二阶段&#xff1a;就是类加载器将字节码文件加载到内存中 第三阶段&#xff1a;程序运行时 类加载 将 class 文件读取到内存中的过程&#xff0c;称为类加载 class 文…

LabVIEW使用边缘检测技术实现彩色图像隐写术

LabVIEW使用边缘检测技术实现彩色图像隐写术 隐写术是隐藏信息的做法&#xff0c;以隐瞒通信的存在而闻名。该技术涉及在适当的载体&#xff08;如图像&#xff0c;音频或视频&#xff09;中插入秘密消息。在这些载体中&#xff0c;数字图像因其在互联网上的广泛使用而受到青睐…

软件测试用例设计方法之因果图法

基本概念 因果图是一种利用图解法分析输入的各种组合情况&#xff0c;从而设计测试用例的方法&#xff0c;它适合于检查程序输入条件的各种组合情况。 设计测试用例的步骤 分析软件规格说明描述中, 哪些是原因(即输入条件或输入条件的等价类),哪些是结果(即输出条件), 并给每…

API 接口设计版本管理控制的规范:向后兼容的3个规则

一下文章内容来自之前做API接口时整理的一些内容&#xff0c;记录分享一下。 在HTTP和HTML发展的早期&#xff0c;有着这样一条规则&#xff1a;任何浏览器在遇到无法识别的元素或元素属性时&#xff0c;都应该像该标签不存在一样行事。这使得快速更新HTML的功能成为可能&…

通过代码实现窗口界面布局的方法

在QWidget窗口中添加相关事件resizeEvent()函数并编写相关功能代码&#xff1a; void Widget::resizeEvent(QResizeEvent *event) {QSize szui->plainTextEdit->size();ui->plainTextEdit->move(5,5);ui->pabpic->move(5,sz.height()5);ui->plainTextEd…

c++游戏小技巧12:输入输出流(存读档超全版)

目录 1.前言 2.输入/输出概念 3.流的概念 4.正文 1.标准I/O流 1.get()函数 2.getline()函数 3.read()函数 4.ignore()函数 5.gcount()函数 6.peek()函数 7.putback()函数 8.istream集合栗子 9.put()函数 10.write()函数 11.ostream集合栗子 2.文件I/O流 1.流…

开机时间分析

一、 开机各个阶段分析 1、Bootloader 阶段 Bootloader 阶段 该阶段分为(preloader 和Ik 阶段&#xff09; 从按电源键振动到 kernel 开始启动&#xff0c;这部分高通平台暂无好的方法确认时间&#xff0c;可以从总时间推算大概时间。MTK 项目可以从bootprof 确认 2、kernel…

day18 内部类、包、垃圾回收机制

匿名内部类 // Animal a new Animal(){ // Override // public void eat() { // System.out.println("动物在吃饭"); // } // }; abstract class Animal{ public abstract void eat(); }

git 配置网络代理

提高 git 访问 github 速度 网络代理前提: 请开启代理&#xff08;梯子&#xff09;检查代理端口&#xff08;可能会有所不同&#xff09; 文章目录 1. git 配置参数列表命令&#xff1a;2. git 添加 http 代理3. git 取消 http 代理 1. git 配置参数列表命令&#xff1a; gi…