多线程基础:线程创建、同步与通信——学习指南

多线程基础:线程创建、同步与通信——学习指南


文章目录

  • 多线程基础:线程创建、同步与通信——学习指南
  • 前言
  • 一、线程创建
    • 1、原理
    • 2、案例
    • 3、使用场景推荐
  • 二、线程同步
    • 1、原理
    • 2、案例
      • 1)synchronized关键字
        • 1.1)修饰实例方法:当synchronized修饰一个实例方法时,它锁定的是调用该方法的对象实例。
        • 1.2)修饰静态方法:当synchronized修饰一个静态方法时,它锁定的是该方法所属的类对象。
        • 1.3)修饰代码块:synchronized也可以用来修饰一个代码块,此时需要指定一个对象作为锁对象。
      • 2)ReentrantLock
    • 3、对比
  • 三、线程通信
    • 1、原理
    • 2、案例
    • 3、使用场景推荐
  • 总结


前言

随着计算机技术的不断发展,多线程编程已经成为现代软件开发中不可或缺的一部分。多线程能够充分利用多核CPU的并行计算能力,提高程序的执行效率。对于初学者来说,掌握多线程的基础知识是迈向高级编程的重要一步。

本文将详细介绍线程的创建、同步与通信,包括怎么创建,有哪几种创建方式,怎么使用,优缺点等等内容。


一、线程创建

1、原理

  1. 线程是操作系统进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位。
  2. Java中创建线程主要方式有:继承Thread类实现Runnable接口实现Callable接口线程池。(本文先省略线程池)。

2、案例

  1. 继承Thread类

继承Thread类创建线程示例:通过继承java.lang.Thread类,重写其run()方法,然后创建子类对象并调用start()方法启动线程。

public class MyThread extends Thread {  @Override  public void run() {  System.out.println("MyThread is running.");  // 线程执行的代码  }  public static void main(String[] args) {  MyThread thread = new MyThread();  thread.start(); // 启动线程  // 直接new,然后重写其中的run方法  new Thread(()->{//重写run方法log.info("run方法打印");}).start();}  
}
  1. 实现Runnable接口

实现Runnable接口创建线程示例:实现java.lang.Runnable接口的类也可以被用来创建线程。这种方式的好处是,一个类可以继承其他类并实现Runnable接口,从而避免Java单继承的限制。

public class MyRunnable implements Runnable {  @Override  public void run() {  System.out.println("MyRunnable is running.");  // 线程执行的代码  }  public static void main(String[] args) {  Thread thread = new Thread(new MyRunnable());  thread.start(); // 启动线程  }  
}
  1. 实现Callable接口

实现Callable接口创建线程示例:java.util.concurrent.Callable接口与Runnable类似,但Callable可以返回执行结果,并且可以抛出异常。通常与Future结合使用,以获取异步计算的结果。

import java.util.concurrent.*;  public class MyCallable implements Callable<String> {  @Override  public String call() throws Exception {  // 执行一些操作并返回结果  return "Result of MyCallable";  }  public static void main(String[] args) throws ExecutionException, InterruptedException {  //结合Future使用ExecutorService executor = Executors.newSingleThreadExecutor();  Future<String> future = executor.submit(new MyCallable());  // 获取计算结果  String result = future.get(); // 阻塞直到计算完成  System.out.println(result);  executor.shutdown(); // 关闭线程池  }  
}

3、使用场景推荐

  1. 继承Thread类:适用于简单的线程任务,且不需要与其他线程共享资源。
  2. 实现Runnable接口:当需要多个线程共享一个任务时,或者类已经继承了其他类时,使用Runnable更合适。
  3. 实现Callable接口:当需要线程执行后返回结果时,或者需要处理可能抛出的异常时,使用Callable结合Future。

二、线程同步

1、原理

  1. 由于多个线程可能同时访问共享资源,导致数据不一致或其他不可预测的问题,因此需要进行线程同步。
  2. Java提供了多种同步机制,如synchronized关键字、Lock接口及其实现类等。

2、案例

1)synchronized关键字

synchronized是Java提供的一种内置锁机制,它可以用来修饰方法或代码块。当一个线程进入一个对象的synchronized方法或代码块时,它获得该对象的锁,其他线程则无法进入该对象的synchronized方法或代码块,直到锁被释放。使用synchronized关键字实现同步方法或同步代码块,确保同一时间只有一个线程可以执行同步区域内的代码。

synchronized是非公平锁。这意味着多个线程在竞争synchronized锁时,它们的获取顺序是不确定的,不按照申请锁的顺序来排队。一个线程在等待锁时,不管自己是不是在等待队列的头部,都有机会在其他线程释放锁后立即获取锁。这种非公平性可能导致某些线程长时间无法获取到锁,产生饥饿现象。然而,synchronized锁也是可重入的,即同一个线程可以反复获取锁多次,然后需要释放多次。

1.1)修饰实例方法:当synchronized修饰一个实例方法时,它锁定的是调用该方法的对象实例。
public class SynchronizedCounter {  private int count = 0;  public synchronized void increment() {  count++;  }  public synchronized void decrement() {  count--;  }  public synchronized int value() {  return count;  }  public static void main(String[] args) {  SynchronizedCounter counter = new SynchronizedCounter();  // 假设有两个线程同时调用increment和decrement方法  // 由于increment和decrement方法都是synchronized的,因此它们是线程安全的  // ...  }  
}
1.2)修饰静态方法:当synchronized修饰一个静态方法时,它锁定的是该方法所属的类对象。
public class SynchronizedStaticCounter {  private static int count = 0;  public static synchronized void increment() {  count++;  }  public static synchronized void decrement() {  count--;  }  public static synchronized int value() {  return count;  }  // ...  
}
1.3)修饰代码块:synchronized也可以用来修饰一个代码块,此时需要指定一个对象作为锁对象。
public class SynchronizedBlock {  private Object lock = new Object();  private int count = 0;  public void increment() {  synchronized (lock) {  count++;  }  }  public void decrement() {  synchronized (lock) {  count--;  }  }  // ...  
}

2)ReentrantLock

ReentrantLock是Java中提供的一个可重入互斥锁,它实现了Lock接口,提供了比synchronized关键字更灵活、更强大的锁定机制。ReentrantLock通过显式地获取和释放锁来控制对共享资源的访问。

ReentrantLock的主要特性包括:

  1. 公平性:可以创建公平或非公平的锁。公平的锁按照请求锁的顺序来授予访问权限,而非公平的锁则不保证这种顺序。
  2. 可重入:同一个线程可以多次获得同一个ReentrantLock实例,而不会造成死锁。
  3. 可中断:当一个线程尝试获取一个由其他线程持有的锁时,该线程可以选择等待,或者响应中断。
  4. 尝试锁定:线程可以尝试获取锁,如果锁不可用,线程可以立即返回,而不是阻塞。

示例:

import java.util.concurrent.locks.ReentrantLock;  public class ReentrantLockExample {  private int count = 0;  private final ReentrantLock lock = new ReentrantLock();  public void increment() {  lock.lock(); // 获取锁  try {  count++;  System.out.println("Incremented: " + count);  } finally {  lock.unlock(); // 释放锁  }  }  public void decrement() {  lock.lock(); // 获取锁  try {  count--;  System.out.println("Decremented: " + count);  } finally {  lock.unlock(); // 释放锁  }  }  public static void main(String[] args) {  ReentrantLockExample example = new ReentrantLockExample();  // 创建两个线程,一个增加计数,一个减少计数  Thread incrementThread = new Thread(() -> {  for (int i = 0; i < 5; i++) {  example.increment();  }  });  Thread decrementThread = new Thread(() -> {  for (int i = 0; i < 5; i++) {  example.decrement();  }  });  // 启动线程  incrementThread.start();  decrementThread.start();  // 等待线程结束  try {  incrementThread.join();  decrementThread.join();  } catch (InterruptedException e) {  e.printStackTrace();  }  }  
}

在上面的代码中,increment()和decrement()方法都使用了ReentrantLock来控制对count变量的访问。在try块中执行临界区代码,在finally块中确保锁被释放,无论是否发生异常。

使用ReentrantLock时,需要注意以下几点:

  1. 一定要在finally块中释放锁,确保锁不会因为异常而没有被释放,从而导致死锁。
  2. 避免在持有锁时执行耗时的操作,以免其他线程长时间等待锁。
  3. 考虑锁的粒度,避免过度同步,以减少线程间的竞争。

ReentrantLock还提供了其他高级功能,比如条件变量(通过newCondition()方法获取),用于在多个线程之间实现更复杂的同步模式。这些高级功能使得ReentrantLock比synchronized关键字更加强大和灵活。

3、对比

  1. 使用场景
    ReentrantLock:适用于需要更精细控制同步的场景,如中断等待获取锁、尝试非阻塞地获取锁,或者与Condition结合使用实现复杂的线程间协调
    synchronized:适用于简单的同步场景,如保护单个方法或代码块不被多个线程同时访问。
  2. 灵活性
    ReentrantLock:提供了更灵活的锁定机制,包括公平锁、可重入锁等特性,以及更精细的锁控制方法。
    synchronized:相对固定,自动管理锁的获取和释放,无需显式操作。
  3. 性能
    性能因具体场景而异。在某些高并发场景下,ReentrantLock可能通过更精细的锁策略提供更佳性能。但在一些简单场景下,synchronized由于其内置性和JVM优化可能表现更好。
  4. 易用性
    ReentrantLock:需要显式管理锁的获取和释放,增加了代码复杂性和出错的可能性。
    synchronized:作为语言特性,使用简单直观,易于理解和维护。
  5. 兼容性
    synchronized:作为Java的关键字,与Java平台完全兼容,无需额外依赖。
    ReentrantLock:是Java标准库的一部分,但使用它需要显式导入相关类。
  6. 锁的类型对比
    synchronized是非公平锁。
    ReentrantLock可以创建公平锁和非公平锁。

在大多数情况下,synchronized是一个很好的选择,因为它简单、易用且性能通常足够。但在需要更精细控制同步的场景下,可以考虑使用ReentrantLock。

三、线程通信

1、原理

  1. 线程通信是指多个线程之间通过共享变量或其他机制进行信息交换和协作。
  2. wait()、notify()和notifyAll()是Java对象中的方法,它们必须与synchronized一起使用,用于在线程间通信。

2、案例

  1. wait():使当前线程等待(进入等待队列),直到其他线程调用该对象的notify()或notifyAll()方法。
  2. notify():唤醒在此对象监视器上等待的单个线程。
  3. notifyAll():唤醒在此对象监视器上等待的所有线程。
public class WaitNotifyExample {  private int count = 0;  private final Object lock = new Object();  public void increment() {  synchronized (lock) {  while (count >= 5) {  try {  lock.wait(); // 当前线程等待,直到被唤醒  } catch (InterruptedException e) {  e.printStackTrace();  }  }  count++;  System.out.println("Incremented: " + count);  lock.notifyAll(); // 唤醒所有在此对象上等待的线程  }  }  public void decrement() {  synchronized (lock) {  while (count <= 0) {  try {  lock.wait(); // 当前线程等待,直到被唤醒  } catch (InterruptedException e) {  e.printStackTrace();  }  }  count--;  System.out.println("Decremented: " + count);  lock.notifyAll(); // 唤醒所有在此对象上等待的线程  }  }  // ...  
}

这个示例展示了如何使用wait()和notifyAll()实现一个基本的生产者-消费者问题,其中increment()方法类似于生产者(增加计数),而decrement()方法类似于消费者(减少计数)。

3、使用场景推荐

  1. 当需要在多个线程之间实现协同工作时,可以使用线程通信机制。例如,生产者消费者模型中,生产者线程生产数据后唤醒消费者线程进行消费,消费者线程消费完数据后唤醒生产者线程继续生产。代码示例如第2点所示

总结

多线程编程是Java编程中的重要部分,掌握线程创建、同步与通信是成为高级程序员的关键。通过本文的介绍,相信你们已经对多线程的基础有了初步的了解。在实际开发中,还需要结合具体需求选择合适的线程创建方式、同步机制和通信方式,以实现高效、稳定的多线程程序。

需要注意的是,多线程编程涉及线程同步、数据共享和通信等复杂问题。在编写多线程程序时,务必谨慎处理这些问题,以避免出现竞态条件、死锁等线程安全问题。

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

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

相关文章

qt-C++笔记之QSpinBox控件

qt-C笔记之QSpinBox控件 code review! 文章目录 qt-C笔记之QSpinBox控件1.运行2.main.cpp3.main.pro4.《Qt6 C开发指南》&#xff1a;4.4 QSpinBox 和QDoubleSpinBox 1.运行 2.main.cpp #include <QApplication> #include <QSpinBox> #include <QPushButton&g…

Vue3 实现基于token 用户登录

前后端分离情况下&#xff0c;实现的大致思路 1 第一次登录的时候&#xff0c;前端调用后端的登录接口&#xff0c;发送用户名与密码 2 后端收到请求&#xff0c;验证用户名和密码&#xff0c;验证成功 给前端返回一个token 3 前段拿到token 将token 存储进localStorage 和…

electron打包桌面版.exe之vue项目踩坑(vue3+electron 解决打包后首页打开空白,打包后路由不跳转及请求不到后端数据等问题)

vue项目https://www.qingplus.cn/components-web/index打包桌面版问题集合 一、静态资源加载问题 npm run electron_dev桌面版运行后页面空白&#xff0c;内容未加载。 填坑&#xff1a; 打包配置要用相对路径 vite.config.ts文件中的base要改成./&#xff0c;之前加了项目…

golang 和java对比

Go(也称为 Golang)和 Java 是两种流行的编程语言,它们在某些方面有相似之处,但在其他方面又有很大的区别。以下是它们之间的对比: 性能和并发性 Go:Go 是一种编译型语言,以其出色的并发性能而闻名。它具有轻量级的协程(goroutines)和通道(channels),使得编写并发代…

2024.3.27力扣(1200-1400)刷题记录

一、2215. 找出两数组的不同 1.排序双指针。我以为遍历时复很高&#xff0c;所以用的双指针。 class Solution:def findDifference(self, nums1: List[int], nums2: List[int]) -> List[List[int]]:#排序双指针nums1.sort()nums2.sort()ans [[],[]]a,b,n1,n2 0,0,len(nu…

软件接口安全设计规范及审计要点

1.token授权安全设计 2.https传输加密 3.接口调用安全设计 4.日志审计里监控 5.开发测试环境隔离&#xff0c;脱敏处理 6.数据库运维监控审计 项目管理全套资料获取&#xff1a;软件开发全套资料_数字中台建设指南-CSDN博客

Qt实现TFTP Server和 TFTP Client(四)

3.3 Server Server包括下面3个类&#xff1a; ServerSocketTFtpServerTFtpServerWidget 3.3.1 ServerSocket ServerSocket从BaseUdp派生实现write接口. 3.3.1.1 ServerSocket定义 class QUdpSocket; class ServerSocket : public BaseUdp { public:ServerSocket(QUdpSock…

Java 学习和实践笔记(49):用javabean和一维数组的方式来存储表格数据

还是存储下面这个表格的数据&#xff0c;但使用另一种方法来做。 用javabean和一维数组的方法来做&#xff0c;示例代码如下&#xff1a; /*先创建一个类&#xff0c;其实就是创建好一个只有各属性列的空表格*/ class Employees {private int id;private String name;private …

[2021]Zookeeper getAcl命令未授权访问漏洞概述与解决

今天在漏洞扫描的时候蹦出来一个zookeeper的漏洞问题&#xff0c;即使是非zookeeper的节点&#xff0c;或者是非集群内部节点&#xff0c;也可以通过nc扫描2181端口&#xff0c;获取极多的zk信息。关于漏洞的详细描述参考apache zookeeper官方概述&#xff1a;CVE-2018-8012: A…

Self-Consistency Improves Chain of Thought Reasoning in Language Models阅读笔记

论文链接&#xff1a;https://arxiv.org/pdf/2203.11171.pdf 又到了读论文的时间&#xff0c;内心有点疲惫。这几天还是在看CoT的文章&#xff0c;今天这篇是讲如何利用self-consistency&#xff08;自我一致性&#xff09;来改进大语言模型的思维链推理过程。什么是self-cons…

5.4 物联网RK3399项目开发实录-Android开发之编译 Android10.0 固件(wulianjishu666)

物联网项目开发实例&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/11VQMhHfIL9mZhNlls4wmjw?pwd0gfa 4. 编译 Android10.0 固件 4.1. 下载 Android SDK 由于 SDK 较大&#xff0c;可以去下载页面选择云盘下载 Firefly-RK3399_Android10.0_git_20211222.7z&#x…

Android-Handler详解_原理解析

为了方便阅读将文章分为使用篇和源码解析两篇&#xff0c;上一篇已经写了Handler是什么、有什么、怎们用&#xff0c;这一片从源码的角度分析完整流程&#xff0c;看看Handler消息机制到底是啥原理。才疏学浅&#xff0c;如有错误&#xff0c;欢迎指正&#xff0c;多谢。 完整…

标定系列——预备知识-OpenCV中实现Rodrigues变换的函数(二)

标定系列——预备知识-OpenCV中实现Rodrigues变换的函数&#xff08;二&#xff09; 说明记录 说明 简单介绍罗德里格斯变换以及OpenCV中的实现函数 记录

ClickHouse10-ClickHouse中Kafka表引擎

Kafka表引擎也是一种常见的表引擎&#xff0c;在很多大数据量的场景下&#xff0c;会从源通过Kafka将数据输送到ClickHouse&#xff0c;Kafka作为输送的方式&#xff0c;ClickHouse作为存储引擎与查询引擎&#xff0c;大数据量的数据可以得到快速的、高压缩的存储。 Kafka大家…

Ubuntu 配置 kubernetes 学习环境,让外部访问 dashboard

Ubuntu 配置 kubernetes 学习环境 一、安装 1. minikube 首先下载一下 minikube&#xff0c;这是一个单机版的 k8s&#xff0c;只需要有容器环境就可以轻松启动和学习 k8s。 首先你需要有Docker、QEMU、Hyperkit等其中之一的容器环境&#xff0c;以下使用 docker 进行。 对…

【C++】编码规范之可靠性原则

C编码规范中的可靠性原则是确保代码的可读性、可维护性和稳定性&#xff0c;以下是几个小点及其例子&#xff1a; 避免使用全局变量&#xff1a; 如果需要多个变量在全局范围内使用&#xff0c;可用context&#xff08;结构体/类&#xff09;解决耦合性问题 // 不推荐的写法&…

65W智能快充—同为科技桌面PDU插座推荐

近10年&#xff0c;移动设备的智能化、功能化已经完全且紧密的融入到我们的基础生活与工作当中。 在常态化的电子设备的应用中&#xff0c;设备的电力续航以及后续的供电充电就尤为重要。 就目前而言&#xff0c;所有消费电子产品中的输入以及充电的接口&#xff0c;usb-c可以…

酷开科技依托酷开系统用“平台+产品+场景”塑造全屋智能生活!

杰弗里摩尔的“鸿沟理论”中写道&#xff1a;高科技企业推进产品的早期市场和产品被广泛接受的主流市场之间&#xff0c;存在着一条巨大的“鸿沟”。“鸿沟”&#xff0c;指产品吸引早期接纳者后、赢得更多客户前的那段间歇&#xff0c;以及其中可预知和不可预知的阻碍。多数产…

面试中会被问到的GIT问题解答(含答案)

在现代软件开发中&#xff0c;Git已经成为了版本控制系统的事实标准。无论是在个人项目还是大型企业级开发中&#xff0c;Git都是不可或缺的工具。因此&#xff0c;掌握Git的基本操作和高级特性对于软件开发者来说是非常重要的。以下是根据提供的文件内容&#xff0c;总结出的3…

基于Rflysim平台的无人机拦截三维比例导引算法仿真

【后厂村路钢铁侠出品】 一、Rflysim简介 RflySim是一套专为科研和教育打造的Pixhawk /PX4 和MATLAB/Simulink生态系统或工具链&#xff0c;采用基于模型设计&#xff08;Model-Based Design&#xff0c; MBD&#xff09;的思想&#xff0c;可用于无人系统的控制和安全测试。…