Java(七)

36 阅读6分钟

本章介绍线程基础、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);  // 数据竞争
    }
}

不要用的情况(清单)

  1. 元素少于几千(除非单项计算极重)
  2. 涉及 IO(读 DB、读文件、HTTP)
  3. 依赖 ThreadLocal、事务、Spring 请求上下文(Web 线程模型)
  4. 需要稳定顺序且操作非关联
  5. 在已经很多的线程里再 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
Streamfilter/map/collect;避免副作用
parallel默认慎用