当前位置: 首页 > news >正文

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()方法无此要求。

相关文章:

  • 实现:mysql-5.7.42 到 mysql-8.2.0 的升级(二进制方式)
  • BUGKU-WEB shell
  • Vector Magic:高效图像矢量化工具,轻松实现图片转矢量
  • PTA L2-021 点赞狂魔
  • 众生度尽,方证菩提,地狱不空,誓不成佛;男人一定要帅,像地藏王菩萨一样
  • 张宇高数一学习笔记-第一讲-函数极限与连续(1)
  • 移除元素(leetcode)
  • HarmonyOS NEXT应用开发之大文件拷贝案例
  • 提升零售行业竞争力的信息抽取技术应用与实践
  • 深入挖掘C语言之——枚举
  • [Linux] 进程间通信基础
  • Java安全 反序列化(1) URLDNS链原理分析
  • Linux下使用ntpdate进行时间同步
  • 代码随想录训练营第48天 | LeetCode 121. 买卖股票的最佳时机、​​​​​​LeetCode 122.买卖股票的最佳时机II
  • 【Linux】进程优先级
  • 【MySQL经典案例分析】 Waiting for table metadata lock
  • 345-反转字符串中的元音字母
  • css的样式优先级
  • ECMAScript 6 学习之路 ( 四 ) String 字符串扩展
  • mysql常用命令汇总
  • Shell编程
  • Spring Boot MyBatis配置多种数据库
  • Vue2.x学习三:事件处理生命周期钩子
  • 安装python包到指定虚拟环境
  • 分布式事物理论与实践
  • 诡异!React stopPropagation失灵
  • 经典排序算法及其 Java 实现
  • 山寨一个 Promise
  • 世界编程语言排行榜2008年06月(ActionScript 挺进20强)
  • 通信类
  • 用Canvas画一棵二叉树
  • [地铁译]使用SSD缓存应用数据——Moneta项目: 低成本优化的下一代EVCache ...
  • 数据可视化之下发图实践
  • #快捷键# 大学四年我常用的软件快捷键大全,教你成为电脑高手!!
  • (1/2)敏捷实践指南 Agile Practice Guide ([美] Project Management institute 著)
  • (10)ATF MMU转换表
  • (52)只出现一次的数字III
  • (C语言)输入一个序列,判断是否为奇偶交叉数
  • (办公)springboot配置aop处理请求.
  • (附源码)spring boot火车票售卖系统 毕业设计 211004
  • (五)c52学习之旅-静态数码管
  • .bat批处理(九):替换带有等号=的字符串的子串
  • .netcore 如何获取系统中所有session_ASP.NET Core如何解决分布式Session一致性问题
  • .NET中GET与SET的用法
  • .stream().map与.stream().flatMap的使用
  • .w文件怎么转成html文件,使用pandoc进行Word与Markdown文件转化
  • ;号自动换行
  • @RequestParam详解
  • [1159]adb判断手机屏幕状态并点亮屏幕
  • [bzoj 3534][Sdoi2014] 重建
  • [C++]打开新世界的大门之C++入门
  • [C++]拼图游戏
  • [CF494C]Helping People
  • [DevEpxress]GridControl 显示Gif动画
  • [GN] DP学习笔记板子