JavaEE初阶:多线程 - 编程

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{@Overridepublic void run() {System.out.println("hello world");}
}

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

t.start;

完整的代码:

class MyThread extends Thread{@Overridepublic 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{@Overridepublic 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{@Overridepublic 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(){@Overridepublic 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() {@Overridepublic 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.hqwc.cn/news/67164.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

APP外包开发的iOS开发语言

学习iOS开发需要掌握Swift编程语言和相关的开发工具、框架和技术。而学习iOS开发需要时间和耐心,尤其是对于初学者。通过坚持不懈的努力,您可以逐步掌握iOS开发技能,构建出功能丰富、优质的移动应用。今天和大家分享学习iOS开发的一些建议方法…

【广州华锐视点】AR电力职业技能培训系统让技能学习更“智慧”

随着科技的发展,教育方式也在不断地进步和创新。其中,增强现实(AR)技术的出现,为教育领域带来了全新的可能。AR电力职业技能培训系统就是这种创新教学方法的完美实践,它将虚拟与现实相结合,为学生提供了一个沉浸式的学…

JavaScript版本ES5/ES6及后续版本

JavaScript简史 1995: Brendan Eich在短短10天内创建了JavaScript的第一个版本。它被称为摩卡,但已经具备了现代JavaScript的许多基本特性! 1996: 为了吸引Java开发人员,Mocha先是更改为LiveScript,然后又更改为Ja…

UI设计师个人工作感悟5篇

UI设计师个人工作感悟一 工作一年了,结合我自身谈谈UI设计的重要性。现在主流的论坛建站程序有两种 Phpwind 和Discuz(Phpwind被阿里巴巴收购 Discuz被腾讯收购这两个论坛程序都是开源免费的),利用这两种程序我都分别建立过论坛,我第一次用的…

R语言APSIM模型高级应用及批量模拟

随着数字农业和智慧农业的发展,基于过程的农业生产系统模型在模拟作物对气候变化的响应与适应、农田管理优化、作物品种和株型筛选、农田固碳和温室气体排放等领域扮演着越来越重要的作用。APSIM (Agricultural Production Systems sIMulator)模型是世界知名的作物生…

【Spring专题】Spring之底层架构核心概念解析

目录 前言前置知识课程内容一、BeanDefinition:图纸二、BeanDefinitionReader:图纸注册器——Spring工厂基础设施之一2.1 AnnotatedBeanDefinitionReader2.2 XmlBeanDefinitionReader2.3 ClassPathBeanDefinitionScanner基本介绍总结使用示例 三、BeanFa…

移动端预览指定链接的pdf文件流

场景 直接展示外部系统返回的获取文件流时出现了跨域问题: 解决办法 1. 外部系统返回的请求头中调整(但是其他系统不会给你改的) 2. 我们系统后台获取文件流并转为新的文件流提供给前端 /** 获取传入url文件流 */ GetMapping("/get…

一百五十二、Kettle——Kettle9.3.0本地连接Hive3.1.2(踩坑,亲测有效)

一、目的 由于先前使用的kettle8.2版本在Linux上安装后&#xff0c;创建共享资源库点击connect时页面为空&#xff0c;后来采用如下方法&#xff0c;在/opt/install/data-integration/ui/menubar.xul文件里添加如下代码 <menuitem id"file-openZiyuanku" label&…

聊聊RedisTemplate的各种序列化器

[版权申明] 非商业目的注明出处可自由转载 出自&#xff1a;shusheng007 文章目录 概述序列化器作用和原理JDK 序列化方式多一点 String 序列化方式JSON 序列化方式 总结源码 概述 在SpringBoot中使用redis基本上都是通过Spring Data Redis&#xff0c;那就不得不说RedisTempl…

11 个 Python 编码习惯

让你成为糟糕程序员的 11 个 Python 编码习惯 简介 Python 因其简洁性和可读性而备受推崇&#xff0c;但即使是最有经验的程序员也可能会陷入影响代码质量的习惯中。 在本博客中&#xff0c;我们将探讨 10 种常见的编码习惯&#xff0c;它们会降低您作为 Python 程序员的效率。…

2023年国赛数学建模思路 - 复盘:校园消费行为分析

文章目录 0 赛题思路1 赛题背景2 分析目标3 数据说明4 数据预处理5 数据分析5.1 食堂就餐行为分析5.2 学生消费行为分析 建模资料 0 赛题思路 &#xff08;赛题出来以后第一时间在CSDN分享&#xff09; https://blog.csdn.net/dc_sinor?typeblog 1 赛题背景 校园一卡通是集…

Intel汇编和ATT汇编的区别?

一、前缀不同 在 Intel 语法中&#xff0c;没有寄存器前缀或立即前缀。 然而&#xff0c;在 AT&T 中&#xff0c;寄存器的前缀是“%”&#xff0c;而 immed 的前缀是“$”。 Intel 语法十六进制或二进制即时数据分别带有“h”和“b”后缀。 此外&#xff0c;如果第一个十六…