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

【Java】多线程篇 —— 多线程的基本使用

目录

  • 多线程的概念
  • 多线程的实现方式
    • 继承 Thread 类的方式进行实现
    • 实现 Runnable 接口的方式进行实现
    • 利用 Callable 接口和 FutureTask 类
    • 三种方式对比
  • 常见的成员方法
    • 线程的命名和休眠
    • 线程的优先级
    • 守护线程
    • 礼让线程
    • 插入线程
  • 线程的生命周期
  • 线程的安全问题
    • 同步代码块
    • 同步方法
    • Lock 锁
    • 死锁
      • 死锁的四个必要条件
      • 死锁示例代码
      • 解决死锁的常用方法
        • 破坏循环等待条件
        • 使用 `tryLock` 实现超时机制
        • 减少锁的持有时间
        • 使用更高层次的并发工具
      • 预防死锁的最佳实践
  • 生产者和消费者(等待唤醒机制)
    • 原理
      • 消费者代码思路:
      • 生产者代码思路:
      • 生产者和消费者常见方法
      • 代码示例
    • 阻塞队列方式实现
      • 原理
      • 代码示例
  • 线程的状态

多线程的概念

线程:是操作系统能够进行运输调度的最小单位,它被包含在进程之中,是进程中的实际运作单位。可以简单理解为,应用软件中互相独立,可以同时运行的功能

进程:是程序的基本执行实体

多线程:可以将线程简单理解为,应用软件中互相独立,可以同时运行的功能。而多线程就是同时运行多个这样的功能,可以让程序同时做多件事

多线程作用:提高效率(压榨 CPU)

多线程应用场景

  • 服务端领域

    • Web 服务:处理大量并发请求,像电商网站高峰期多用户同时访问,多线程让服务器并行响应,提升并发处理力和响应速度。
    • 数据库服务:多个客户端同时进行读写操作时,多线程并行处理,提高数据库吞吐量和性能。
  • 界面相关应用

    • 桌面软件:进行图形渲染、文件读写等耗时操作时,多线程避免界面卡顿,保证流畅交互。

    • 游戏开发:不同线程分别负责逻辑计算、图形渲染、网络通信等,实现流畅游戏体验。

  • 数据计算场景

    • 科学计算:气象预报、基因测序等需大量数值计算,多线程分解任务并行处理,缩短计算时间。

    • 大数据处理:对海量数据进行挖掘、分析,多线程同时处理不同部分数据,加快处理进程。

  • 异步 I/O 操作

    • 文件读写:读写大文件时,多线程让读写与其他任务并行,提高整体效率。

    • 网络通信:即时通讯等应用中,不同线程分别负责收、发消息,保证消息及时处理。

并发:在同一时刻,有多条指令在单个 CPU 上交替执行

并行:在同一时刻,有多条指令在多个 CPU(多核) 上同时执行

多线程的实现方式

继承 Thread 类的方式进行实现

操作步骤

  1. 创建一个子类继承 Thread 类
  2. 重写 Thread 类中的 run()
  3. 创建子类对象并启动线程

代码示例

public class MyThread extends Thread{
   
	
	@Override
	public void run() {
   
		for (int i = 0; i < 100; i++) {
   
			System.out.println(getName() + "helloworld");
		}
	}
	
	public static void main(String[] args) {
   
		MyThread mt1 = new MyThread();
		MyThread mt2 = new MyThread();
		
		mt1.setName("线程1:");
		mt2.setName("线程2:");
		
		// 开启线程
		mt1.start();
		mt2.start();
	}
}

实现 Runnable 接口的方式进行实现

操作步骤

  1. 创建一个类实现 Runnable 接口
  2. 重写 Runnable 接口中的 run()
  3. 创建实现类对象
  4. 创建一个 Thread 类对象并启动线程

代码示例

public class MyRun implements Runnable{
   

	@Override
	public void run() {
   
		for (int i = 0; i < 100; i++) {
   
			Thread t = Thread.currentThread();
			System.out.println(t.getName() + "HelloWorld");
			//System.out.println(Thread.currentThread().getName() + "HelloWorld");
		}
		
	}

	public static void main(String[] args) {
   
		
		MyRun mr = new MyRun();
		
		Thread t1 = new Thread(mr);
		Thread t2 = new Thread(mr);
		
		t1.setName("线程1:");
		t2.setName("线程2:");
		
		t1.start();
		t2.start();
	}
}

利用 Callable 接口和 FutureTask 类

特点:可以获取到多线程运行的结果

操作步骤

  1. 创建一个类实现 Callable 接口
  2. 重写 call()(是有返回值的,表示多线程运行的结果)
  3. 创建实现类对象(表示多线程要执行的任务)
  4. 创建 FutureTask 类的对象(作用:管理多线程运行的结果)
  5. 创建 Thread 类的对象并启动线程

代码示例

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class MyCallable implements Callable<Integer>{
   

	@Override
	public Integer call() throws Exception {
   
		int sum = 0;
		for(int i = 1;i <= 100;i++) {
   
			sum = sum + i;
		}
		return sum;
	}
	
	public static void main(String[] args) throws InterruptedException, ExecutionException {
   
		
		MyCallable mc = new MyCallable();
		
		FutureTask<Integer> ft = new FutureTask<Integer>(mc);
		
		Thread t1 = new Thread(ft);
		
		t1.start();
		
		Integer result = ft.get();
		System.out.println(result);
	}

}

三种方式对比

对比维度 继承 Thread 实现 Runnable 接口 实现 Callable 接口并结合 FutureTask
代码复杂度 简单直观,只需继承 Thread 类并重写 run() 方法,但受单继承限制 稍高,需额外创建 Thread 对象并传入 Runnable 实例,可避免单继承限制 最高,要创建 Callable 类、FutureTask 包装对象和 Thread 对象
线程执行结果获取 无法直接获取,run() 方法返回值为 void,需借助共享变量、回调函数等 无法直接获取,run() 方法返回值为 void,需借助额外手段 可直接获取,通过 FutureTaskget() 方法阻塞等待获取 call() 方法返回值
资源共享和线程安全性 不同线程实例有独立实例变量,共享需用静态变量,需额外同步机制保证安全 多个线程可共享同一 Runnable 实例成员变量,需同步机制保证数据一致性 Runnable 类似,多个线程可共享同一 Callable 实例成员变量,需同步机制
扩展性和灵活性 受单继承限制,扩展性差 不影响类继承关系,扩展性和灵活性好,可分离任务和线程创建 不影响类继承关系,扩展性和灵活性好,可分离任务和线程创建

常见的成员方法

线程的命名和休眠

  • String getName():方法名,返回此线程的名称,返回值类型为String
  • void setName(String name):方法名,用于设置线程的名字(构造方法也可设置),无返回值(void) 。

代码示例

public class MyThread extends Thread{
   
	
	public MyThread() {
   }
	public MyThread(String name) {
   
		super(name);
	}
	
	@Override
	public void run() {
   
		for (int i = 1; i <= 3; i++) {
   
			System.out.println(getName() + "@" + i);
		}
	}
	
	public static void main(String[] args) {
   
		// 构造方法命名
		MyThread mt1 = new MyThread("线程1");
		MyThread mt2 = new MyThread();
		MyThread mt3 = new MyThread();
		
		// setName()命名
		mt2.setName("线程2");

		mt1.start();
		mt2.start();
		// 默认命名
		mt3.start();
	}
}

程序运行结果如下:

线程1@1
线程1@2
Thread-1@1
Thread-1@2
Thread-1@3
线程2@1
线程2@2
线程2@3
线程1@3

注意事项

  1. 如果没有给线程命名,线程会有默认名,格式:Thread-X(X 是序号,从 0 开始)
  2. 线程命名可以使用 setName(),也可使用构造方法,由于子类不会继承父类的构造方法,所以不要忘了在继承类中写构造方法
  • static Thread currentThread():静态方法,获取当前线程的对象,返回值类型为Thread

在前面实现 Runnable 接口中使用过该方法,就不写代码示例了,不过需要思考一个问题,在 main 方法中不开启线程,用 currentThread 方法获取的对象是什么?

public class MyThread extends Thread{
   
	
	@Override
	public void run() {
   
		for (int i = 1; i <= 3; i++) {
   
			System.out.println(getName() + "@" + i);
		}
	}
	
	public static void main(String[] args) {
   
		
		Thread t = Thread.currentThread();
		System.out.println(t.getName()); // 输出 main
        
	}
}

当 JVM 虚拟机启动之后,会自动开启多条线程,其中有一条就是 main 线程,作用是调用 main 方法并执行其中的代码

  • static void sleep(long time):静态方法,让线程休眠指定的毫秒数,无返回值(void)。

代码示例

public class Test {
   

	public static void main(String[] args) throws InterruptedException {
   
		System.out.println("123");
		long start = System.currentTimeMillis();
		Thread.sleep(5000);
		long end = System.currentTimeMillis();
		long time = (end - start) / 1000;
		System.out.println("线程休眠了"+ time + "秒");
		System.out.println("456");
	}

}

注意事项

  1. 执行到这个方法的线程会在这里停留对应的时间
  2. 当时间到了后,线程会自动继续执行后面的代码

线程的优先级

调度分为抢占式调度(随机)和非抢占式调度(交替),Java 采取的就是抢占式调度,优先级越大,在随即中抢到 CPU 的概率就越大,优先级为 1-10,默认是 5。

  • setPriority(int newPriority):方法名,设置线程的优先级,无返回值(void)。
  • final int getPriority():方法名,获取线程的优先级,返回值类型为int

相关文章:

  • 58、深度学习-自学之路-自己搭建深度学习框架-19、RNN神经网络梯度消失和爆炸的原因(从公式推导方向来说明),通过RNN的前向传播和反向传播公式来理解。
  • 商城源码的框架
  • JAVA学习笔记038——bean的概念和常见注解标注
  • 计算机毕业设计SpringBoot+Vue.js体育馆使用预约平台(源码+文档+PPT+讲解)
  • Pytest之fixture的常见用法
  • AI人工智能机器学习之监督线性模型
  • 【广度优先搜索】图像渲染 岛屿数量
  • 7-1JVMCG垃圾回收
  • 【文献阅读】A Survey Of Resource-Efficient LLM And Multimodal Foundation Models
  • 如何保证 Redis 缓存和数据库的一致性?
  • 在编译Linux的内核镜像和模块时,必须先编译内核镜像,再编译模块,顺序不可随意调整的原因
  • 备战蓝桥杯Day11 DFS
  • React 常见面试题及答案
  • Mysql系统表
  • 【考试大纲】中级信息安全工程师考试大纲
  • HTMLS基本结构及标签
  • 神经网络之CNN图像识别(torch api 调用)
  • 建易WordPress
  • 算法-二叉树篇23-二叉搜索树中的插入操作
  • 夜天之书 #106 Apache 软件基金会如何投票选举?
  • 龙岩房地产信息网/青岛网站优化公司哪家好
  • 企业支付宝登录入口/网站推广优化技巧
  • 莱芜网站优化/怎么样做推广
  • 平谷网站建设/怎么做一个公司网站
  • 淮南公司做网站/上海单个关键词优化
  • 三亚谁做网站/百度推广账户优化