Java 入门教程(十一):多线程与 Lambda
1830 字
9 分钟
Java 入门教程(十一):多线程与 Lambda
- 1Java 入门教程(一):JDK、IDEA 与第一个程序
- 2Java 入门教程(二):语法基础与运算符
- 3Java 入门教程(三):流程控制
- 4Java 入门教程(四):方法与数组
- 5Java 入门教程(五):面向对象(上)——类与封装
- 6Java 入门教程(六):面向对象(下)——继承、多态与接口
- 7Java 入门教程(七):核心类库——String、包装类与常用工具
- 8Java 入门教程(八):集合框架
- 9Java 入门教程(九):异常处理
- 10Java 入门教程(十):文件 IO 与 NIO
- 11Java 入门教程(十一):多线程与 Lambda本文
- 12Java 入门教程(十二):Stream API 与综合实战
本篇目标
两个主题一次讲完:多线程(让程序同时干多件事)与 Lambda(函数式语法)。它们是 Java 8 的双主角,也是后端面试的必考区。目标:会建线程、懂同步、用线程池、熟练写 Lambda。
一、线程基础
为什么需要多线程
单线程:下载文件时界面卡死(一件事阻塞一切)。多线程:下载归下载、界面归界面——并发提升响应与吞吐。
创建线程两法
// 法1:继承 Threadclass Downloader extends Thread { private final String file; Downloader(String file) { this.file = file; }
@Override public void run() { // 线程要做的事 System.out.println("下载 " + file + " 开始"); try { Thread.sleep(1000); } // 模拟耗时 catch (InterruptedException e) { return; } System.out.println("下载 " + file + " 完成"); }}
// 法2:实现 Runnable(推荐——不占用继承名额)class Task implements Runnable { @Override public void run() { System.out.println("任务运行在: " + Thread.currentThread().getName()); }}
public class ThreadDemo { public static void main(String[] args) throws InterruptedException { Thread t1 = new Downloader("a.zip"); Thread t2 = new Thread(new Task(), "工作线程-2"); // 第二参命名 Thread t3 = new Thread(() -> System.out.println("Lambda 更简洁"), "工作线程-3");
t1.start(); // ⚠ 调 start() 不是 run()!run() 只是普通方法调用 t2.start(); t3.start();
t1.join(); // main 线程等 t1 干完再往下走 System.out.println("main 继续"); }}Warning
start() 启动新线程执行 run;直接调 run() 是在当前线程执行——没有新线程,这是新手第一大坑。
常用方法
Thread t = Thread.currentThread(); // 当前线程t.getName();Thread.sleep(500); // 当前线程睡 500ms(静态方法)t.join(); // 等 t 结束Thread.yield(); // 建议让出 CPU(了解)t.setDaemon(true); // 守护线程(主程序结束它陪葬)t.setPriority(Thread.MAX_PRIORITY); // 优先级(仅建议性)二、线程安全与 synchronized
竞态条件:多线程第一坑
class Counter { private int count = 0; public void increment() { count++; } // count++ 不是原子操作! public int get() { return count; }}
// 1000 个线程各加 1000 次 → 理论 100 万,实际常小于(丢失更新)count++ 实际是”读→改→写”三步,两线程交错执行就丢更新。
synchronized:互斥锁
class SafeCounter { private int count = 0;
public synchronized void increment() { // 同一时刻只有一个线程能进入 count++; } public synchronized int get() { return count; }}
// 同步块版(锁更小范围)class Pair { private int a = 0; public void add() { synchronized (this) { // 锁住当前实例 a++; } }}原子类:无锁方案(简单计数首选)
import java.util.concurrent.atomic.AtomicInteger;
class AtomicCounter { private final AtomicInteger count = new AtomicInteger(); public void increment() { count.incrementAndGet(); } // CAS 实现,无锁高效 public int get() { return count.get(); }}生产者-消费者(BlockingQueue,工程正解)
import java.util.concurrent.*;
public class ProducerConsumer { public static void main(String[] args) throws Exception { BlockingQueue<Integer> queue = new LinkedBlockingQueue<>(5); // 容量5
// 生产者:往队列放(满了自动阻塞等待) Thread producer = new Thread(() -> { try { for (int i = 1; i <= 10; i++) { queue.put(i); System.out.println("生产 " + i); } queue.put(-1); // 结束信号 } catch (InterruptedException ignored) {} });
// 消费者:从队列取(空了自动阻塞等待) Thread consumer = new Thread(() -> { try { int v; while ((v = queue.take()) != -1) System.out.println(" 消费 " + v); } catch (InterruptedException ignored) {} });
producer.start(); consumer.start(); producer.join(); consumer.join(); }}三、线程池:生产标配
手动 new Thread 有创建销毁开销、无法控数量。线程池 = 线程复用 + 队列排队 + 限流:
import java.util.concurrent.*;
public class PoolDemo { public static void main(String[] args) throws Exception { // 固定 4 个工作线程的池 ExecutorService pool = Executors.newFixedThreadPool(4);
// 提交 10 个任务(自动排队分发) for (int i = 1; i <= 10; i++) { final int taskId = i; pool.submit(() -> { // submit 接收 Runnable/Callable System.out.printf("任务%d 由 %s 处理%n", taskId, Thread.currentThread().getName()); try { Thread.sleep(300); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } return taskId * taskId; // Callable 可返回结果 }); }
// Callable + Future:拿异步结果 Future<Integer> future = pool.submit(() -> { Thread.sleep(500); return 42; }); System.out.println("等结果..."); System.out.println("结果 = " + future.get()); // 阻塞直到完成
pool.shutdown(); // 温和关闭(等任务做完) // pool.awaitTermination(5, TimeUnit.SECONDS); }}池的选型:
| 工厂方法 | 适用 |
|---|---|
newFixedThreadPool(n) | 负载稳定(默认首选) |
newCachedThreadPool() | 短时大量小任务 |
newSingleThreadExecutor() | 任务需严格串行 |
Tip
生产环境推荐 new ThreadPoolExecutor(...) 手动配七个参数(核心数/最大数/队列/拒绝策略),框架(Spring/Tomcat)内部全是定制池——入门先会 Executors,进阶再读源码。
四、Lambda 与函数式接口
从匿名内部类到 Lambda
// 需求:按长度排序List<String> list = new ArrayList<>(List.of("banana", "apple", "fig"));
// 远古写法list.sort(new Comparator<String>() { @Override public int compare(String a, String b) { return a.length() - b.length(); }});
// Lambda 写法list.sort((a, b) -> a.length() - b.length());
// 方法引用:更极致的简洁list.sort(Comparator.comparingInt(String::length));Lambda 语法全解
// (参数) -> 表达式 或 { 语句块 }Runnable r1 = () -> System.out.println("无参");Runnable r2 = () -> { System.out.println("多行"); System.out.println("要花括号");};
// 单参可省括号(类型自动推断)java.util.function.Function<String, Integer> len = s -> s.length();java.util.function.Function<String, Integer> len2 = String::length; // 方法引用
// 有返回值的java.util.function.BinaryOperator<Integer> add = (a, b) -> a + b;四大函数式接口(java.util.function)
// Function<T,R>:T → R(转换)Function<String, Integer> parse = Integer::parseInt;System.out.println(parse.apply("42"));
// Predicate<T>:T → boolean(判断)Predicate<String> isEmpty = String::isEmpty;System.out.println(isEmpty.test("")); // true
// Consumer<T>:T → void(消费)Consumer<String> printer = s -> System.out.println("⚡ " + s);printer.accept("hello");
// Supplier<T>:() → T(生产/惰性)Supplier<Double> random = Math::random;System.out.println(random.get());Lambda 能出现在任何”函数式接口”(只有一个抽象方法的接口)的位置——Runnable、Comparator、以及上面四大金刚。
方法引用四式
String::length // 对象方法:x -> x.length()System.out::println // 特定对象方法:x -> System.out.println(x)Integer::parseInt // 静态方法:s -> Integer.parseInt(s)ArrayList::new // 构造器:() -> new ArrayList<>()五、Optional:告别 NPE
import java.util.Optional;
// 包装可能为 null 的返回值Optional<String> findUser(int id) { return id == 1 ? Optional.of("小明") : Optional.empty();}
// 使用方(再也不写 if (x != null))findUser(1).ifPresent(name -> System.out.println("找到 " + name));
String name = findUser(2).orElse("游客"); // 默认值String n2 = findUser(2).orElseGet(() -> "懒加载默认"); // 惰性默认String n3 = findUser(1).orElseThrow( () -> new RuntimeException("用户不存在")); // 强制要求六、实战:并发下载统计
import java.util.concurrent.*;import java.util.concurrent.atomic.AtomicLong;
public class DownloadSimulator { public static void main(String[] args) throws Exception { String[] files = {"a.zip", "b.zip", "c.zip", "d.zip", "e.zip"}; AtomicLong totalBytes = new AtomicLong(); // 原子累计 ExecutorService pool = Executors.newFixedThreadPool(3);
var futures = new java.util.ArrayList<Future<Long>>(); long start = System.currentTimeMillis();
for (String f : files) { futures.add(pool.submit(() -> { // Callable 返回本文件字节数 Thread.sleep(500); // 模拟下载 long size = (long) (Math.random() * 1_000_000); totalBytes.addAndGet(size); System.out.printf(" [%s] %s 完成: %,d B%n", Thread.currentThread().getName(), f, size); return size; })); }
long sum = 0; for (Future<Long> fu : futures) sum += fu.get(); // 收齐结果
pool.shutdown(); System.out.printf("全部完成:%d 文件,共 %,d 字节,并发耗时 %d ms%n", files.length, sum, System.currentTimeMillis() - start); // 约 1 秒(串行要 2.5 秒) }}小练习
- 创建 3 个线程分别打印 1~20,观察输出交错;用
join让它们严格顺序执行 - 用 AtomicInteger 写”10 线程各加 10 万次”验证总数正确;改成普通 int 对比丢失
- 线程池提交 20 个计算任务(平方),用
Future收集结果求和 - 用
Function链式组合:andThen实现”字符串 → 长度 → 平方” - 用 Optional 重写”根据 id 查学生成绩”:找不到返回默认 0 并打印提示
本篇小结
- Thread 两种创建法;start() 启动、join() 等待;sleep 静态方法
- count++ 非原子;synchronized 互斥、AtomicInteger 无锁计数
- BlockingQueue 天然解决生产者消费者
- 线程池三工厂 + submit/Future 收结果;生产用 ThreadPoolExecutor 定制
- Lambda 只认函数式接口;四大金刚 Function/Predicate/Consumer/Supplier
- 方法引用四式;Optional 优雅防空
下一篇:Stream API 与综合实战——系列的收官之战。
本系列为 Cyber先锋社原创教程。“并发耗时 vs 串行耗时”的对比实验请亲手跑,体感最深刻。
文章分享
如果这篇文章对你有帮助,欢迎分享给更多人!
Java 入门教程(十一):多线程与 Lambda
https://www.cyberpioneer.club/posts/java-tutorial-11/相关文章智能推荐
1
Java 入门教程(四):方法与数组
Java 教程掌握 Java 方法定义与重载、可变参数、值传递本质、数组全操作(声明/遍历/工具类 Arrays/二维数组)、命令行参数与 main 签名,附数组统计与矩阵转置实战。
2
Java 入门教程(二):语法基础与运算符
Java 教程详解 Java 基本语法:注释、变量与常量、八种基本类型与类型转换规则、七类运算符、Scanner 输入进阶与 printf 格式化,配典型陷阱与对照示例。
3
Java 入门教程(一):JDK、IDEA 与第一个程序
Java 教程进入 Java 的世界:理解"一次编写,到处运行"的原理、安装 JDK 与 IDEA、解剖第一个程序的结构、掌握变量与数据类型。企业级开发语言的起点。
4
Java 入门教程(三):流程控制
Java 教程完整讲解 Java 分支(if/switch 语句与 switch 表达式)、三种循环、break/continue 带标签跳转、嵌套循环经典练习(九九表/质数/图形/水仙花),覆盖 Java 14+ 增强 switch 新语法。
5
Java 入门教程(十):文件 IO 与 NIO
Java 教程掌握 Java 文件读写全套路:File 与 Path、字节流与字符流、缓冲流、Files 工具类一行流(readString/write/lines/walk)、CSV 通讯录持久化实战、try-with-resources 综合运用。
随机文章随机推荐



