本章介绍线程基础、Lambda、Stream。
⚠️ Java 的 -> 不是 MyBatis
| 语境 | 含义 |
|---|---|
| Java Lambda | (x) -> x + 1 匿名函数语法 |
| MyBatis XML / 注解 | #{name} 参数占位、${} 字符串替换 |
| 12 章「表达式」 | 可能指 SQL 片段或 SpEL,与 Java Lambda 语法无关 |
初学时切勿把「箭头」混为一谈:Stream 里 n -> n * 2 是 Java 语言特性;MyBatis 不会写 user -> user.getName() 在 XML 里。
1. Thread 与 Runnable
概念
- 进程:OS 资源单位;线程:进程内调度单位,共享堆内存。
- 实现多线程常见方式:继承
Thread(不推荐)或实现Runnable/Callable。
原理
Java 线程映射到 OS 线程(平台线程)。并发问题来自共享可变状态的竞争。
完整示例
package com.example.demo;
public class ThreadDemo {
static class HelloTask implements Runnable {
private final String name;
HelloTask(String name) { this.name = name; }
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " says " + name);
}
}
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(new HelloTask("hi"), "worker-1");
t.start();
t.join(); // 等待结束
System.out.println("main done");
}
}
练习
开两个线程各打印 1~5,观察输出交错(不保证顺序)。
完整例子
package com.example.demo;
public class TwoThreadsPrint {
public static void main(String[] args) throws InterruptedException {
Runnable task = () -> {
for (int i = 1; i <= 5; i++) {
System.out.println(Thread.currentThread().getName() + " -> " + i);
}
};
Thread t1 = new Thread(task, "A");
Thread t2 = new Thread(task, "B");
t1.start();
t2.start();
t1.join();
t2.join();
}
}
常见坑
- 调用
run()而非start()— 仍在当前线程同步执行。 - 共享变量无同步导致计数错误(见下一节)。
2. synchronized 入门
概念
synchronized 保证同一监视器锁上互斥进入,用于保护临界区。
原理
每个对象可作锁;synchronized (obj) { } 或 synchronized 实例/静态方法。
完整示例
package com.example.demo;
public class CounterDemo {
static class Counter {
private int value;
synchronized void increment() { value++; }
synchronized int get() { return value; }
}
public static void main(String[] args) throws InterruptedException {
Counter c = new Counter();
Thread t1 = new Thread(() -> { for (int i = 0; i < 1000; i++) c.increment(); });
Thread t2 = new Thread(() -> { for (int i = 0; i < 1000; i++) c.increment(); });
t1.start(); t2.start();
t1.join(); t2.join();
System.out.println(c.get()); // 2000
}
}
练习
把 Counter 改成无 synchronized,对比结果。
完整例子
package com.example.demo;
public class UnsafeCounterDemo {
static class Counter {
private int value;
void increment() { value++; } // 无 synchronized
int get() { return value; }
}
public static void main(String[] args) throws InterruptedException {
Counter c = new Counter();
Runnable r = () -> { for (int i = 0; i < 1000; i++) c.increment(); };
Thread t1 = new Thread(r), t2 = new Thread(r);
t1.start(); t2.start();
t1.join(); t2.join();
System.out.println(c.get()); // 常 < 2000
}
}
常见坑
- 锁错对象(两个 Counter 实例各锁各的)。
String字面量作锁 — 常量池可能意外共享锁。- 初学不必深入
volatile/java.util.concurrent,Spring 业务多数在 Web 请求线程内单线程写 Service(仍要注意 static 可变状态)。
3. Lambda 表达式
概念
Lambda:简洁表示函数式接口(仅一个抽象方法的接口)的实现。语法:(参数) -> 表达式或 { 语句; }
原理
编译器生成 synthetic 方法 + invokedynamic;本质仍是对象(实现接口)。
完整示例
package com.example.demo;
import java.util.Comparator;
import java.util.List;
public class LambdaDemo {
@FunctionalInterface
interface MathOperation {
int apply(int a, int b);
}
public static void main(String[] args) {
MathOperation add = (a, b) -> a + b;
System.out.println(add.apply(3, 4));
List<String> names = List.of("bob", "alice", "carol");
names.stream()
.sorted(Comparator.comparing(String::length))
.forEach(n -> System.out.println(n));
// 等价匿名类(旧写法)
MathOperation mul = new MathOperation() {
@Override
public int apply(int a, int b) { return a * b; }
};
}
}
练习
用 Lambda 实现 Comparator<User> 按 id 排序
完整例子
package com.example.demo;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
public class UserSortDemo {
record User(Long id, String name) {}
public static void main(String[] args) {
List<User> users = new ArrayList<>(List.of(
new User(3L, "c"), new User(1L, "a"), new User(2L, "b")));
users.sort(Comparator.comparing(User::id));
// 或:users.sort((u1, u2) -> Long.compare(u1.id(), u2.id()));
System.out.println(users);
}
}
常见坑
- Lambda 引用的局部变量必须是 effectively final。
- 与 MyBatis 无关 — 见文首说明。
4. 方法引用 ::
概念
已有方法的简写:
| 形式 | 示例 |
|---|---|
| 静态 | Integer::parseInt |
| 实例 | System.out::println |
| 类型 | String::length |
| 构造 | ArrayList::new |
完整示例
package com.example.demo;
import java.util.List;
import java.util.stream.Collectors;
public class MethodRefDemo {
public static void main(String[] args) {
List<String> list = List.of("a", "bb", "ccc");
List<Integer> lengths = list.stream()
.map(String::length)
.collect(Collectors.toList());
System.out.println(lengths);
}
}
常见坑
- 方法签名与函数式接口抽象方法不匹配时编译失败。
Class::new需无参构造器。
5. Stream 常用操作
概念
Stream 是对元素序列的声明式 pipeline:中间操作(lazy)+ 终端操作(触发计算)。
不要改外部可变状态;尽量无 side effect。
原理
中间操作返回新 Stream;终端操作如 collect、forEach、reduce 消费 Stream(同一 Stream 通常只能用一次)。
完整示例
package com.example.demo;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class StreamDemo {
record Order(long id, double amount, String status) {}
public static void main(String[] args) {
List<Order> orders = List.of(
new Order(1, 99, "PAID"),
new Order(2, 150, "CREATED"),
new Order(3, 80, "PAID")
);
double paidTotal = orders.stream()
.filter(o -> "PAID".equals(o.status))
.mapToDouble(Order::amount)
.sum();
Map<String, Long> byStatus = orders.stream()
.collect(Collectors.groupingBy(Order::status, Collectors.counting()));
List<Long> ids = orders.stream()
.sorted((a, b) -> Double.compare(b.amount, a.amount))
.map(Order::id)
.toList(); // Java 16+ 不可变列表
System.out.println(paidTotal);
System.out.println(byStatus);
System.out.println(ids);
}
}
练习
对 List<String>:去重、转大写、按长度降序,收集为 List。
对订单列表求 PAID 平均金额(无 PAID 时返回 0)。
完整例子
package com.example.demo.exercise;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class StreamExercises {
record Order(long id, double amount, String status) {}
public static void main(String[] args) {
List<String> words = List.of("Java", "is", "FUN", "java", "Stream");
List<String> result = words.stream()
.map(String::toLowerCase)
.distinct()
.sorted(Comparator.comparingInt(String::length).reversed())
.toList();
System.out.println(result);
List<Order> orders = List.of(
new Order(1, 99, "PAID"),
new Order(2, 150, "CREATED"),
new Order(3, 80, "PAID"));
double avg = orders.stream()
.filter(o -> "PAID".equals(o.status()))
.mapToDouble(Order::amount)
.average()
.orElse(0);
double paidTotal = orders.stream()
.filter(o -> "PAID".equals(o.status()))
.mapToDouble(Order::amount)
.sum();
Map<String, Long> byStatus = orders.stream()
.collect(Collectors.groupingBy(Order::status, Collectors.counting()));
System.out.println("avg=" + avg + " total=" + paidTotal + " " + byStatus);
}
}
常见坑
- 在
forEach里修改外部ArrayList— 用collect。 map返回 null 导致 NPE — 用filter(Objects::nonNull)或mapOptional模式。
6. 何时不要用 parallelStream
概念
parallelStream() 利用 ForkJoinPool.commonPool() 并行化 pipeline。
原理
仅当数据量大、计算CPU 密集、无共享可变状态、拆分成本低时可能提速。
小集合、IO 密集、已有线程池环境、需严格顺序时,并行常更慢或更错。
完整示例
package com.example.demo;
import java.util.List;
import java.util.stream.IntStream;
public class ParallelStreamDemo {
public static void main(String[] args) {
List<Integer> small = IntStream.rangeClosed(1, 100).boxed().toList();
// 一般不要用
long sum = small.parallelStream().mapToInt(i -> i).sum();
// 更糟:并行 + 非线程安全容器
// List<Integer> bad = new ArrayList<>();
// small.parallelStream().forEach(bad::add); // 数据竞争
}
}
不要用的情况(清单)
- 元素少于几千(除非单项计算极重)
- 涉及 IO(读 DB、读文件、HTTP)
- 依赖 ThreadLocal、事务、Spring 请求上下文(Web 线程模型)
- 需要稳定顺序且操作非关联
- 在已经很多的线程里再 parallel(common pool 饱和)
练习
列举 3 条 在练习代码里不应使用 parallelStream 的理由(笔答)。
完整例子
package com.example.demo;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.IntStream;
/**
* 演示练习代码里为何通常不该用 parallelStream(对应 3 条理由)。
*/
public class WhenNotParallelDemo {
public static void main(String[] args) {
// 1) 数据量小:并行调度开销常大于收益
List<Integer> small = IntStream.rangeClosed(1, 100).boxed().toList();
int sum = small.parallelStream().mapToInt(i -> i).sum();
System.out.println("small parallel sum=" + sum); // 结果对,但往往不更快
// 2) 副作用 / 非线程安全容器:并行 forEach 往 ArrayList 塞会数据竞争
List<Integer> bad = new ArrayList<>();
// 切勿在练习/业务里这么写:
// small.parallelStream().forEach(bad::add);
small.forEach(bad::add); // 顺序流 + 副作用才安全(仍优先 collect)
System.out.println("safe size=" + bad.size());
// 3) 依赖顺序 / ThreadLocal / 请求上下文时,commonPool 并行会破坏假设
List<String> words = List.of("a", "b", "c");
String joined = String.join(",", words.stream().toList()); // 保持遇见顺序
// words.parallelStream() 的遇见顺序与线程调度相关,业务日志/事务场景慎用
System.out.println("ordered join=" + joined);
}
}
常见坑
- 以为 parallel 一定更快 — 先测量。
parallelStream与synchronized块混用导致死锁或性能骤降。
7. Optional 与 Stream(衔接)
package com.example.demo;
import java.util.Optional;
public class OptionalStreamDemo {
public static void main(String[] args) {
Optional<String> opt = Optional.of("java");
opt.map(String::toUpperCase).ifPresent(System.out::println);
}
}
本章小结
| 主题 | 要点 |
|---|---|
| 线程 | Runnable + start/join |
| 同步 | synchronized 保护共享状态 |
| Lambda | -> 是 Java 语法,不是 MyBatis |
| Stream | filter/map/collect;避免副作用 |
| parallel | 默认慎用 |