Java 多线程(超详细讲解)上篇

多线程可以使程序在同一时间内执行多个操作,采用Java中的多线程机制可以使计算机资源得到更充分的利用,多线程技术在网络编程中有广泛的应用。

一、进程与线程

进程是程序的一次动态执行过程,它是从代码加载、执行中到执行完毕的一个完整过程,也就是进程本身从产生、发展到最终消亡的过程。操作系统同时管理一个计算机系统中的多个进程,让计算机系统中的多个进程轮流使用中央处理器资源,或者共享操作系统的其他资源。由于CPU执行速度非常快,所有程序好像在“同时”运行一样。
在操作系统中可以多个进程,这些进程包括系统进程(由操作系统内部建立的进程)和用户进程(由用户程序建立的进程)。可以从Windows任务管理器中查看已启动的进程,进程是系统运行程序的最小单元。各进程之间是独立的,每个进程的内部数据和状态也是完全独立的。
线程是进程中执行运算的最小单位,是在进程基础上的进一步划分,一个线程可以完成一个独立的顺序控制流程。
与进程不同,同一进程内的多个线程共享同一块内存空间(包括代码空间、数据空间)和一块系统资源,所以系统在产生一个线程或在各线程之间切换工作,其负担要比在进程间切换小得多。

二、线程的优势

多线程作为一种多任务并发的工作方式,有着广泛的应用。合理使用线程,将减少开发和维护的成本,甚至可以改善复杂应用程序的性能。使用多线程的优势如下:
  1. 充分利用CPU的资源
  2. 简化编程模型
  3. 良好的用户体验

三、多线程编程

在Java语言中,实现多线程的方式有两种:一种是继承Thread类,另一种是实现Runnable接口。

1、Thread类介绍

Thread类提供了大量的方法来控制和操作线程。

方法描述类型
Thread()创建Thread对象构造方法
Thread(Runnable target)创建Thread对象,target为run()方法被调用的对象构造方法
Thread(Runnable target,String name)创建Thread对象,target为run()方法被调用的对象,name为新线程的名称构造方法
void run()执行任务操作的方法实例方法
void start()使该线程开始运行,JVM将调用该线程的run()方法实例方法
void sleep(long millis)在指定的毫秒数内让当前正在运行的线程休眠(暂停运行)静态方法
Thread currentThread()返回当前线程对象的引用静态方法

Thread类的静态方法currentThread()返回当前线程对象的引用。在Java程序启动时,一个线程立即随之启动,这个线程通常被称为程序的主线程。
Thread类的重要性:

  1. 主线程是产生其他子线程的线程
  2. 主线程通常必须最后完成运行,因为它执行各种关闭动作
    示例:
public class MainThreadTest {public static void main(String[] args) {Thread t=Thread.currentThread();System.out.println("当前线程:"+t.getName());t.setName("MainThread");System.out.println("当前线程:"+t.getName());}
}

以上示例中,使用Thread currentThread()方法获取当前线程,即主线程的Thread对象。在Thread中定义了name属性,记录线程名称,可以使用setName()方法或getName()方法对线程名称进行赋值和取值操作。

继承Thread类创建线程类

继承Thread类是实现线程的一种方式。在使用此方法自定义线程类时,必须在格式上满足如下要求:

  • 此类必须继承Thread类
  • 将线程执行的代码写在run()方法中

语法结构:

//继承Thread类的方式创建自定义线程类
public class MyThread extends Thread{//重写Thread类中的run()方法public void run(){//线程执行任务的代码}
}

示例:

public class WorkThread extends Thread{public void run(){System.out.println(Thread.currentThread().getName()+"开始采摘苹果树:");for (int i = 0; i <5 ; i++) {System.out.println(Thread.currentThread().getName()+"进度:第"+(i+1)+"颗");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}System.out.println(Thread.currentThread().getName()+"已完成采摘任务!");}}
class ThreadTest{public static void main(String[] args) {WorkThread workThread=new WorkThread();workThread.setName("果农A");workThread.start();WorkThread workThread2=new WorkThread();workThread2.setName("果农B");workThread2.start();}
}

运行结果:

果农B开始采摘苹果树:
果农A开始采摘苹果树:
果农B进度:第1颗
果农A进度:第1颗
果农B进度:第2颗
果农A进度:第2颗
果农A进度:第3颗
果农B进度:第3颗
果农B进度:第4颗
果农A进度:第4颗
果农B进度:第5颗
果农A进度:第5颗
果农B已完成采摘任务!
果农A已完成采摘任务!

从以上示例可以看出,两个线程对象调用start()方法启动后,每个线程都会独立完成各自的线程操作,相互之间没有影响并行运行。

实现Runnable接口创建线程类

Runnable接口位于java.lang包中,其中只提供一个抽象方法run()的声明,Thread类也实现了Runnable接口。使用Runnable接口时离不开Thread类,这是因为它要用Thread类中的start()方法。在Runnable接口中只有run()方法,其他操作都要借助于Thread类。

语法结构:

//实现Runnable接口方式创建线程类
class MyThread implements Runnable{public void run(){//这里写线程内容}
}
//测试类
public class RunnableTest{public static void main(String[] args){//通过Thread类创建线程对象MyThread myThread=new MyThread();Thread thread=new Thread(myThread);thread.start();}
}

在上面的代码中,MyThread类实现了Runnable接口,在run()方法中编写线程所执行的代码。如果MyThread还需要继承其他类(如Base类),也完全可以实现。关键代码如下:

class MyThread extends Base implements Runnable{public void run(){//线程执行任务的代码}
}

示例:

public class OneGroupThread extends Thread {public void run() {System.out.println(Thread.currentThread().getName() + "开始为苹果树剪枝");for (int i = 0; i < 5; i++) {System.out.println(Thread.currentThread().getName() + "进度:第" + (i + 1) + "颗");try {Thread.sleep(500);} catch (InterruptedException e) {throw new RuntimeException(e);}}System.out.println(Thread.currentThread().getName() + "已完成任务!");}
}class TwoGroupThread extends Thread {public void run() {System.out.println(Thread.currentThread().getName() + "开始为苹果树剪枝");for (int i = 0; i < 7; i++) {System.out.println(Thread.currentThread().getName() + "进度:第" + (i + 1) + "颗");try {Thread.sleep(300);} catch (InterruptedException e) {throw new RuntimeException(e);}}System.out.println(Thread.currentThread().getName() + "已完成任务!");}
}class Test {public static void main(String[] args) {OneGroupThread thread = new OneGroupThread();TwoGroupThread thread2 = new TwoGroupThread();thread.setName("一组阿哲");thread.start();thread2.setName("二组洋洋");thread2.start();}
}}
}

运行结果:

一组阿哲开始为苹果树剪枝
二组洋洋开始为苹果树剪枝
一组阿哲进度:第1颗
二组洋洋进度:第1颗
二组洋洋进度:第2颗
一组阿哲进度:第2颗
二组洋洋进度:第3颗
二组洋洋进度:第4颗
一组阿哲进度:第3颗
二组洋洋进度:第5颗
一组阿哲进度:第4颗
二组洋洋进度:第6颗
二组洋洋进度:第7颗
一组阿哲进度:第5颗
二组洋洋已完成任务!
一组阿哲已完成任务!

2、线程调度相关方法

方法描述
int getPriority()返回线程的优先级
void setPrority(int newPriority)更改线程的优先级
boolean isAlive()测试线程是否处于活动状态
void join()进程中的其他线程必须等待该线程终止后才能运行
void interrupt()中断线程
void yield()暂停当前正在执行的线程类对象并运行其他线程

1、线程的优先级

范围是1~10;可以使用数字也可以使用以下三个静态常量

  1. MAX_PRIORITY:其值是10,表示优先级最高。
  2. MIN_PRIORITY:其值是1,表示优先级最低。
  3. NORM_PRIORITY:其值是5,表示普通优先级,也就是默认值。

优先级并不代表永远是该线程一直有运行的机会,而是,会获得更多的运行机会,优先级低,也是会获得运行机会的。

示例:

public class WorkThread2 implements Runnable{public void run(){System.out.println(Thread.currentThread().getName()+"开始采摘苹果树:");for (int i = 0; i <5 ; i++) {System.out.println(Thread.currentThread().getName()+"进度:第"+(i+1)+"颗,优先级:"+Thread.currentThread().getPriority());try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}System.out.println(Thread.currentThread().getName()+"已完成采摘任务!");}}
class RunnableTest{public static void main(String[] args) {WorkThread2 work1=new WorkThread2();Thread t1=new Thread(work1,"果农A");WorkThread2 work2=new WorkThread2();Thread t2=new Thread(work2,"果农B");WorkThread2 work3=new WorkThread2();Thread t3=new Thread(work3,"果农C");
//        Thread t1=new Thread(new WorkThread2());
//        Thread t2=new Thread(new WorkThread2());t1.setPriority(Thread.MAX_PRIORITY);t2.setPriority(Thread.MIN_PRIORITY);t3.setPriority(Thread.NORM_PRIORITY);t1.start();t2.start();t3.start();}
}

运行结果:

果农A开始采摘苹果树:
果农B开始采摘苹果树:
果农B进度:第1,优先级:1
果农C开始采摘苹果树:
果农A进度:第1,优先级:10
果农C进度:第1,优先级:5
果农A进度:第2,优先级:10
果农C进度:第2,优先级:5
果农B进度:第2,优先级:1
果农A进度:第3,优先级:10
果农C进度:第3,优先级:5
果农B进度:第3,优先级:1
果农A进度:第4,优先级:10
果农B进度:第4,优先级:1
果农C进度:第4,优先级:5
果农A进度:第5,优先级:10
果农B进度:第5,优先级:1
果农C进度:第5,优先级:5
果农C已完成采摘任务!
果农B已完成采摘任务!
果农A已完成采摘任务!

2、线程的强制运行

在线程操作中,可以使用join()方法让一个线程强制运行。在线程强制运行期间,其他线程无法运行,必须等待此线程完成之后才可以继续运行。它有三个重载方法,定义如下:

public final void join()
public final void join(long mills)
public final void join(long mills,int nanos)

注意:调用join()方法需要处理InterruptedException异常。

示例:

public class JoinThread implements Runnable {public void run() {for (int i = 0; i < 10; i++) {try {Thread.sleep(100);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println(Thread.currentThread().getName() + "采购进度:第" + (i + 1) + "车");}}
}
//测试类
class Test3 {public static void main(String[] args) {//创建子线程并启动Thread t=new Thread(new JoinThread(),"大型商超");t.start();Thread.currentThread().setName("果商");//修改主线程名称//正常采购for (int i = 0; i < 10; i++) {if (i==5){try {t.join();} catch (InterruptedException e) {throw new RuntimeException(e);}}try {Thread.sleep(100);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println(Thread.currentThread().getName() + "采购进度:第" + (i + 1) + "车");}}
}

运行结果:

果商采购进度:第1车
大型商超采购进度:第1车
果商采购进度:第2车
大型商超采购进度:第2车
果商采购进度:第3车
大型商超采购进度:第3车
大型商超采购进度:第4车
果商采购进度:第4车
果商采购进度:第5车
大型商超采购进度:第5车
大型商超采购进度:第6车
大型商超采购进度:第7车
大型商超采购进度:第8车
大型商超采购进度:第9车
大型商超采购进度:第10车
果商采购进度:第6车
果商采购进度:第7车
果商采购进度:第8车
果商采购进度:第9车
果商采购进度:第10

在以上代码中,创建子线程类对象t代表大型商超,主线程代表果商。当向果商供应了五车水果后,改为向大型商超集中供应。在代码中,程序开始运行后,代码果商的主线程和代表大型商超的子线程交替运行。当条件满足后,执行t.join()方法,子线程会夺得CPU使用权,优先运行,子线程全部运行完毕后,代表果商的主线程恢复运行。

3、线程的礼让

当一个线程在运行中执行了Thread类的yield()静态方法后,如果此时还有相同或更高优先级的其他线程处于就绪状态,系统将会选择其他相同或更高优先级的线程运行,如果不存在这样的线程,则该线程继续运行。

注意:使用yield()方法实现线程礼让只是提供一种可能,不能保证一定会实现礼让,因为礼让的线程处于就绪状态时,还有可能被线程调度程序再次选中。

示例:

public class ChildThread implements Runnable {public void run() {for (int i = 0; i < 5; i++) {Thread.yield();//线程礼让System.out.println(Thread.currentThread().getName() + "品尝:第" + (i + 1) + "块");}}
}class Test4 {public static void main(String[] args) {Thread t1=new Thread(new ChildThread(),"Child1");Thread t2=new Thread(new ChildThread(),"Child2");Thread t3=new Thread(new ChildThread(),"Child3");t1.start();t2.start();t3.start();}
}

运行结果:

Child3品尝:第1Child2品尝:第1Child1品尝:第1Child2品尝:第2Child3品尝:第2Child3品尝:第3Child3品尝:第4Child1品尝:第2Child3品尝:第5Child2品尝:第3Child1品尝:第3Child2品尝:第4Child1品尝:第4Child2品尝:第5Child1品尝:第5

可以看出执行Thread.yield()方法之后,多线程交替运行较为频繁,提高了程序的并发性。

注意:
sleep()方法和yield()方法都是Thread类的静态方法,都会使当前处于运行状态的线程放弃CPU使用权,将运行机会让给其他线程,两者的区别如下:
1.sleep()方法会给其他线程运行机会,不考虑其他线程的优先级,因此较低优先级线程可能会获得运行机会。
2.yield()方法只会将运行机会让给相同优先级或更高优先级的线程。
3.调用sleep()方法需处理InterruptedException异常,而调用yeild()方法无此要求。

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

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

相关文章

你知道三拳打死镇关西的故事吗?郑屠户做了什么让鲁达竟将他置于死地?

你知道三拳打死镇关西的故事吗&#xff1f;郑屠户做了什么让鲁达竟将他置于死地&#xff1f; 《水浒传》第二集里&#xff0c;咱们看到了鲁提辖&#xff0c;也就是鲁达&#xff0c;他为啥要打郑屠户&#xff0c;也就是人们常说的镇关西。这鲁提辖可是个仗义疏财的好汉&#xf…

好委屈,东方甄选为何总是被供应商骗?

东方甄选最近很委屈。 315晚会过后&#xff0c;知名打假人王海爆料&#xff0c;称315晚会曝光的槽头肉扣肉在东方甄选和小杨哥的直播间里销售过。 东方甄选赶忙去问了问供应商情况。 供应商的回答让他感到暖心&#xff0c;表示虽然315晚会曝光了我们公司违规使用糟头肉&…

vue3项目

案例用到的知识点如下&#xff1a; ① vite 创建项目 ② 组件的封装与注册 ③ props ④ 样式绑定 ⑤ 计算属性 ⑥ 自定义事件 ⑦ 组件上的 v-model 效果如下图&#xff1b; 页面2 项目结构&#xff1a; 初始化项目 在终端运行以下的命令&#xff0c;初始化 vite 项目&#xf…

每日五道java面试题之mybatis篇(五)

目录&#xff1a; 第一题. 实体类属性名和表中字段名不⼀样 &#xff0c;怎么办?第二题. Mybatis是否可以映射Enum枚举类&#xff1f;第三题. Mybatis能执⾏⼀对⼀、⼀对多的关联查询吗&#xff1f;第四题. Mybatis是否⽀持延迟加载&#xff1f;原理&#xff1f;第五题. 如何获…

每日三个JAVA经典面试题(十七)

1.Java 中的线程池是如何实现的 Java中的线程池主要通过java.util.concurrent包提供的Executor框架实现。线程池的核心是重用一组现有线程来执行任务&#xff0c;而不是为每个任务创建新线程。这样做可以减少因频繁创建和销毁线程带来的开销&#xff0c;提高系统资源的利用率&…

Android逆向(二)-系统调试开关

Android逆向(二)-系统调试开关 本篇文章主要介绍下android下的系统调试开关. 1: build.prop简介 android中有一些常用的配置信息都存放在一个文件中,如:设备系统/版本号/Cpu等信息. 而这个文件就是/system/build.prop 我们先简单看下这个文件: zhzh:~/workSpace$ adb she…

【项目实践day06】JWT令牌相关

什么是JWT 简洁的、自包含的格式&#xff0c;用于在通信双方以json数据格式安全的传输信息。 由于数字签名的存在&#xff0c;这些信息是可靠的。 jwt就是将原始的json数据格式进行了安全的封装&#xff0c;这样就可以直接基于jwt在通信双方安全的进行信息传输了。简洁&#…

Playwright中locator() 方法快速定位网页元素[全面总结]

Playwright 是一个用于浏览器自动化的库&#xff0c;它支持多种浏览器和多种语言。在 Playwright 中&#xff0c;page.locator() 方法用于创建一个元素定位器&#xff08;Element Locator&#xff09;。元素定位器是一个强大的工具&#xff0c;可以帮助你在页面上找到并操作元素…

【前端基础】什么是视口?

视口 了解视口相关概念及理想视口的设置 是移动Web开发非常重要环节。 什么是视口&#xff1f; 视口简单来说就是浏览器显示页面内容的区域。 在PC端&#xff0c;正常的视口宽度就是整个浏览器的窗口可视区的宽度&#xff0c;会随着浏览器窗口大小的重置而缩放&#xff1b;…

CTF 题型 SSRF攻击例题总结

CTF 题型 SSRF攻击&例题总结 文章目录 CTF 题型 SSRF攻击&例题总结Server-side Request Forgery 服务端请求伪造SSRF的利用面1 任意文件读取 前提是知道要读取的文件名2 探测内网资源3 使用gopher协议扩展攻击面Gopher协议 &#xff08;注意是70端口&#xff09;python…

前端项目,个人笔记(五)【图片懒加载 + 路由配置 + 面包屑 + 路由行为修改】

目录 1、图片懒加载 步骤一&#xff1a;自定义全局指令 步骤二&#xff1a;代码中使用 ​编辑步骤三&#xff1a;效果查看 步骤四&#xff1a;代码优化 2、封装组件案例-传对象 3、路由配置——tab标签 4、根据tab标签添加面包屑 4.1、实现 4.2、bug&#xff1a;需要…

智能合约 之 部署ERC-20

Remix介绍 Remix是一个由以太坊社区开发的在线集成开发环境&#xff08;IDE&#xff09;&#xff0c;旨在帮助开发者编写、测试和部署以太坊智能合约。它提供了一个简单易用的界面&#xff0c;使得开发者可以在浏览器中直接进行智能合约的开发&#xff0c;而无需安装任何额外的…

进销存管理完整方案:那些让人头疼的进销存难题及解决方法!

什么是进销存管理&#xff1f;为何进销存管理在企业管理中如此重要&#xff1f;进销存管理的核心模块包括哪些&#xff1f;为何企业在进销存管理中常常遭遇前后方协作不畅、数据不同步等痛点&#xff1f;又该如何针对进销存管理痛点进行优化&#xff1f;本文将从进销存管理的基…

代码随想录训练营第50天 | LeetCode 123.买卖股票的最佳时机III、LeetCode 188.买卖股票的最佳时机IV

LeetCode 123.买卖股票的最佳时机III 文章讲解&#xff1a;代码随想录(programmercarl.com) 视频讲解&#xff1a;动态规划&#xff0c;股票至多买卖两次&#xff0c;怎么求&#xff1f; | LeetCode&#xff1a;123.买卖股票最佳时机III_哔哩哔哩_bilibili 思路 代码如下&am…

java实现kml文件下载接口

根据业务需求&#xff0c;需提供一个下载kml格式航线文件的HTTP GET接口 示例代码 package com.kyrielx.kmzdemo.controller;import org.apache.commons.io.FileUtils; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org…

git是什么git能做什么

git是什么 git 是一个免费、开源、分布式的版本控制系统&#xff0c;跟踪个人和团队在项目上协作时的变更历史。随着开发者对项目进行更改&#xff0c;可以随时恢复项目的任何早期版本。 git能做什么 开发者可以通过git查看项目历史&#xff0c;了解&#xff1a; - 做了哪些…

5.首页搜索与瀑布流

搜索栏 官网 https://vkuviewdoc.fsq.pub/components/search.html 是否开启右边控件 该控件为类似按钮形式&#xff0c;可以设置为"搜索"或者"取消"等内容&#xff0c;如果开启动画效果&#xff0c;用户确认搜索后&#xff0c;该控件会自动消失 show-ac…

各种窗函数对脉压结果的影响

雷达导论专栏总目录链接: 雷达导论专栏总目录-CSDN博客 1. 各类窗函数 有几个窗函数的系数可配,配置如下: tukeywin(N,0.75)kaiser(N,2.5)gausswin(N,1.5)taylorwin(N,3,-24)2. 时域加窗 时域加窗时,直接加在匹配滤波函数上:Htw=exp(1j*pi*K*tp.^2).*win;。那么矩形窗就相…

使用jQuery的autocomplete实现数据查询一次,联想自动补全

书接上回&#xff0c;上次说到在jsp页面中&#xff0c;通过监听输入框的数值变化&#xff0c;实时查询数据库&#xff0c;得到返回值使用autocomplete属性自动补全&#xff0c;实现一个联想补全辅助操作&#xff0c;链接&#xff1a;使用jquery的autocomplete属性实现联想补全操…

73_Pandas获取分位数/百分位数

73_Pandas获取分位数/百分位数 使用 quantile() 方法获取 pandas 中 DataFrame 或 Series 的分位数/百分位数。 目录 Quantile() 的基本用法指定要获取的分位数/百分位数&#xff1a;参数 q指定interpolation方法&#xff1a;参数interpolation 数据类型 dtype 的差异 指定行…