JavaSE——多线程基础

概述

        现代操作系统(Windows,macOS,Linux)都可以执行多任务。多任务就是同时允许多个任务。例如:播放音乐的同时,浏览器可以进行文件下载,同时可以进行QQ消息的收发。

        CPU执行代码都是一条一条顺序执行的,但是,即使是单核CPU,也可以同时运行多个任务。因为操作系统执行多任务实际上就是让CPU对多个任务轮流交替执行。

        操作系统轮流让多个任务交替执行,例如,让浏览器执行0.001秒,让QQ执行0.001秒,再让音乐播放器执行0.001秒。在用户使用的体验来看,CPU就是在同时执行多个任务。

1. 进程与线程

1.1 什么是程序?

        程序是含有指令和数据的文件,被存储在磁盘或其他的数据存储设备中,可以理解为程序是包含静态代码的文件。例如:浏览器文件、音乐播放器软件等软件的安装目录和文件。

1.2 什么是进程?

        进程是程序的一次执行过程,是系统运行程序的基本单位。在Windows系统中,每一个正在执行的exe文件或后台服务,都是一个进程,由操作系统同意管理并分配资源,因此进程是动态的。例如:正在允许中的浏览器就是一个进程,正在允许中的音乐播放器是另一个进程,同理,正在允许中的QQ和WPS等都是进程。

        操作系统运行一个程序,即是一个进程从创建,运行到消亡的过程。简单来说,一个进程就是一个执行中的程序,它在计算机中一个指令接着一个指令地执行,同时,每个进程还占有某些系统资源如CPU时间,内存空间、文件,输入输出设备的使用权等。

1.3 什么是线程?

        某些进程内部还需要同时执行多个子程序。例如,我们在使用WPS时,WPS可以让我们一边打字,一边进行拼写检查,同时还可以在后台自动保存和上传云文档,我们把子任务称为线程。线程进程划分成更小的运行单位。

        进程和线程的关系就是:一个进程可以包含一个或多个线程,但至少或有一个主线程。

                        ┌──────────┐│Process   ││┌────────┐│┌──────────┐││ Thread ││┌──────────┐│Process   ││└────────┘││Process   ││┌────────┐││┌────────┐││┌────────┐│
┌──────────┐││ Thread ││││ Thread ││││ Thread ││
│Process   ││└────────┘││└────────┘││└────────┘│
│┌────────┐││┌────────┐││┌────────┐││┌────────┐│
││ Thread ││││ Thread ││││ Thread ││││ Thread ││
│└────────┘││└────────┘││└────────┘││└────────┘│
└──────────┘└──────────┘└──────────┘└──────────┘
┌──────────────────────────────────────────────┐
│               Operating System               │
└──────────────────────────────────────────────┘

        例如,我们启动JVM运行一个Java程序,其实就是启动了一个JVM的进程。在JVM进程中,又包含了main主线程、Reference Handler 清理线程、FInalizer线程(用于调用对象的finalizer()方法)等线程。

        线程是一个比进程更小的执行单位(CPU的最小执行单位)。一个进程在其执行的过程中可以产生多个线程。与进程不同的是,同类的多个线程共享同一块内存空间很一组系统资源,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多。

1.4 进程与线程的区别

  • 根本区别:进程是操作系统资源分配的基本单位,而线程是处理器任务调度和执行的基本单位;
  • 资源开销:每个进程都有独立的代码副本和数据空间,进程之间的切换,资源开销较大;线程可以看作轻量级的进程,每个线程都有自己独立的运行栈和程序计数器,线程之间切换,资源开销小。
  • 包含关系:一个进程内包换多个线程,在执行过程,线程的执行不是线性串行的,而是多条线程并行共同完成;
  • 内存分配:同一个进程内的所有线程共享本进程的内存空间和资源;进程之间的内存空间和资源相互独立;
  • 影响关系:一个进程崩溃后,在保护模式下不会对其他进程产生影响;一个线程崩溃,对导致整个进程退出。所以多进程要比多线程健壮。
  • 执行过程:每个独立的进程有程序运行的入口和程序出口。但是线程不能独立执行,必须依存在应用程序(进程)中,有应用程序提供多个线程执行控制。

1.5 JVM进程

JVM进程的内存区域分配

  • JVM进程启动时,自动创建Heap(堆区)和Metaspace(元空间,JDK1.8以前叫Method Area方法区);
  • 多个线程共享JVM进程的Heap(堆区)和Metaspace(元空间)资源,但每个线程有自己的程序计数器、虚拟机栈和本地方法栈。系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,因此,线程也被称为轻量级进程。

JVM进程由哪些线程组成?

public class Main {public static void main(String[] args) {// 获取 Java 线程管理对象 ThreadMXBeanThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();// 不需要获取的锁监视器 lockedMonitor 和 synchronizer 信息// 仅获取线程和线程信息ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(false, false);// 遍历线程信息,仅打印线程 ID 和线程名称信息for (ThreadInfo threadInfo : threadInfos) {System.out.println("[" + threadInfo.getThreadId() + "] " + threadInfo.getThreadName());}}
}

2. 线程基本概念

  • 单线程:单线程就是进程中只有一个线程。单线程在程序执行时,所走的程序路径按照连续顺序排下来,前面碧玺处理好,后面的才会执行。
punlic class SingleThread{public static void main(String[] args){for(int i = 0; i <= 100000; i++){System.out.print( i + " " );}}}
  • 多线程:由一个以上的线程组成的程序称为多线程程序。Java中,一定是从主线程开始执行(main方法),然后在主线程的某个位置创建并启动新的线程
public class MultiThread {public static void main(String[] args) {// 创建2个线程Thread t1 = new Thread(new Runnable() {@Overridepublic void run() {for (int i = 0; i < 10000; i++) {System.out.println("线程1:" + i + " ");}}});Thread t2 = new Thread(new Runnable() {@Overridepublic void run() {for (int i = 0; i < 10000; i++) {System.out.println("线程2:" + i + " ");}}});// 启动2个线程t1.start();t2.start();}
}

4.线程的创建与启动

  • 通过创建Thread实例,完成线程的创建。
    • 线程的内部实现可以通过继承Thread类、实现Runnable接口等方式进行封装
  • 通过调用Thread实例的start()方法启动新线程
  • 查看Thread类的源代码,会看到start()方法内部调用了一个private native void start0()方法,native修饰符表示这个方法是由JVM虚拟机内部的c代码实现的本地方法,由JVM根据当前操作系统进行本地实现。
public class Main {public static void main(String[] args) {// Step1: main主线程执行输出System.out.println("main start...");// Step2: main主线程,创建子线程sub,输出字母A-ZThread sub = new Thread() {// Step4:子线程被执行时,自动调用run()方法public void run() {for(char c='A';c<='Z';c++){System.out.println("子线程:" + c);}}};// Step3: main主线程,启动子线程subsub.start();// Step4: main主线程执行输出字母的ASCII码for(int c ='a';c<='z';c++){System.out.println("main线程:" + c);}System.out.println("main end...");}
}
  • Step1:main主线程执行输出
  • Step2:main主线程,创建子线程
  • Step3:main主线程,启动子线程
  • Step4:main主线程与子线程同时运行,由操作系统调度,程序本身无法确定线程的调度顺序
    • 同时,main主线程执行输出
    • 同时,子线程被执行时,自动调用run()方法
  • 注意:直接调用Thread实例的run()方法是无效的,因为直接调用run()方法,相当于直接调用了一个普通的Java方法,当前线程并没有任何改变,也不会启动新线程。
public class Main {public static void main(String[] args) {Thread t = new MyThread();t.run();System.out.println("再执行main主线程");}
}class MyThread extends Thread {public void run() {System.out.println("先执行子线程");}
}

5. 线程的创建方式

5. 1 方式一:继承 java.lang.Thread 类(线程子类)

// 线程子类
public class SubThread extends Thread {public void run() {for (int i = 0; i < 10000; i++) {System.out.println("子线程" + i + " ");}}
}// 主线程main
public class MultiThread {public static void main(String[] args) {//创建并启动子线程SubThread thd = new SubThread();thd.start();//主线程继续同时向下执行for (int i = 0; i < 10000; i++) {System.out.println("主线程" + i + " ");}}
}

5. 2 方式二:实现 java.lang.Runnable 接口(线程执行类)

// 线程执行类
public class SubThread implements Runnable {public void run() {for (int i = 0; i < 10000; i++) {System.out.println("子线程" + i + " ");}}
}// 主线程 main
public class MultiThread {public static void main(String[] args) {//创建并启动子线程Thread t = new Thread(new SubThread());t.start(); //主线程继续同时向下执行for (int i = 0; i < 10000; i++) {System.out.println("主线程" + i + " ");}}
}

5. 3 方式三:实现 java.util.concurrent.Callable 接口,允许子线程返回结果、抛出异常

子线程实现类

// 实现子线程
public class SubThread implements Callable<Integer>{private int begin,end;public SubThread(int begin,int end){this.begin = begin;this.end = end;}@Overridepublic Integer call() throws Exception {int result = 0;for(int i=begin;i<=end;i++){result+=i;}return result;}
}

子线程创建并启动

// 子线程封装为FutureTask对象,计算1-100的累加和
SubThread subThread1 = new SubThread(1,100);
FutureTask<Integer> task1 = new FutureTask<>(subThread1);// 子线程封装为FutureTask对象,计算101-200的累加和
SubThread subThread2 = new SubThread(101,200);
FutureTask<Integer> task2 = new FutureTask<>(subThread2);// 分别启动两个子线程
new Thread(task1).start();
new Thread(task2).start();// 分别获取两个子线程的计算结果
int sum1 = task1.get();
int sum2 = task2.get();// 汇总计算结果
int total = sum1 + sum2;

5. 4 方式四:线程池

        线程池,按照配置参数(核心线程数、最大线程数等)创建并管理若干线程对象。程序中如果需要使用线程,将一个执行任务传给线程池,线程池就会使用一个空闲状态的线程来执行这个任务。执行结束以后,该线程并不会死亡,而是再次返回线程池中成为空闲状态,等待执行下一个任务。使用线程池可以很好地提高性能。

// 创建固定大小的线程池
ExecutorService threadPool = Executors.newFixedThreadPool(10);
while (true) {// 提交多个执行任务至线程池,并执行threadPool.execute(new Runnable() {@Overridepublic void run() {System.out.println("当前运行的线程名为: " + Thread.currentThread().getName());try {Thread.sleep(1000);} catch (Exception e) {throw new RuntimeException(e);}}});
}

6. 线程的命名

生产环境中,为了排查问题方便,建议在创建线程的时候指定一个合理的线程名字

  • 调用父类的setName()方法或在构造方法中给线程名字赋值
  • 如果没有为线程命名,系统会默认指定线程名,命名规则是Thread-N的形式

7. 线程的休眠(暂停)

        在线程中,可以通过调用Thread.sleep(long millis),强迫当前线程按照指定毫秒值休眠。

        案例:

        通过调整子线程和main主线程的休眠时间长短,观察执行结果:

        

public class Main {public static void main(String[] args) {System.out.println("main start...");Thread t = new Thread() {public void run() {System.out.println("thread run...");try {Thread.sleep(10); // 子线程休眠10毫秒} catch (InterruptedException e) {}System.out.println("thread end.");}};t.start();try {Thread.sleep(20); // 主线程休眠20毫秒} catch (InterruptedException e) {}System.out.println("main end...");}
}

8. 线程的优先级

  • 在线程中,通过setProiority(int n) 设置线程优先级,范围是1-10,默认为5
  • 优先级高的线程被操作系统调度的优先级较高(操作系统对高优先级线程,调度更频繁)
  • 注意:并不能代表,通过设置优先级来确保高优先级的线程一定会先执行

案例:

        通过调整子线程thread1和thread2的优先级,观察执行结果:

public class Main {public static void main(String[] args) {// 创建子线程:打印数字1-26Thread thread1 = new Thread("数字线程") {@Overridepublic void run() {for (int i = 1; i <= 26; i++) {System.out.println(Thread.currentThread().getName() + "打印=>" + i + "[" + Thread.currentThread().getPriority() + "]");}}};// 创建子线程:打印字母A-ZThread thread2 = new Thread("字母线程") {@Overridepublic void run() {for (char i = 'A'; i <= 'Z'; i++) {System.out.println(Thread.currentThread().getName() + "打印=>" + i + "[" + Thread.currentThread().getPriority() + "]");}}};thread1.setPriority(8); // 设置数字线程优先级=8thread2.setPriority(1); // 设置字母线程优先级=1// 启动子线程thread1.start();thread2.start();}
}

小结

  • Java用Thread对象表示一个线程,通过调用start()启动一个新线程;
  • 一个线程对象只能调用一次start()方法
  • 线程的执行代码写在run()方法中;
  • 线程调度由操作系统决定。程序本身无法决定调度顺序;
  • Thread.sleep()可以把当前线程暂停一段时间。

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

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

相关文章

Matlab R2018a怎么下载安装?Matlab R2018a保姆级详细安装教程

Matlab R2018a下载方法&#xff1a; Matlab R2018a安装教程&#xff1a; 1、右击下载好的压缩包&#xff0c;选择解压到Matlab R2018a 2、打开文件夹【R2018a_win64】&#xff0c;右击下面的setup.exe&#xff0c;选择【以管理员身份运行】 3、点击选择【使用文件安装密钥】&a…

IDEA连接数据库报错:Access denied for user ****

使用IDEA开发时&#xff0c;通过Databse连接数据库。多次连接报错&#xff1a;Access denied for user **** 如下所示&#xff1a; ​ ‍ ‍ ​ ‍ 花了不少时间排查&#xff0c;确认账号、密码&#xff0c;后面发现账号后多了个空格&#xff0c;而且不容易发现&#xf…

proteus仿真软件简体中文版网盘资源下载(附教程)

对于电子通信专业的小伙伴来说&#xff0c;今天文章的标题应该不会陌生。Proteus是一款具有广泛应用的仿真软件&#xff0c;它的功能非常强大&#xff0c;适用于所有单片机的仿真工作&#xff0c;能够从原理图、调试、到与电路的协同仿真一条龙全部搞定&#xff0c;受到所有用户…

交叉熵损失函数的使用

交叉熵损失函数 交叉熵损失函数&#xff08;Cross-Entropy Loss&#xff09;&#xff0c;也称为对数损失&#xff08;Log Loss&#xff09;&#xff0c;是机器学习和深度学习中常用的损失函数之一&#xff0c;尤其在分类问题中。它衡量的是模型预测的概率分布与真实标签的概率…

使用Properties

a.特点 i.它的Key-Value一般都是String-String类型的&#xff0c;可以用Map<String, String>表示。 ii.Java标准库提供Properties来表示一组“配置”。 iii.读写Properties时&#xff0c;使用getProperty()和setProperty()方法&#xff0c;不要调用继承自HashTabled的ge…

开始场景的制作+气泡特效的添加

3D场景或2D场景的切换 1.新建项目时选择3D项目或2D项目 2.如下图操作&#xff1a; 开始前的固有流程 按照如下步骤进行操作&#xff0c;于步骤3中更改Company Name等属性&#xff1a; 本案例分辨率可以如下设置&#xff0c;有能力者可根据需要自行调整&#xff1a; 场景制作…

轻掺杂漏极(LDD)技术

轻掺杂漏极&#xff08;LDD&#xff09;是一种低能量、低电流的注入工艺&#xff0c;通过该工艺在栅极附近形成浅结&#xff0c;以减少靠近漏极处的垂直电场。对于亚微米MOSFET来说&#xff0c;LDD是必需的&#xff0c;以便抑制热电子效应&#xff0c;这种效应会导致器件退化并…

Python进阶学习笔记(一)对象

1.对象模型 在面向对象理论中类和对象是不同的概念&#xff0c;而在python中类也是对象&#xff0c;叫做类型对象。 所以python中的类&#xff0c;实例对象&#xff0c;类型都是对象。 元类型&#xff1a; 在python中实例对象的类型为对应类型的对象&#xff0c;而类型的对象…

14. PEFT:在大模型中快速应用 LoRA

如果你对LoRA还没有一个直观的概念&#xff0c;可以回看这篇文章&#xff1a;《3. 认识 LoRA&#xff1a;从线性层到注意力机制》。 我们将在这里进一步探讨如何快速地在大型预训练模型中应用 LoRA&#xff0c;并解答可能存在的问题&#xff0c;包括&#xff1a; peft 和 lora …

博途TIA v18下载时,需要重启才能安装下载路径是灰色改不了

一、需要重启才能安装 删除下面注册表P开头的文件&#xff1a; 二、下载路径是灰色改不了 注册表HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion里找到C:\Program Files或者C:\Program Files&#xff08;x86&#xff09;&#xff0c;具体哪个看安装的时候对应…

TikTokDownloader 开源项目操作教程

TikTokDownloader TikTokDownloader 是一个开源的多功能视频下载工具&#xff0c;它专门用于从抖音和TikTok平台下载无水印的视频、图集和直播内容。这个工具支持批量下载账号作品、收藏内容&#xff0c;并可以采集详细数据。它提供了命令行和Web界面&#xff0c;具有多线程下…

arm-硬件

一、ARM体系与架构 ARM芯片组成 -- arm 体系中&#xff0c;一般讲到的芯片由两大部分组成&#xff1a;arm的内核、外设 arm内核&#xff1a; -- 其内核主要由&#xff1a;寄存器、指令集、总线、存储器映射规则、中断逻辑主调试组件构成。ARM公司只设计内核&#xff0c;授权给…

java intellij idea开发步骤,使用指南,工程创建与背景色字体配置,快捷键

intellij idea2021 配置背景色&#xff0c;字体大小&#xff0c;主题 快捷键

网站建设模板选择哪种

在选择网站建设模板时&#xff0c;需要考虑多个因素&#xff0c;包括网站的目的、受众、内容类型以及个性化需求等。以下是一些常见的网站建设模板类型&#xff0c;以及它们的特点&#xff0c;希望对你的选择有所帮助。 企业/商务模板&#xff1a; 企业和商务网站通常需要专业、…

14、主机、应用及数据安全解读

数据来源&#xff1a;14.主机、应用及数据安全解读_哔哩哔哩_bilibili

leetcode第十一题:盛最多水的容器

给定一个长度为 n 的整数数组 height 。有 n 条垂线&#xff0c;第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。 找出其中的两条线&#xff0c;使得它们与 x 轴共同构成的容器可以容纳最多的水。 返回容器可以储存的最大水量。 说明&#xff1a;你不能倾斜容器。 示例…

简单题101. 对称二叉树 (python)20240922

问题描述&#xff1a; python: # Definition for a binary tree node. # class TreeNode(object): # def __init__(self, val0, leftNone, rightNone): # self.val val # self.left left # self.right rightclass Solution(object):def isSymm…

Windows内网穿透远程桌面操作指南

1、登录NatCross官网https://www.natcross.com 账密登录或手机验证码登录。 2、点击左侧场景映射&#xff0c;选择【3389远程桌面】点击添加。 3、检查本地ip&#xff1a;127.0.0.1为本机&#xff0c;本地端口默认&#xff1a;3389&#xff0c;点击保存&#xff0c;系统生产成外…

【LeetCode】每日一题 2024_9_22 找到小镇的法官(模拟)

前言 每天和你一起刷 LeetCode 每日一题~ LeetCode 启动&#xff01; 题目&#xff1a;找到小镇的法官 代码与解题思路 func findJudge(n int, trust [][]int) int {// 我当时的思路就是&#xff1a;每个人&#xff08;除了小镇法官&#xff09;都信任这位小镇法官。// 直接…

黑马头条day2-2 freemaker minio

其实就是freemaker生成一个静态页面 然后存储到minio上 返回一个链接在表里 最后直接通过url访问minio里边的动态页面 freemaker和minio 就是一个展示一个存储 下边这个弹幕感觉说的很清楚 遇到的问题 1 依赖报错 引不到依赖 一直没找到问题出在哪里 明明在pom文件里边引入了…