深入理解 Java 多线程、Lambda 表达式及线程安全最佳实践

本文涉及的产品
应用实时监控服务-可观测链路OpenTelemetry版,每月50GB免费额度
函数计算FC,每月15万CU 3个月
云原生网关 MSE Higress,422元/月
简介: 线程使程序能够通过同时执行多个任务而更有效地运行。线程可用于在不中断主程序的情况下在后台执行复杂的任务。创建线程有两种创建线程的方式。扩展Thread类可以通过扩展Thread类并覆盖其run()方法来创建线程:

Java 线程

线程使程序能够通过同时执行多个任务而更有效地运行。

线程可用于在不中断主程序的情况下在后台执行复杂的任务。

创建线程

有两种创建线程的方式。

  1. 扩展Thread类

可以通过扩展Thread类并覆盖其run()方法来创建线程:

public class MyThread extends Thread {
   
  public void run() {
   
    System.out.println("This code is running in a thread");
  }
}
  1. 实现Runnable接口

另一种创建线程的方式是实现Runnable接口:

public class MyRunnable implements Runnable {
   
  public void run() {
   
    System.out.println("This code is running in a thread");
  }
}

运行线程

  1. 扩展Thread类

如果类扩展Thread类,则可以通过创建类的实例并调用其start()方法来运行线程:

public class Main {
   
  public static void main(String[] args) {
   
    MyThread myThread = new MyThread();
    myThread.start();
    System.out.println("This code is outside of the thread");
  }
}
  1. 实现Runnable接口

如果类实现了Runnable接口,则可以通过将类的实例传递给Thread对象的构造函数,然后调用线程的start()方法来运行线程:

public class Main {
   
  public static void main(String[] args) {
   
    MyRunnable myRunnable = new MyRunnable();
    Thread thread = new Thread(myRunnable);
    thread.start();
    System.out.println("This code is outside of the thread");
  }
}

区分“扩展”和“实现”线程

主要区别在于,当一个类扩展Thread类时,无法扩展任何其他类,但通过实现Runnable接口,可以扩展另一个类,例如:

class MyClass extends OtherClass implements Runnable

并发问题

因为线程与程序的其他部分同时运行,所以无法知道代码将按照什么顺序运行。当线程和主程序同时读取和写入相同的变量时,值是不可预测的。由此导致的问题称为并发问题。

示例

一个变量amount值不可预测的代码示例:

public class Main extends Thread {
   
  public static int amount = 0;

  public static void main(String[] args) {
   
    Main thread = new Main();
    thread.start();
    System.out.println(amount);
    amount++;
    System.out.println(amount);
  }

  public void run() {
   
    amount++;
  }
}

为避免并发问题,最好尽可能少地在线程之间共享属性。如果需要共享属性,一种可能的解决方案是在使用线程可以更改的任何属性之前,使用线程的isAlive()方法检查线程是否已完成运行。

示例

使用isAlive()防止并发问题:

public class Main extends Thread {
   
  public static int amount = 0;

  public static void main(String[] args) {
   
    Main thread = new Main();
    thread.start();
    // 等待线程完成
    while (thread.isAlive()) {
   
      System.out.println("Waiting...");
    }
    // 更新amount并打印其值
    System.out.println("Main program: " + amount);
    amount++;
    System.out.println("Main program: " + amount);
  }

  public void run() {
   
    amount++;
  }
}

线程池

线程池是一种管理线程的资源。它允许您创建并维护一组可重用的线程。使用线程池可以提高应用程序的性能和效率。

线程安全

线程安全是指多个线程可以访问和修改数据而不导致数据损坏。使数据线程安全的一种方法是使用同步。同步是一种机制,它允许线程一次一个地访问共享数据。

常见的线程安全问题

  • 竞态条件:当多个线程同时访问共享数据并尝试对其进行更改时,就会发生竞态条件。这可能导致数据损坏。
  • 原子性:原子操作是指不可分割的操作。当多个线程尝试同时执行原子操作时,可能会导致数据损坏。
  • 可见性:当一个线程对共享数据进行更改时,其他线程必须能够看到这些更改。

避免线程安全问题

  • 使用同步
  • 使用不可变对象
  • 使用原子操作

Java Lambda表达式

Lambda表达式简介

Lambda表达式是在Java 8中引入的。Lambda表达式是一小段代码块,它接受参数并返回一个值。Lambda表达式类似于方法,但它们不需要名称,并且可以直接在方法体中实现。

Lambda表达式的语法

最简单的Lambda表达式包含一个参数和一个表达式:

参数 -> 表达式

要使用多个参数,请将它们放在括号中:

(参数1, 参数2) -> 表达式

表达式是有限制的。它们必须立即返回一个值,并且不能包含变量、赋值或if或for等语句。为了执行更复杂的操作,可以使用带有花括号的代码块。如果Lambda表达式需要返回一个值,那么代码块应该有一个return语句。

(参数1, 参数2) -> {
    代码块 }

Lambda表达式的使用

Lambda表达式通常作为参数传递给函数。在以下示例中,Lambda表达式作为参数传递给ArrayList的forEach()方法,以打印列表中的每个项:

import java.util.ArrayList;

public class Main {
   
  public static void main(String[] args) {
   
    ArrayList<Integer> numbers = new ArrayList<>();
    numbers.add(5);
    numbers.add(9);
    numbers.add(8

);
    numbers.add(1);
    numbers.forEach((n) -> {
    System.out.println(n); });
  }
}

Lambda表达式的存储

如果变量的类型是仅具有一个方法的接口,那么Lambda表达式可以存储在变量中。Lambda表达式应该具有与该方法相同数量的参数和相同的返回类型。Java内置了许多这类接口,如Consumer接口(在java.util包中),它被列表使用。

import java.util.ArrayList;
import java.util.function.Consumer;

public class Main {
   
  public static void main(String[] args) {
   
    ArrayList<Integer> numbers = new ArrayList<>();
    numbers.add(5);
    numbers.add(9);
    numbers.add(8);
    numbers.add(1);
    Consumer<Integer> method = (n) -> {
    System.out.println(n); };
    numbers.forEach(method);
  }
}

Lambda表达式作为方法参数

要在方法中使用Lambda表达式,该方法应该有一个以单一方法接口作为其类型的参数。调用接口的方法将运行Lambda表达式。

interface StringFunction {
   
  String run(String str);
}

public class Main {
   
  public static void main(String[] args) {
   
    StringFunction exclaim = (s) -> s + "!";
    StringFunction ask = (s) -> s + "?";
    printFormatted("Hello", exclaim);
    printFormatted("Hello", ask);
  }

  public static void printFormatted(String str, StringFunction format) {
   
    String result = format.run(str);
    System.out.println(result);
  }
}

Lambda表达式的优势

  • 简化代码
  • 提高可读性
  • 增强代码的表达力

Lambda 表达式是 Java 8 中引入的一项强大功能,可以简化代码并提高可读性。它们是函数式编程的重要组成部分,可以用于各种任务,例如数据处理、事件处理和流处理。

最后

为了方便其他设备和平台的小伙伴观看往期文章:

微信公众号搜索:Let us Coding,关注后即可获取最新文章推送

看完如果觉得有帮助,欢迎 点赞、收藏、关注

相关文章
|
2天前
|
存储 网络协议 安全
Java网络编程,多线程,IO流综合小项目一一ChatBoxes
**项目介绍**:本项目实现了一个基于TCP协议的C/S架构控制台聊天室,支持局域网内多客户端同时聊天。用户需注册并登录,用户名唯一,密码格式为字母开头加纯数字。登录后可实时聊天,服务端负责验证用户信息并转发消息。 **项目亮点**: - **C/S架构**:客户端与服务端通过TCP连接通信。 - **多线程**:采用多线程处理多个客户端的并发请求,确保实时交互。 - **IO流**:使用BufferedReader和BufferedWriter进行数据传输,确保高效稳定的通信。 - **线程安全**:通过同步代码块和锁机制保证共享数据的安全性。
46 23
|
4天前
|
缓存 运维 Java
Java静态代码块深度剖析:机制、特性与最佳实践
在Java中,静态代码块(或称静态初始化块)是指类中定义的一个或多个`static { ... }`结构。其主要功能在于初始化类级别的数据,例如静态变量的初始化或执行仅需运行一次的初始化逻辑。
23 4
|
19天前
|
Java
Java中执行命令并使用指定配置文件的最佳实践
通过本文的介绍,您可以了解如何在Java中使用 `ProcessBuilder`执行系统命令,并通过指定配置文件、设置环境变量和重定向输入输出流来控制命令的行为。通过这些最佳实践,可以确保您的Java应用程序在执行系统命令时更加健壮和灵活。
26 7
|
1月前
|
安全 Java 开发者
【JAVA】封装多线程原理
Java 中的多线程封装旨在简化使用、提高安全性和增强可维护性。通过抽象和隐藏底层细节,提供简洁接口。常见封装方式包括基于 Runnable 和 Callable 接口的任务封装,以及线程池的封装。Runnable 适用于无返回值任务,Callable 支持有返回值任务。线程池(如 ExecutorService)则用于管理和复用线程,减少性能开销。示例代码展示了如何实现这些封装,使多线程编程更加高效和安全。
|
3月前
|
Java
Java—多线程实现生产消费者
本文介绍了多线程实现生产消费者模式的三个版本。Version1包含四个类:`Producer`(生产者)、`Consumer`(消费者)、`Resource`(公共资源)和`TestMain`(测试类)。通过`synchronized`和`wait/notify`机制控制线程同步,但存在多个生产者或消费者时可能出现多次生产和消费的问题。 Version2将`if`改为`while`,解决了多次生产和消费的问题,但仍可能因`notify()`随机唤醒线程而导致死锁。因此,引入了`notifyAll()`来唤醒所有等待线程,但这会带来性能问题。
Java—多线程实现生产消费者
|
2月前
|
缓存 安全 算法
Java 多线程 面试题
Java 多线程 相关基础面试题
|
3月前
|
安全 Java Kotlin
Java多线程——synchronized、volatile 保障可见性
Java多线程中,`synchronized` 和 `volatile` 关键字用于保障可见性。`synchronized` 保证原子性、可见性和有序性,通过锁机制确保线程安全;`volatile` 仅保证可见性和有序性,不保证原子性。代码示例展示了如何使用 `synchronized` 和 `volatile` 解决主线程无法感知子线程修改共享变量的问题。总结:`volatile` 确保不同线程对共享变量操作的可见性,使一个线程修改后,其他线程能立即看到最新值。
|
3月前
|
消息中间件 缓存 安全
Java多线程是什么
Java多线程简介:本文介绍了Java中常见的线程池类型,包括`newCachedThreadPool`(适用于短期异步任务)、`newFixedThreadPool`(适用于固定数量的长期任务)、`newScheduledThreadPool`(支持定时和周期性任务)以及`newSingleThreadExecutor`(保证任务顺序执行)。同时,文章还讲解了Java中的锁机制,如`synchronized`关键字、CAS操作及其实现方式,并详细描述了可重入锁`ReentrantLock`和读写锁`ReadWriteLock`的工作原理与应用场景。
|
1月前
|
Linux
Linux编程: 在业务线程中注册和处理Linux信号
本文详细介绍了如何在Linux中通过在业务线程中注册和处理信号。我们讨论了信号的基本概念,并通过完整的代码示例展示了在业务线程中注册和处理信号的方法。通过正确地使用信号处理机制,可以提高程序的健壮性和响应能力。希望本文能帮助您更好地理解和应用Linux信号处理,提高开发效率和代码质量。
48 17
|
1月前
|
Linux
Linux编程: 在业务线程中注册和处理Linux信号
通过本文,您可以了解如何在业务线程中注册和处理Linux信号。正确处理信号可以提高程序的健壮性和稳定性。希望这些内容能帮助您更好地理解和应用Linux信号处理机制。
59 26