MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Java Lambda表达式与多线程编程的结合

2023-04-156.6k 阅读

Java Lambda 表达式基础回顾

在深入探讨 Java Lambda 表达式与多线程编程的结合之前,我们先来回顾一下 Lambda 表达式的基本概念。

Lambda 表达式是 Java 8 引入的一个重要特性,它提供了一种简洁的方式来表示可传递给方法或存储在变量中的代码块。Lambda 表达式本质上是一个匿名函数,它没有名称,但可以有参数列表、函数体、返回类型,甚至可以抛出异常。

其基本语法格式为:(parameters) -> expression 或者 (parameters) -> { statements; }。例如,一个简单的加法 Lambda 表达式:

BinaryOperator<Integer> add = (a, b) -> a + b;
int result = add.apply(3, 5);
System.out.println(result); 

在这个例子中,(a, b) -> a + b 就是一个 Lambda 表达式,它接受两个 Integer 类型的参数 ab,并返回它们的和。

Lambda 表达式的优势在于它大大简化了代码的编写,尤其是在处理一些只有单一方法的接口(即函数式接口)时。函数式接口是指只包含一个抽象方法的接口,Java 8 中提供了许多这样的接口,如 RunnableCallableConsumerFunction 等。

多线程编程基础

在 Java 中,多线程编程是一项重要的技术,它允许程序同时执行多个任务,从而提高程序的效率和响应性。

Java 提供了两种创建线程的主要方式:继承 Thread 类和实现 Runnable 接口。

继承 Thread 类

通过继承 Thread 类来创建线程,需要重写 run 方法,在 run 方法中定义线程要执行的任务。例如:

class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("线程 " + getName() + " 正在运行");
    }
}

public class ThreadExample1 {
    public static void main(String[] args) {
        MyThread thread = new MyThread();
        thread.start();
    }
}

在这个例子中,MyThread 类继承自 Thread 类,并重写了 run 方法。在 main 方法中,创建了 MyThread 的实例并调用 start 方法来启动线程。

实现 Runnable 接口

实现 Runnable 接口也是创建线程的常见方式。Runnable 接口只有一个抽象方法 run,我们需要在实现类中实现这个方法。例如:

class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("线程 " + Thread.currentThread().getName() + " 正在运行");
    }
}

public class ThreadExample2 {
    public static void main(String[] args) {
        MyRunnable runnable = new MyRunnable();
        Thread thread = new Thread(runnable);
        thread.start();
    }
}

这里,MyRunnable 类实现了 Runnable 接口,在 main 方法中,先创建 MyRunnable 的实例,然后将其作为参数传递给 Thread 类的构造函数来创建线程并启动。

Java Lambda 表达式与 Runnable 接口结合

由于 Runnable 接口是一个函数式接口(只包含一个 run 抽象方法),因此可以很方便地使用 Lambda 表达式来创建 Runnable 实例。

传统方式创建 Runnable 实例并启动线程如下:

class TraditionalRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("传统方式创建的 Runnable 线程正在运行");
    }
}

public class TraditionalRunnableExample {
    public static void main(String[] args) {
        TraditionalRunnable runnable = new TraditionalRunnable();
        Thread thread = new Thread(runnable);
        thread.start();
    }
}

使用 Lambda 表达式后,代码变得更加简洁:

public class LambdaRunnableExample {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> System.out.println("Lambda 方式创建的 Runnable 线程正在运行"));
        thread.start();
    }
}

在这个例子中,() -> System.out.println("Lambda 方式创建的 Runnable 线程正在运行") 就是一个 Lambda 表达式,它替代了传统的 Runnable 实现类。这种方式不仅减少了代码量,还使代码的意图更加清晰。

线程池与 Lambda 表达式

线程池是多线程编程中的一个重要概念,它可以管理一组线程,复用线程资源,从而提高性能并减少资源消耗。Java 提供了 ExecutorService 接口及其实现类来实现线程池功能。

使用 ExecutorService 创建线程池

以下是使用 ExecutorService 创建固定大小线程池并提交任务的示例:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadPoolExample1 {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(3);
        for (int i = 0; i < 5; i++) {
            int taskNumber = i;
            executorService.submit(() -> {
                System.out.println("任务 " + taskNumber + " 由线程 " + Thread.currentThread().getName() + " 执行");
            });
        }
        executorService.shutdown();
    }
}

在这个例子中,首先通过 Executors.newFixedThreadPool(3) 创建了一个固定大小为 3 的线程池。然后,使用 submit 方法向线程池提交了 5 个任务,每个任务都是一个 Lambda 表达式表示的 Runnable。线程池会按照其规则分配线程来执行这些任务。最后,调用 shutdown 方法关闭线程池。

提交 Callable 任务与 Future

Callable 接口也是一个函数式接口,与 Runnable 不同的是,Callablecall 方法可以返回一个值并且可以抛出异常。当我们需要在线程执行结束后获取返回值时,可以使用 Callable

以下是使用线程池提交 Callable 任务并获取返回值的示例:

import java.util.concurrent.*;

public class ThreadPoolCallableExample {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        Callable<Integer> callable = () -> {
            System.out.println("Callable 任务由线程 " + Thread.currentThread().getName() + " 执行");
            return 42;
        };
        Future<Integer> future = executorService.submit(callable);
        Integer result = future.get();
        System.out.println("任务返回值: " + result);
        executorService.shutdown();
    }
}

在这个例子中,Callable<Integer> callable = () -> {... } 是一个 Lambda 表达式创建的 Callable 实例。通过 executorService.submit(callable) 提交任务后,会返回一个 Future 对象,通过调用 future.get() 方法可以获取任务的返回值。

CompletableFuture 与 Lambda 表达式

CompletableFuture 是 Java 8 引入的一个强大的异步编程工具,它可以帮助我们更方便地处理异步任务的结果,并且支持链式调用和组合多个异步任务。

简单的 CompletableFuture 示例

以下是一个简单的使用 CompletableFuture 执行异步任务并处理结果的示例:

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

public class CompletableFutureExample1 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "任务完成";
        });
        String result = future.get();
        System.out.println(result);
    }
}

在这个例子中,CompletableFuture.supplyAsync(() -> {... }) 使用 Lambda 表达式创建了一个异步任务,该任务会在后台线程中执行。future.get() 方法会阻塞当前线程,直到异步任务完成并返回结果。

CompletableFuture 的链式调用

CompletableFuture 支持链式调用,通过 thenApplythenAcceptthenRun 等方法可以对异步任务的结果进行进一步处理。例如:

import java.util.concurrent.CompletableFuture;

public class CompletableFutureChainingExample {
    public static void main(String[] args) {
        CompletableFuture.supplyAsync(() -> "初始任务结果")
               .thenApply(result -> result + " 经过处理")
               .thenAccept(System.out::println);
    }
}

在这个例子中,supplyAsync 方法启动一个异步任务,返回一个 CompletableFuture。然后通过 thenApply 方法对任务结果进行转换,thenAccept 方法消费最终的结果并打印。

组合多个 CompletableFuture

CompletableFuture 还提供了方法来组合多个异步任务,如 thenCombineallOfanyOf 等。

thenCombine 方法可以将两个异步任务的结果合并处理:

import java.util.concurrent.CompletableFuture;

public class CompletableFutureCombinationExample {
    public static void main(String[] args) {
        CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "结果1");
        CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "结果2");
        CompletableFuture<String> combinedFuture = future1.thenCombine(future2, (r1, r2) -> r1 + " 和 " + r2);
        combinedFuture.thenAccept(System.out::println);
    }
}

allOf 方法会等待所有的 CompletableFuture 都完成:

import java.util.concurrent.CompletableFuture;

public class CompletableFutureAllOfExample {
    public static void main(String[] args) {
        CompletableFuture<Void> future1 = CompletableFuture.runAsync(() -> System.out.println("任务1完成"));
        CompletableFuture<Void> future2 = CompletableFuture.runAsync(() -> System.out.println("任务2完成"));
        CompletableFuture<Void> allFutures = CompletableFuture.allOf(future1, future2);
        allFutures.join();
        System.out.println("所有任务完成");
    }
}

anyOf 方法会在任何一个 CompletableFuture 完成时就返回:

import java.util.concurrent.CompletableFuture;

public class CompletableFutureAnyOfExample {
    public static void main(String[] args) {
        CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "任务1结果";
        });
        CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "任务2结果";
        });
        CompletableFuture<Object> anyFuture = CompletableFuture.anyOf(future1, future2);
        anyFuture.thenAccept(System.out::println);
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

并发集合与 Lambda 表达式

在多线程编程中,使用线程安全的集合类是非常重要的。Java 提供了许多并发集合类,如 ConcurrentHashMapCopyOnWriteArrayList 等。这些集合类在多线程环境下能够保证数据的一致性和线程安全性。

使用 ConcurrentHashMap 与 Lambda 表达式

ConcurrentHashMap 支持在遍历和更新操作时使用 Lambda 表达式。例如,以下是使用 forEach 方法遍历 ConcurrentHashMap 的示例:

import java.util.concurrent.ConcurrentHashMap;

public class ConcurrentHashMapLambdaExample {
    public static void main(String[] args) {
        ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
        map.put("A", 1);
        map.put("B", 2);
        map.put("C", 3);
        map.forEach((key, value) -> System.out.println(key + " : " + value));
    }
}

在这个例子中,map.forEach((key, value) -> System.out.println(key + " : " + value)) 使用 Lambda 表达式遍历了 ConcurrentHashMap 中的每一个键值对。

使用 CopyOnWriteArrayList 与 Lambda 表达式

CopyOnWriteArrayList 是一个线程安全的列表,在遍历操作时性能较好。以下是使用 Lambda 表达式对 CopyOnWriteArrayList 进行遍历和过滤的示例:

import java.util.concurrent.CopyOnWriteArrayList;
import java.util.stream.Collectors;

public class CopyOnWriteArrayListLambdaExample {
    public static void main(String[] args) {
        CopyOnWriteArrayList<Integer> list = new CopyOnWriteArrayList<>();
        list.add(1);
        list.add(2);
        list.add(3);
        list.add(4);
        list.add(5);
        CopyOnWriteArrayList<Integer> filteredList = new CopyOnWriteArrayList<>(list.stream()
               .filter(num -> num % 2 == 0)
               .collect(Collectors.toList()));
        filteredList.forEach(System.out::println);
    }
}

在这个例子中,首先使用 stream 方法将 CopyOnWriteArrayList 转换为流,然后使用 filter 方法过滤出偶数,最后使用 collect 方法将结果收集到一个新的 CopyOnWriteArrayList 中,并使用 Lambda 表达式进行遍历打印。

总结 Lambda 与多线程结合的优势

通过上述各种场景的介绍,我们可以看到 Java Lambda 表达式与多线程编程结合带来了诸多优势。

首先,代码变得更加简洁明了。Lambda 表达式减少了传统方式下创建线程、实现接口等繁琐的代码结构,使开发人员能够更专注于业务逻辑。例如,在创建 Runnable 实例时,Lambda 表达式一行代码即可完成,而传统方式需要定义一个类并实现 run 方法。

其次,提高了代码的可读性。Lambda 表达式以一种更直接的方式表达了代码的意图,特别是在处理复杂的异步任务组合和链式调用时,如 CompletableFuture 的使用。通过链式调用和 Lambda 表达式,我们可以清晰地看到异步任务的执行流程和数据处理逻辑。

最后,增强了代码的灵活性和可维护性。Lambda 表达式可以很方便地作为参数传递给方法,使得在不同的多线程场景下可以复用相同的代码逻辑。例如,在使用线程池提交任务时,可以轻松地将不同的 Lambda 表达式作为任务提交,而不需要为每个任务创建单独的类。

综上所述,Java Lambda 表达式与多线程编程的结合为开发人员提供了更高效、简洁且强大的多线程编程方式,在现代 Java 开发中具有重要的地位。在实际项目中,合理运用这种结合方式可以显著提升代码的质量和开发效率。