手写答案

1.实现单例模式 (Singleton Pattern)

懒汉式:

使用双重检查锁定 (DCL) 实现线程安全的懒汉式单例模式。这种模式在第一次调用时才创建实例,并且通过两次检查和同步块确保了线程安全和性能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class Singleton {
// 1. volatile 关键字确保多线程环境下,instance 变量的修改能够立即被其他线程看到
private static volatile Singleton instance;

// 2. 私有化构造器,防止外部直接 new 实例
private Singleton() {}

// 3. 提供一个全局访问点,使用 DCL 确保线程安全
public static Singleton getInstance() {
// 第一次检查:如果实例已经存在,直接返回,避免不必要的同步
if (instance == null) {
// 第一次检查失败,进入同步块
synchronized (Singleton.class) {
// 第二次检查:在同步块内部再次检查,防止多个线程同时通过第一次检查,
// 导致创建多个实例
if (instance == null) {
// 创建实例
instance = new Singleton();
}
}
}
return instance;
}


public void showMessage() {
System.out.println("这是一个线程安全的懒汉式单例模式实例。");
}
}

饿汉式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package test1;

class SingletonEager {
// 1. 在类加载时就创建静态实例
private static final SingletonEager INSTANCE = new SingletonEager();

// 2. 私有化构造器,防止外部直接创建实例
private SingletonEager() {}

// 3. 提供一个公共的静态方法来获取唯一实例
public static SingletonEager getInstance() {
return INSTANCE;
}

public void showMessage() {
System.out.println("这是一个线程安全的饿汉式单例模式实例。");
}
}

2.继承与多态 (Inheritance and Polymorphism)

设计一个 Animal 抽象类,并创建 DogCat 类来展示继承和多态。=

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// 1. Animal 抽象类,定义通用行为
abstract class Animal {
// 抽象方法,子类必须实现
public abstract void eat();
}

// 2. Dog 类继承 Animal,并重写 eat() 方法
class Dog extends Animal {
@Override
public void eat() {
System.out.println("狗正在吃骨头。");
}
}

// 3. Cat 类继承 Animal,并重写 eat() 方法
class Cat extends Animal {
@Override
public void eat() {
System.out.println("猫正在吃鱼。");
}
}

// 4. 展示多态的类
class PolymorphismDemo {
public static void showPolymorphism() {
// 父类引用指向子类对象,这就是多态
Animal myDog = new Dog();
Animal myCat = new Cat();

// 调用相同的方法,但由于指向不同的子类实例,执行不同的行为
myDog.eat(); // 输出: 狗正在吃骨头。
myCat.eat(); // 输出: 猫正在吃鱼。
}
}

equals()hashCode()

编写 Student 类,并重写 equals()hashCode()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import java.util.Objects;

class Student {
private int id;
private String name;

public Student(int id, String name) {
this.id = id;
this.name = name;
}

// 重写 equals() 方法,根据 id 和 name 判断两个 Student 对象是否相等
@Override
public boolean equals(Object o) {
// 1. 检查是否为同一个对象的引用
if (this == o) return true;
// 2. 检查对象是否为 null 或类型是否匹配
if (o == null || getClass() != o.getClass()) return false;
// 3. 类型转换
Student student = (Student) o;
// 4. 比较关键字段 (id 和 name)
return id == student.id &&
Objects.equals(name, student.name);
}

// 重写 hashCode() 方法,为相等的对象生成相同的哈希码
@Override
public int hashCode() {
// 使用 Objects.hash() 方法生成哈希码,它会为多个字段生成一个组合哈希值
return Objects.hash(id, name);
}
}

/*
* 为什么需要两者一起重写?
* 1. 它们之间存在约定:如果两个对象通过 equals() 方法比较是相等的,那么它们的 hashCode() 方法返回的值也必须相等。
* 反之则不一定,不相等的对象可以有相同的哈希码(哈希冲突)。
* 2. 哈希表类(如 HashSet, HashMap)依赖于这个约定:当将对象存入哈希表时,
* 它会先调用 hashCode() 确定存储位置,再调用 equals() 来确认是否存在相同的对象。
* 3. 如果只重写 equals() 但不重写 hashCode(),可能导致两个逻辑上相等的对象被存储在不同的哈希位置,
* 从而无法正确查找和去重。例如,在 HashSet 中,即使两个 Student 对象 id 和 name 相同,
* 也会被认为是不同的对象而重复添加。
*/

接口与实现 (Interfaces and Implementation)

设计 Drawable 接口,并由 CircleRectangle 实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 1. 定义 Drawable 接口,包含一个抽象方法
interface Drawable {
void draw();
}

// 2. Circle 类实现 Drawable 接口
class Circle implements Drawable {
@Override
public void draw() {
System.out.println("正在画一个圆形。");
}
}

// 3. Rectangle 类实现 Drawable 接口
class Rectangle implements Drawable {
@Override
public void draw() {
System.out.println("正在画一个矩形。");
}
}

异常处理 (Exception Handling)

使用 try-catch-finally 结构处理 FileNotFoundException 并确保资源关闭。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

class ExceptionDemo {
public static void processFile(String filePath) {
FileInputStream fileInputStream = null; // 在 try 块外部声明,以便在 finally 块中访问
try {
// 尝试打开文件
fileInputStream = new FileInputStream(filePath);
System.out.println("文件已成功打开。");
// 假设这里进行文件读取操作
} catch (FileNotFoundException e) {
// 捕获文件未找到异常
System.err.println("错误:指定的文件不存在!路径:" + filePath);
e.printStackTrace(); // 打印异常堆栈信息
} finally {
// 无论是否发生异常,finally 块都会执行
if (fileInputStream != null) {
try {
// 确保文件流被关闭,防止资源泄露
fileInputStream.close();
System.out.println("文件流已在 finally 块中关闭。");
} catch (IOException e) {
System.err.println("关闭文件流时发生异常:" + e.getMessage());
}
}
}
}
}

try-with-resources

使用 try-with-resources 重写上一个题目,展示其简化优势。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

class TryWithResourcesDemo {
public static void processFileWithResources(String filePath) {
// try-with-resources 语句,自动管理实现了 AutoCloseable 接口的资源
try (FileInputStream fileInputStream = new FileInputStream(filePath)) {
System.out.println("文件已成功打开。");
// 假设这里进行文件读取操作
} catch (FileNotFoundException e) {
// 捕获文件未找到异常
System.err.println("错误:指定的文件不存在!路径:" + filePath);
e.printStackTrace();
} catch (IOException e) {
// 捕获文件关闭时可能发生的异常
System.err.println("关闭文件流时发生异常:" + e.getMessage());
}
// 不需要单独的 finally 块来关闭资源,JVM 会自动完成
System.out.println("文件流已在 try-with-resources 语句中自动关闭。");
}
}

/*
* try-with-resources 的优势:
* 1. 简化代码:不再需要显式的 finally 块来关闭资源,代码更简洁。
* 2. 避免资源泄露:无论 try 块是否正常完成或抛出异常,资源都会被自动关闭,
* 有效防止了因忘记关闭资源而导致的内存和文件句柄泄露。
* 3. 更好的异常处理:如果 try 块和资源关闭时都抛出异常,try-with-resources 会
* 将资源关闭时抛出的异常作为被抑制(suppressed)的异常,主异常保持不变,
* 便于调试。
*/

冒泡排序 (Bubble Sort)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class SortingAlgorithms {
public static void bubbleSort(int[] arr) {
if (arr == null || arr.length <= 1) {
return;
}

int n = arr.length;
// 外层循环控制比较轮数,共进行 n-1 轮
for (int i = 0; i < n - 1; i++) {
boolean swapped = false; // 优化:如果在某一轮没有发生交换,说明数组已经有序
// 内层循环负责每轮比较和交换,将最大元素“冒”到数组末尾
for (int j = 0; j < n - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
// 交换元素
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
swapped = true;
}
}
if (!swapped) {
break; // 如果没有发生交换,提前结束排序
}
}
}
}

快速排序 (Quick Sort)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public class SortingAlgorithms {
public static void quickSort(int[] arr) {
if (arr == null || arr.length <= 1) {
return;
}
quickSort(arr, 0, arr.length - 1);
}

private static void quickSort(int[] arr, int low, int high) {
if (low < high) {
// 找到基准元素的正确位置
int pivotIndex = partition(arr, low, high);
// 递归地对左子数组进行排序
quickSort(arr, low, pivotIndex - 1);
// 递归地对右子数组进行排序
quickSort(arr, pivotIndex + 1, high);
}
}

private static int partition(int[] arr, int low, int high) {
// 选择最右边的元素作为基准
int pivot = arr[high];
int i = (low - 1); // i 指向小于等于基准的元素的最后一个位置

for (int j = low; j < high; j++) {
// 如果当前元素小于等于基准
if (arr[j] <= pivot) {
i++;
// 交换 arr[i] 和 arr[j]
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
// 将基准元素放到正确的位置
int temp = arr[i + 1];
arr[i + 1] = arr[high];
arr[high] = temp;

return i + 1;
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class SearchAlgorithms {
public static int binarySearch(int[] arr, int target) {
if (arr == null || arr.length == 0) {
return -1;
}

int left = 0;
int right = arr.length - 1;

while (left <= right) {
int mid = left + (right - left) / 2; // 防止整数溢出

if (arr[mid] == target) {
return mid; // 找到目标,返回索引
} else if (arr[mid] < target) {
left = mid + 1; // 目标在右半部分,更新左边界
} else {
right = mid - 1; // 目标在左半部分,更新右边界
}
}
return -1; // 没有找到目标
}
}

链表反转 (Reverse Linked List)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class LinkedListProblems {
public static class ListNode {
int val;
ListNode next;
ListNode(int x) { val = x; }
}

public static ListNode reverseList(ListNode head) {
ListNode prev = null; // 前一个节点
ListNode current = head; // 当前节点

while (current != null) {
ListNode nextTemp = current.next; // 暂存下一个节点
current.next = prev; // 将当前节点的指针指向前一个节点
prev = current; // 前一个节点向前移动
current = nextTemp; // 当前节点向前移动
}
return prev; // prev 最终会是新的头节点
}
}

链表中间节点 (Middle of Linked List)

使用快慢指针,快指针每次走两步,慢指针每次走一步。当快指针到达链表末尾时,慢指针正好在中间。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class LinkedListProblems {
public static class ListNode {
int val;
ListNode next;
ListNode(int x) { val = x; }
}

public static ListNode findMiddleNode(ListNode head) {
if (head == null) {
return null;
}

ListNode slow = head; // 慢指针,每次走一步
ListNode fast = head; // 快指针,每次走两步

// 当快指针到达末尾时,慢指针正好在中间
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
}
return slow;
}
}

移除重复元素 (Remove Duplicates)

不使用 Set,通过双层循环或排序后遍历实现。这里使用排序后遍历的方法,因为它更高效。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import java.util.ArrayList;
import java.util.Collections;

public class ListProblems {
public static void removeDuplicates(ArrayList<String> list) {
if (list == null || list.size() <= 1) {
return;
}

Collections.sort(list); // 先排序,使得重复元素相邻

// 倒序遍历,移除重复元素
for (int i = list.size() - 2; i >= 0; i--) {
if (list.get(i).equals(list.get(i + 1))) {
list.remove(i + 1);
}
}
}
}

字符串反转 (Reverse String)

不使用 StringBuilderStringBufferreverse() 方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class StringProblems {
public static String reverseString(String str) {
if (str == null || str.isEmpty()) {
return str;
}

char[] charArray = str.toCharArray();
int left = 0;
int right = charArray.length - 1;

while (left < right) {
// 交换首尾字符
char temp = charArray[left];
charArray[left] = charArray[right];
charArray[right] = temp;
left++;
right--;
}
return new String(charArray);
}
}

回文字符串 (Palindrome String)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class StringProblems {
public static boolean isPalindrome(String str) {
if (str == null) {
return false;
}
// 将字符串转换为小写并移除非字母数字字符
String cleanedStr = str.toLowerCase().replaceAll("[^a-z0-9]", "");

int left = 0;
int right = cleanedStr.length() - 1;

while (left < right) {
if (cleanedStr.charAt(left) != cleanedStr.charAt(right)) {
return false;
}
left++;
right--;
}
return true;
}
}

计算阶乘 (Factorial Calculation)

递归方式

1
2
3
4
5
6
7
8
9
10
11
public class MathProblems {
public static long factorialRecursive(int n) {
if (n < 0) {
throw new IllegalArgumentException("Factorial is not defined for negative numbers.");
}
if (n == 0 || n == 1) {
return 1;
}
return n * factorialRecursive(n - 1);
}
}

非递归方式

1
2
3
4
5
6
7
8
9
10
11
12
public class MathProblems {
public static long factorialIterative(int n) {
if (n < 0) {
throw new IllegalArgumentException("Factorial is not defined for negative numbers.");
}
long result = 1;
for (int i = 2; i <= n; i++) {
result *= i;
}
return result;
}
}

斐波那契数列 (Fibonacci Sequence)

递归方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class MathProblems {
public static long fibonacciRecursive(int n) {
if (n < 0) {
throw new IllegalArgumentException("Invalid input for Fibonacci sequence.");
}
if (n == 0) {
return 0;
}
if (n == 1) {
return 1;
}
return fibonacciRecursive(n - 1) + fibonacciRecursive(n - 2);
}
}

非递归方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class MathProblems {
public static long fibonacciIterative(int n) {
if (n < 0) {
throw new IllegalArgumentException("Invalid input for Fibonacci sequence.");
}
if (n == 0) {
return 0;
}
if (n == 1) {
return 1;
}

long a = 0;
long b = 1;
for (int i = 2; i <= n; i++) {
long sum = a + b;
a = b;
b = sum;
}
return b;
}
}

字符串中字符计数 (Character Count)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import java.util.HashMap;
import java.util.Map;

public class StringProblems {
public static Map<Character, Integer> countCharacters(String str) {
Map<Character, Integer> charCountMap = new HashMap<>();
if (str == null || str.isEmpty()) {
return charCountMap;
}

for (char c : str.toCharArray()) {
charCountMap.put(c, charCountMap.getOrDefault(c, 0) + 1);
}
return charCountMap;
}
}

两数之和 (Two Sum)

使用 HashMap 将值和索引存储起来,实现一次遍历。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import java.util.HashMap;
import java.util.Map;

public class ArrayProblems {
public static int[] twoSum(int[] nums, int target) {
Map<Integer, Integer> numMap = new HashMap<>();
for (int i = 0; i < nums.length; i++) {
int complement = target - nums[i];
if (numMap.containsKey(complement)) {
return new int[]{numMap.get(complement), i};
}
numMap.put(nums[i], i);
}
return new int[]{}; // 未找到
}
}

数组中最大/小值 (Max/Min in Array)

一次遍历找到最大值和最小值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class ArrayProblems {
public static int[] findMaxMin(int[] arr) {
if (arr == null || arr.length == 0) {
throw new IllegalArgumentException("Array cannot be null or empty.");
}

int min = arr[0];
int max = arr[0];

for (int i = 1; i < arr.length; i++) {
if (arr[i] < min) {
min = arr[i];
}
if (arr[i] > max) {
max = arr[i];
}
}
return new int[]{max, min};
}
}

质数判断 (Prime Number)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class MathProblems {
public static boolean isPrime(int n) {
if (n <= 1) {
return false; // 1 和更小的数都不是质数
}
// 只需要检查到 n 的平方根
for (int i = 2; i * i <= n; i++) {
if (n % i == 0) {
return false;
}
}
return true;
}
}

集合交集 (List Intersection)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import java.util.ArrayList;
import java.util.List;

public class ListProblems {
public static <T> List<T> intersection(List<T> list1, List<T> list2) {
List<T> result = new ArrayList<>();
if (list1 == null || list2 == null) {
return result;
}

// 遍历第一个列表,检查元素是否在第二个列表中
for (T element : list1) {
if (list2.contains(element) && !result.contains(element)) {
result.add(element);
}
}
return result;
}
}

数组合并 (Merge Sorted Arrays)

将两个已排序的数组合并为一个新的已排序数组,使用双指针方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class ArrayProblems {
public static int[] mergeSortedArrays(int[] arr1, int[] arr2) {
int[] merged = new int[arr1.length + arr2.length];
int i = 0, j = 0, k = 0;

while (i < arr1.length && j < arr2.length) {
if (arr1[i] < arr2[j]) {
merged[k++] = arr1[i++];
} else {
merged[k++] = arr2[j++];
}
}

while (i < arr1.length) {
merged[k++] = arr1[i++];
}

while (j < arr2.length) {
merged[k++] = arr2[j++];
}

return merged;
}
}

判断回文数 (Palindrome Number)

不使用 String 转换,通过数学方法反转一半数字。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class MathProblems {
public static boolean isPalindromeNumber(int x) {
// 负数和以 0 结尾的非 0 数字都不是回文
if (x < 0 || (x % 10 == 0 && x != 0)) {
return false;
}

int reversedNumber = 0;
while (x > reversedNumber) {
int lastDigit = x % 10;
reversedNumber = reversedNumber * 10 + lastDigit;
x /= 10;
}

// 偶数位数字时,x 等于 reversedNumber
// 奇数位数字时,x 等于 reversedNumber 除以 10
return x == reversedNumber || x == reversedNumber / 10;
}
}

罗马数字转整数 (Roman to Integer)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import java.util.HashMap;
import java.util.Map;

public class StringProblems {
public static int romanToInt(String s) {
Map<Character, Integer> romanMap = new HashMap<>();
romanMap.put('I', 1);
romanMap.put('V', 5);
romanMap.put('X', 10);
romanMap.put('L', 50);
romanMap.put('C', 100);
romanMap.put('D', 500);
romanMap.put('M', 1000);

int result = 0;
for (int i = 0; i < s.length(); i++) {
int currentVal = romanMap.get(s.charAt(i));
// 如果当前字符的值小于下一个字符的值,则需要减去当前值
if (i < s.length() - 1 && currentVal < romanMap.get(s.charAt(i + 1))) {
result -= currentVal;
} else {
result += currentVal;
}
}
return result;
}
}

爬楼梯 (Climbing Stairs)

这是经典的动态规划问题。

动态规划方式 (非递归)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class MathProblems {
public static int climbStairs(int n) {
if (n <= 2) {
return n;
}

int[] dp = new int[n + 1];
dp[1] = 1; // 爬 1 级台阶,有 1 种方法
dp[2] = 2; // 爬 2 级台阶,有 2 种方法 (1+1, 2)

for (int i = 3; i <= n; i++) {
// 到达第 i 级台阶的方法数 = (从 i-1 级爬 1 级的方法数) + (从 i-2 级爬 2 级的方法数)
dp[i] = dp[i - 1] + dp[i - 2];
}

return dp[n];
}
}

好的,以下是基于 语言的线程基础与同步的实现,并附带详细注释。

线程创建

1. 继承 Thread

通过继承 java.lang.Thread 类并重写其 run() 方法来创建线程。

1
2
3
4
5
6
7
8
// 继承 Thread 类创建线程
class MyThread extends Thread {
@Override
public void run() {
// 线程执行的任务
System.out.println("使用继承 Thread 类的方式创建的线程正在运行。");
}
}

2. 实现 Runnable 接口

通过实现 java.lang.Runnable 接口并将其作为参数传递给 Thread 类的构造函数来创建线程。这种方式更灵活,推荐使用。

1
2
3
4
5
6
7
8
// 实现 Runnable 接口创建线程
class MyRunnable implements Runnable {
@Override
public void run() {
// 线程执行的任务
System.out.println("使用实现 Runnable 接口的方式创建的线程正在运行。");
}
}

sleep()yield()

sleep()yield() 都是线程调度的方法,但它们的作用和效果不同。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class SleepYieldDemo {
public static void main(String[] args) {
// 使用 sleep() 的线程
new Thread(() -> {
System.out.println("线程 A: 开始执行...");
try {
// sleep() 使线程暂停指定时间,进入 WAITING 状态,但不会释放锁
System.out.println("线程 A: 准备睡眠 2 秒...");
Thread.sleep(2000);
System.out.println("线程 A: 睡眠结束,继续执行。");
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();

// 使用 yield() 的线程
new Thread(() -> {
System.out.println("线程 B: 开始执行...");
for (int i = 0; i < 5; i++) {
System.out.println("线程 B: 正在执行 " + i);
// yield() 提示线程调度器让出 CPU 时间,但不保证一定生效
Thread.yield();
}
System.out.println("线程 B: 执行完毕。");
}).start();
}
}
  • sleep(): 使当前线程暂停执行指定的时间,进入 TIMED_WAITING 状态。它会释放 CPU 资源,但不释放锁
  • yield(): 提示线程调度器,当前线程愿意让出当前 CPU 时间片。线程会从 RUNNING 状态转换为 RUNNABLE 状态,与其他线程竞争 CPU,但不保证其他线程能立即获得执行。它主要用于优化线程调度,通常在多线程程序中不应依赖其行为来保证正确性。

join() 方法

join() 方法允许一个线程等待另一个线程执行完毕。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class JoinDemo {
public static void main(String[] args) throws InterruptedException {
Thread workerThread = new Thread(() -> {
try {
System.out.println("子线程: 正在执行...");
Thread.sleep(3000); // 模拟耗时操作
System.out.println("子线程: 执行完毕。");
} catch (InterruptedException e) {
e.printStackTrace();
}
});

workerThread.start();
System.out.println("主线程: 等待子线程执行完毕...");
// 调用 join() 方法,主线程进入等待状态,直到 workerThread 执行完毕
workerThread.join();
System.out.println("主线程: 子线程已执行完毕,主线程继续执行。");
}
}

非线程安全计数器

在多线程环境下,多个线程同时对共享资源进行读写操作,可能导致数据不一致。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class UnsafeCounter {
private int count = 0;

public void increment() {
count++; // 这一行代码并非原子操作,它包含三个步骤:读、加 1、写
}

public int getCount() {
return count;
}
}

class ThreadSafetyDemo {
public static void main(String[] args) throws InterruptedException {
UnsafeCounter counter = new UnsafeCounter();
Thread[] threads = new Thread[100];
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(() -> {
for (int j = 0; j < 10000; j++) {
counter.increment();
}
});
threads[i].start();
}

for (Thread thread : threads) {
thread.join();
}
// 最终结果不一定是 100 * 10000 = 1000000
System.out.println("非线程安全计数器的最终结果: " + counter.getCount());
}
}

synchronized 关键字

使用 synchronized 关键字可以保证同一时刻只有一个线程访问共享资源,从而解决线程安全问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class SynchronizedCounter {
private int count = 0;

// 使用 synchronized 关键字修饰方法,锁住整个对象实例
public synchronized void increment() {
count++;
}

public int getCount() {
return count;
}
}

class SynchronizedDemo {
public static void main(String[] args) throws InterruptedException {
SynchronizedCounter counter = new SynchronizedCounter();
Thread[] threads = new Thread[100];
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(() -> {
for (int j = 0; j < 10000; j++) {
counter.increment();
}
});
threads[i].start();
}

for (Thread thread : threads) {
thread.join();
}
// 最终结果为 1000000
System.out.println("synchronized 计数器的最终结果: " + counter.getCount());
}
}

synchronized 块与方法

  • synchronized 方法: 锁定的是当前对象实例 (this)。
  • synchronized 块: 提供了更细粒度的控制,可以指定锁定的对象。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
class SynchronizedBlockDemo {
private final Object lock1 = new Object();
private final Object lock2 = new Object();

// synchronized 方法,锁定当前对象实例
public synchronized void synchronizedMethod() {
System.out.println(Thread.currentThread().getName() + " 进入 synchronized 方法。");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " 离开 synchronized 方法。");
}

// synchronized 块,锁定指定的对象
public void synchronizedBlock1() {
System.out.println(Thread.currentThread().getName() + " 尝试获取 lock1...");
synchronized (lock1) {
System.out.println(Thread.currentThread().getName() + " 已获取 lock1。");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " 释放 lock1。");
}
}

public void synchronizedBlock2() {
System.out.println(Thread.currentThread().getName() + " 尝试获取 lock2...");
synchronized (lock2) {
System.out.println(Thread.currentThread().getName() + " 已获取 lock2。");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " 释放 lock2。");
}
}
}

/*
* 区别与适用场景:
* - synchronized 方法: 简单易用,但锁定范围大,可能导致性能问题。当需要同步整个方法时使用。
* - synchronized 块: 锁定范围小,可以只同步需要保护的代码段,提高并发性能。当只需要同步部分代码时使用。
* 同时,可以通过锁定不同的对象来避免不必要的阻塞,实现更高的并发度。
*/

volatile 关键字

volatile 关键字保证了变量在多线程间的可见性,但不保证原子性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class VolatileDemo {
// volatile 保证所有线程看到的是该变量的最新值
private static volatile boolean ready = false;

public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
System.out.println("线程 A: 正在等待标志位变为 true。");
while (!ready) {
// 空循环,等待 ready 变为 true
}
System.out.println("线程 A: 标志位已变为 true,循环结束。");
}).start();

Thread.sleep(1000); // 确保线程 A 先运行
System.out.println("主线程: 正在将标志位设置为 true。");
ready = true;
}
}

/*
* 为什么 volatile 不能保证原子性?
* - 原子性是指一个操作是不可中断的,要么全部执行,要么都不执行。
* - volatile 只能保证变量的读写操作是原子的,但像 `count++` 这样的复合操作(读、加、写)
* 依然不是原子的。多个线程可能同时读到旧值,导致最终结果不正确。
* - volatile 主要用于一写多读的场景,或者用于控制线程执行流程的标志位。
*/

AtomicInteger

AtomicInteger 是一个原子类,它使用 CAS (Compare-and-Swap) 机制来保证操作的原子性,从而实现线程安全。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import java.util.concurrent.atomic.AtomicInteger;

class AtomicCounter {
// 使用 AtomicInteger 替代 int
private AtomicInteger count = new AtomicInteger(0);

public void increment() {
count.incrementAndGet(); // 这是一个原子操作
}

public int getCount() {
return count.get();
}
}

class AtomicDemo {
public static void main(String[] args) throws InterruptedException {
AtomicCounter counter = new AtomicCounter();
Thread[] threads = new Thread[100];
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(() -> {
for (int j = 0; j < 10000; j++) {
counter.increment();
}
});
threads[i].start();
}

for (Thread thread : threads) {
thread.join();
}
// 最终结果为 1000000
System.out.println("AtomicInteger 计数器的最终结果: " + counter.getCount());
}
}

线程通信

使用 wait()notifyAll() 实现两个线程交替打印奇数和偶数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
class PrintNumbers {
private int count = 0;
private final Object lock = new Object();

public void printOdd() {
synchronized (lock) {
while (count < 10) {
while (count % 2 == 0) { // 如果是偶数,则等待
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + ": " + count++);
lock.notifyAll(); // 唤醒其他等待的线程
}
}
}

public void printEven() {
synchronized (lock) {
while (count < 10) {
while (count % 2 != 0) { // 如果是奇数,则等待
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + ": " + count++);
lock.notifyAll(); // 唤醒其他等待的线程
}
}
}

public static void main(String[] args) {
PrintNumbers pn = new PrintNumbers();
Thread oddThread = new Thread(pn::printOdd, "奇数线程");
Thread evenThread = new Thread(pn::printEven, "偶数线程");

oddThread.start();
evenThread.start();
}
}

好的,以下是 中高级并发编程的实现,并附带详细注释。

生产者-消费者模式 (使用 wait()notifyAll())

这是一个经典的线程协作问题。生产者生产数据,放入共享队列;消费者从队列中取出数据进行消费。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
import java.util.LinkedList;
import java.util.Queue;

class ProducerConsumerClassic {
// 共享队列,使用 LinkedList 实现
private final Queue<Integer> queue = new LinkedList<>();
// 队列最大容量
private final int MAX_SIZE = 5;
private final Object LOCK = new Object();

public void produce() throws InterruptedException {
int i = 0;
while (true) {
synchronized (LOCK) {
// 如果队列已满,生产者等待
while (queue.size() == MAX_SIZE) {
System.out.println("队列已满,生产者等待...");
LOCK.wait();
}
// 生产数据并放入队列
queue.offer(i);
System.out.println("生产者生产了: " + i);
i++;
// 唤醒所有等待的线程(包括消费者)
LOCK.notifyAll();
}
Thread.sleep(100); // 模拟生产时间
}
}

public void consume() throws InterruptedException {
while (true) {
synchronized (LOCK) {
// 如果队列为空,消费者等待
while (queue.isEmpty()) {
System.out.println("队列为空,消费者等待...");
LOCK.wait();
}
// 消费数据
int data = queue.poll();
System.out.println("消费者消费了: " + data);
// 唤醒所有等待的线程(包括生产者)
LOCK.notifyAll();
}
Thread.sleep(500); // 模拟消费时间
}
}
}

生产者-消费者模式 (使用 BlockingQueue)

java.util.concurrent.BlockingQueue 接口提供了线程安全的队列操作,简化了生产者-消费者模型的实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

class ProducerConsumerBlockingQueue {
// 使用 BlockingQueue,它内部已经处理了线程同步
private final BlockingQueue<Integer> queue = new LinkedBlockingQueue<>(5);

public void produce() throws InterruptedException {
int i = 0;
while (true) {
// put() 方法在队列满时会自动阻塞
queue.put(i);
System.out.println("生产者生产了: " + i);
i++;
Thread.sleep(100);
}
}

public void consume() throws InterruptedException {
while (true) {
// take() 方法在队列空时会自动阻塞
int data = queue.take();
System.out.println("消费者消费了: " + data);
Thread.sleep(500);
}
}
}

ReentrantLock

使用 ReentrantLock 实现线程安全计数器,并解释其与 synchronized 的区别。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import java.util.concurrent.locks.ReentrantLock;

class ReentrantLockCounter {
private int count = 0;
private final ReentrantLock lock = new ReentrantLock();

public void increment() {
lock.lock(); // 获取锁
try {
count++; // 在 try 块中执行需要同步的代码
} finally {
lock.unlock(); // 在 finally 块中释放锁,确保锁总是被释放
}
}

public int getCount() {
lock.lock();
try {
return count;
} finally {
lock.unlock();
}
}
}

/*
* ReentrantLock 与 synchronized 的区别:
* 1. 语法层面: `synchronized` 是 JVM 的内置关键字,而 `ReentrantLock` 是一个类。
* 2. 灵活性: `ReentrantLock` 提供了更灵活的锁定控制。例如,它支持公平锁(按请求顺序获取锁),
* 可以尝试非阻塞地获取锁(`tryLock()`),以及支持中断(`lockInterruptibly()`)。
* 3. 性能: 在早期版本中,`ReentrantLock` 通常性能更好。但随着 对 `synchronized` 优化(偏向锁、轻量级锁),
* 两者性能已非常接近。在简单场景下,`synchronized` 更简洁。
* 4. 协作: `ReentrantLock` 必须配合 `Condition` 接口才能实现线程间的等待/唤醒机制,而 `synchronized` 直接
* 使用 `Object` 的 `wait()` 和 `notify()`。
*/

ReentrantLockCondition

使用 ReentrantLockCondition 重新实现生产者-消费者模型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

class ProducerConsumerCondition {
private final Queue<Integer> queue = new LinkedList<>();
private final int MAX_SIZE = 5;
private final ReentrantLock lock = new ReentrantLock();
// 创建两个 Condition 实例,分别用于生产者和消费者
private final Condition producerCondition = lock.newCondition();
private final Condition consumerCondition = lock.newCondition();

public void produce() throws InterruptedException {
int i = 0;
while (true) {
lock.lock();
try {
while (queue.size() == MAX_SIZE) {
System.out.println("队列已满,生产者等待...");
producerCondition.await(); // 生产者等待
}
queue.offer(i);
System.out.println("生产者生产了: " + i);
i++;
consumerCondition.signalAll(); // 唤醒所有消费者
} finally {
lock.unlock();
}
Thread.sleep(100);
}
}

public void consume() throws InterruptedException {
while (true) {
lock.lock();
try {
while (queue.isEmpty()) {
System.out.println("队列为空,消费者等待...");
consumerCondition.await(); // 消费者等待
}
int data = queue.poll();
System.out.println("消费者消费了: " + data);
producerCondition.signalAll(); // 唤醒所有生产者
} finally {
lock.unlock();
}
Thread.sleep(500);
}
}
}

ExecutorService

创建一个固定大小的线程池,并向其提交多个任务。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

class ExecutorServiceDemo {
public static void main(String[] args) {
// 创建一个固定大小为 3 的线程池
ExecutorService executorService = Executors.newFixedThreadPool(3);

for (int i = 0; i < 10; i++) {
final int taskId = i;
executorService.execute(() -> {
System.out.println("任务 " + taskId + " 正在由线程 " + Thread.currentThread().getName() + " 执行。");
try {
Thread.sleep(1000); // 模拟任务执行时间
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
}
// 关闭线程池,不再接收新任务,已提交的任务会继续执行
executorService.shutdown();
}
}

CallableFuture

使用 Callable 提交任务,并使用 Future 获取返回值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import java.util.concurrent.*;

class CallableFutureDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService executorService = Executors.newSingleThreadExecutor();

// 创建一个 Callable 任务,它会返回一个字符串
Callable<String> task = () -> {
System.out.println("任务开始执行...");
Thread.sleep(2000); // 模拟耗时操作
return "任务执行完毕,返回结果!";
};

// 提交任务并获得 Future 对象
Future<String> future = executorService.submit(task);

System.out.println("主线程: 任务已提交,继续执行其他操作...");
// get() 方法会阻塞,直到任务完成并返回结果
String result = future.get();
System.out.println("主线程: 获得任务结果 -> " + result);

executorService.shutdown();
}
}

CountDownLatch

CountDownLatch 允许一个或多个线程等待直到在其他线程中执行的一组操作完成。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
int workerCount = 5;
// 计数器,当计数器减到 0 时,主线程会被唤醒
CountDownLatch latch = new CountDownLatch(workerCount);
ExecutorService executor = Executors.newFixedThreadPool(workerCount);

for (int i = 0; i < workerCount; i++) {
final int workerId = i;
executor.execute(() -> {
System.out.println("工作线程 " + workerId + " 开始执行任务...");
try {
Thread.sleep((long) (Math.random() * 2000)); // 模拟任务执行
System.out.println("工作线程 " + workerId + " 任务完成。");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
latch.countDown(); // 任务完成后,计数器减 1
}
});
}

System.out.println("主线程: 等待所有工作线程完成...");
latch.await(); // 阻塞主线程,直到计数器为 0
System.out.println("主线程: 所有工作线程已完成,继续执行下一步。");

executor.shutdown();
}
}

CyclicBarrier

CyclicBarrier 允许一组线程相互等待,直到所有线程都到达一个共同的屏障点。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

class CyclicBarrierDemo {
public static void main(String[] args) {
int partySize = 3;
// 当 3 个线程都到达屏障时,执行一个屏障动作
CyclicBarrier barrier = new CyclicBarrier(partySize, () -> {
System.out.println("\n所有线程已到达屏障点!继续执行下一阶段。\n");
});
ExecutorService executor = Executors.newFixedThreadPool(partySize);

for (int i = 0; i < partySize; i++) {
final int threadId = i;
executor.execute(() -> {
try {
System.out.println("线程 " + threadId + " 正在执行第一阶段任务...");
Thread.sleep((long) (Math.random() * 1000));
System.out.println("线程 " + threadId + " 第一阶段任务完成,到达屏障。");
barrier.await(); // 线程在此处等待

System.out.println("线程 " + threadId + " 正在执行第二阶段任务...");
} catch (InterruptedException | BrokenBarrierException e) {
Thread.currentThread().interrupt();
}
});
}
executor.shutdown();
}
}

Semaphore

Semaphore(信号量)用来控制对资源的并发访问数量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import java.util.concurrent.Semaphore;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

class SemaphoreDemo {
// 允许 3 个线程同时访问
private final Semaphore semaphore = new Semaphore(3);

public void accessResource(int threadId) {
try {
semaphore.acquire(); // 获取许可
System.out.println("线程 " + threadId + " 正在访问资源...");
Thread.sleep(2000); // 模拟资源访问时间
System.out.println("线程 " + threadId + " 访问资源完毕。");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
semaphore.release(); // 释放许可
}
}

public static void main(String[] args) {
SemaphoreDemo demo = new SemaphoreDemo();
ExecutorService executor = Executors.newFixedThreadPool(10);

for (int i = 0; i < 10; i++) {
final int threadId = i;
executor.execute(() -> demo.accessResource(threadId));
}

executor.shutdown();
}
}

线程死锁

1. 死锁演示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
class DeadlockDemo {
private static final Object lock1 = new Object();
private static final Object lock2 = new Object();

public void threadA() {
synchronized (lock1) {
System.out.println("线程A: 已获得 lock1,尝试获取 lock2...");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock2) {
System.out.println("线程A: 已获得 lock2。");
}
}
}

public void threadB() {
synchronized (lock2) {
System.out.println("线程B: 已获得 lock2,尝试获取 lock1...");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock1) {
System.out.println("线程B: 已获得 lock1。");
}
}
}

public static void main(String[] args) {
DeadlockDemo demo = new DeadlockDemo();
new Thread(demo::threadA, "Thread-A").start();
new Thread(demo::threadB, "Thread-B").start();
}
}

/*
* 死锁产生条件:
* 1. 互斥条件: 资源是独占的,一个线程在使用时,其他线程无法使用。
* 2. 请求与保持条件: 线程已经持有至少一个资源,但又请求其他资源,同时又保持对已有资源的占有。
* 3. 不可剥夺条件: 资源只能在持有它的线程完成任务后由它自己释放。
* 4. 循环等待条件: 存在一个线程资源的循环链,每个线程都在等待下一个线程所持有的资源。
*/

死锁预防

通过打破死锁的四个必要条件之一来预防死锁。这里通过资源有序分配来打破循环等待

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class DeadlockPrevention {
private static final Object lock1 = new Object();
private static final Object lock2 = new Object();

public void threadA() {
// 线程 A 按顺序先获取 lock1,再获取 lock2
synchronized (lock1) {
System.out.println("线程A: 已获得 lock1,尝试获取 lock2...");
synchronized (lock2) {
System.out.println("线程A: 已获得 lock2。");
}
}
}

public void threadB() {
// 线程 B 也按顺序先获取 lock1,再获取 lock2
synchronized (lock1) {
System.out.println("线程B: 已获得 lock1,尝试获取 lock2...");
synchronized (lock2) {
System.out.println("线程B: 已获得 lock2。");
}
}
}

public static void main(String[] args) {
DeadlockPrevention demo = new DeadlockPrevention();
new Thread(demo::threadA, "Thread-A").start();
new Thread(demo::threadB, "Thread-B").start();
// 两个线程都按相同的顺序获取锁,不会发生死锁
}
}

ThreadLocal

ThreadLocal 为每个线程提供了一个独立的变量副本,实现了线程间的数据隔离。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class ThreadLocalDemo {
private static final ThreadLocal<String> threadLocal = new ThreadLocal<>();

public void setAndPrint(String value) {
// 设置线程本地变量
threadLocal.set(value);
try {
Thread.sleep(1000); // 模拟任务
// 获取并打印当前线程的变量值
System.out.println(Thread.currentThread().getName() + " 的变量值: " + threadLocal.get());
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 最佳实践:使用完后移除,防止内存泄漏
threadLocal.remove();
}
}

public static void main(String[] args) {
ThreadLocalDemo demo = new ThreadLocalDemo();
new Thread(() -> demo.setAndPrint("线程 A 的数据"), "Thread-A").start();
new Thread(() -> demo.setAndPrint("线程 B 的数据"), "Thread-B").start();
}
}

/*
* 原理和作用:
* 原理: ThreadLocal 内部有一个 `ThreadLocalMap`,每个线程都有一个独立的 `ThreadLocalMap`。
* 当我们调用 `set()` 方法时,实际上是将值存储到了当前线程的 `ThreadLocalMap` 中,
* 键为 `ThreadLocal` 实例本身。
* 作用:
* 1. 数据隔离: 解决了多线程访问共享变量的线程安全问题,但其本质不是同步,而是通过“以空间换时间”的方式,
* 为每个线程提供独立副本,避免了竞争。
* 2. 传递参数: 在整个方法调用链中,无需层层传递参数,可以方便地在任何地方获取当前线程的上下文信息。
*/

线程安全单例 (静态内部类)

静态内部类方式是实现线程安全的懒汉式单例的最佳实践之一。它利用了 JVM 类加载机制的线程安全特性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class SingletonThreadSafe {
// 私有化构造器
private SingletonThreadSafe() {}

// 静态内部类,它只在 SingletonThreadSafe 被首次调用时才会被加载
private static class SingletonHolder {
// 静态成员变量,在类加载时初始化
private static final SingletonThreadSafe INSTANCE = new SingletonThreadSafe();
}

// 提供全局访问点
public static SingletonThreadSafe getInstance() {
return SingletonHolder.INSTANCE;
}
}

/*
* 优点:
* 1. 线程安全: 类的加载是线程安全的,因此 INSTANCE 的初始化是原子的。
* 2. 懒加载: 只有在调用 getInstance() 方法时,SingletonHolder 类才会被加载,
* 从而实现懒加载。
*/

ReadWriteLock

ReadWriteLock 适用于读多写少的场景,它允许多个线程同时进行读操作,但写操作必须是互斥的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

class ReadWriteLockDemo {
private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
private final Object sharedData = "初始数据";

public String readData() {
rwLock.readLock().lock(); // 获取读锁
try {
System.out.println(Thread.currentThread().getName() + " 正在读取数据...");
Thread.sleep(1000);
return (String) sharedData;
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return null;
} finally {
rwLock.readLock().unlock(); // 释放读锁
}
}

public void writeData(String newData) {
rwLock.writeLock().lock(); // 获取写锁
try {
System.out.println(Thread.currentThread().getName() + " 正在写入数据...");
Thread.sleep(2000);
// sharedData = newData; // 实际更新数据
System.out.println(Thread.currentThread().getName() + " 写入完成。");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
rwLock.writeLock().unlock(); // 释放写锁
}
}
}

线程池关闭

ExecutorService 的两种关闭方式:shutdown()shutdownNow()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

class ShutdownDemo {
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(2);

// 提交 5 个任务
for (int i = 0; i < 5; i++) {
final int taskId = i;
executor.execute(() -> {
try {
System.out.println("任务 " + taskId + " 正在执行...");
Thread.sleep(2000);
} catch (InterruptedException e) {
System.out.println("任务 " + taskId + " 被中断。");
}
});
}

// shutdown() vs shutdownNow()
// executor.shutdown(); // 优雅关闭
executor.shutdownNow(); // 暴力关闭

try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程池已关闭。");
}
}

/*
* 区别:
* - shutdown(): 优雅关闭。不再接受新的任务,但会等待已提交的任务(包括正在执行和在队列中的)全部执行完毕。
* 执行后,isShutdown() 返回 true,isTerminated() 返回 false,直到所有任务完成。
* - shutdownNow(): 暴力关闭。立即停止所有正在执行的任务,并返回在队列中等待执行的任务列表。
* 它会向所有线程发送 interrupt() 中断信号。
* 执行后,isShutdown() 返回 true,isTerminated() 立即返回 true。
*/

中断机制

一个线程通过响应 interrupt() 调用来正确停止自身。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class InterruptedThreadDemo {
public static void main(String[] args) {
Thread worker = new Thread(() -> {
while (!Thread.currentThread().isInterrupted()) { // 检查中断标志
try {
System.out.println("线程正在执行...");
// sleep()、wait() 等方法会抛出 InterruptedException 并清除中断标志
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println("线程被中断!");
// 重新设置中断标志,以便外层循环能正确退出
Thread.currentThread().interrupt();
// 或者直接 return 退出
return;
}
}
System.out.println("线程已退出。");
});

worker.start();
try {
Thread.sleep(3500);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 发送中断信号
worker.interrupt();
}
}

子数组求和 (Subarray Sum)

使用滑动窗口前缀和 + 哈希表两种方式解决。这里演示前缀和 + 哈希表,它能处理负数情况且时间复杂度更优。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import java.util.HashMap;
import java.util.Map;

class ArrayProblems {
/**
* 找出和等于目标值的连续子数组。
* 使用前缀和与哈希表的方式,时间复杂度为 O(n)。
*
* @param nums 整数数组
* @param target 目标值
* @return 如果找到,返回子数组的起始和结束索引;否则返回 null
*/
public static int[] findSubarraySum(int[] nums, int target) {
if (nums == null || nums.length == 0) {
return null;
}

// key: 前缀和, value: 出现该前缀和的索引
Map<Integer, Integer> prefixSumMap = new HashMap<>();
prefixSumMap.put(0, -1); // 初始化,处理从数组开头开始的子数组

int currentSum = 0;
for (int i = 0; i < nums.length; i++) {
currentSum += nums[i];

// 检查是否存在 (currentSum - target) 这样的前缀和
if (prefixSumMap.containsKey(currentSum - target)) {
int start = prefixSumMap.get(currentSum - target) + 1;
int end = i;
return new int[]{start, end};
}

prefixSumMap.put(currentSum, i);
}

return null;
}
}

字符串去重 (Remove Duplicates from String)

使用 LinkedHashSet 保持相对顺序或手动遍历。这里手动遍历实现,避免使用额外数据结构。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class StringProblems {
/**
* 对一个字符串进行去重,并保持原有字符的相对顺序。
*
* @param str 待去重的字符串
* @return 去重后的字符串
*/
public static String removeDuplicates(String str) {
if (str == null || str.length() <= 1) {
return str;
}

StringBuilder sb = new StringBuilder();
boolean[] charSet = new boolean[256]; // 假设为 ASCII 字符集

for (int i = 0; i < str.length(); i++) {
char c = str.charAt(i);
if (!charSet[c]) {
sb.append(c);
charSet[c] = true;
}
}
return sb.toString();
}
}

最长不重复子串 (Longest Substring Without Repeating Characters)

使用滑动窗口哈希表数组来高效解决。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import java.util.HashMap;
import java.util.Map;

class StringProblems {
/**
* 找出最长不重复子串的长度。
* 使用滑动窗口(双指针)和哈希表
*
* @param s 输入字符串
* @return 最长不重复子串的长度
*/
public static int lengthOfLongestSubstring(String s) {
if (s == null || s.length() == 0) {
return 0;
}

// key: 字符, value: 字符的最新索引
Map<Character, Integer> charIndexMap = new HashMap<>();
int maxLength = 0;
int left = 0; // 滑动窗口的左边界

for (int right = 0; right < s.length(); right++) {
char currentChar = s.charAt(right);

// 如果当前字符已在窗口内,更新左边界
if (charIndexMap.containsKey(currentChar)) {
left = Math.max(left, charIndexMap.get(currentChar) + 1);
}

charIndexMap.put(currentChar, right);
maxLength = Math.max(maxLength, right - left + 1);
}
return maxLength;
}
}

字符串转整数 (String to Integer - atoi)

实现 atoi 函数,需要考虑各种边界情况。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
class StringProblems {
/**
* 实现 atoi 函数,将字符串转换为整数。
*
* @param s 字符串
* @return 转换后的整数
*/
public static int myAtoi(String s) {
if (s == null || s.isEmpty()) {
return 0;
}

s = s.trim(); // 1. 去掉前导空格

if (s.isEmpty()) {
return 0;
}

int index = 0;
int sign = 1; // 2. 处理正负号
if (s.charAt(index) == '-') {
sign = -1;
index++;
} else if (s.charAt(index) == '+') {
index++;
}

long result = 0;
while (index < s.length()) {
char c = s.charAt(index);
// 3. 检查是否为数字
if (!Character.isDigit(c)) {
break;
}

int digit = c - '0';
result = result * 10 + digit;

// 4. 处理溢出
if (sign == 1 && result > Integer.MAX_VALUE) {
return Integer.MAX_VALUE;
}
if (sign == -1 && -result < Integer.MIN_VALUE) {
return Integer.MIN_VALUE;
}
index++;
}

return (int) (result * sign);
}
}

判断子串 (Substring Check)

使用 String.indexOf() 是最直接的方式。如果不能使用内置方法,可以通过双指针或 KMP 算法实现。这里提供双指针的实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class StringProblems {
/**
* 判断一个字符串是否是另一个字符串的子串。
*
* @param mainStr 主字符串
* @param subStr 子字符串
* @return 如果 subStr 是 mainStr 的子串,返回 true;否则返回 false
*/
public static boolean isSubstring(String mainStr, String subStr) {
if (mainStr == null || subStr == null || mainStr.length() < subStr.length()) {
return false;
}
if (subStr.isEmpty()) {
return true;
}

for (int i = 0; i <= mainStr.length() - subStr.length(); i++) {
boolean match = true;
for (int j = 0; j < subStr.length(); j++) {
if (mainStr.charAt(i + j) != subStr.charAt(j)) {
match = false;
break;
}
}
if (match) {
return true;
}
}
return false;
}
}

数组旋转 (Array Rotation)

向右旋转 k 步,可以通过三次反转或使用额外数组实现。这里使用空间复杂度为 O(1) 的三次反转方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class ArrayProblems {
/**
* 将一个整数数组向右旋转 k 步。
* 使用三次反转的方法
*
* @param nums 整数数组
* @param k 旋转步数
*/
public static void rotate(int[] nums, int k) {
if (nums == null || nums.length <= 1) {
return;
}

k %= nums.length; // 处理 k 大于数组长度的情况

// 1. 反转整个数组
reverse(nums, 0, nums.length - 1);
// 2. 反转前 k 个元素
reverse(nums, 0, k - 1);
// 3. 反转剩下的元素
reverse(nums, k, nums.length - 1);
}

private static void reverse(int[] nums, int start, int end) {
while (start < end) {
int temp = nums[start];
nums[start] = nums[end];
nums[end] = temp;
start++;
end--;
}
}
}

交错字符串 (Interleaving String)

判断 s3 是否由 s1s2 交错而成,可以使用动态规划解决。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class StringProblems {
/**
* 判断两个字符串交错形成的字符串是否等于第三个字符串。
*
* @param s1 字符串1
* @param s2 字符串2
* @param s3 字符串3
* @return 如果是交错字符串,返回 true;否则返回 false
*/
public static boolean isInterleave(String s1, String s2, String s3) {
if (s1.length() + s2.length() != s3.length()) {
return false;
}

// dp[i][j] 表示 s1 的前 i 个字符和 s2 的前 j 个字符能否交错组成 s3 的前 i+j 个字符
boolean[][] dp = new boolean[s1.length() + 1][s2.length() + 1];
dp[0][0] = true;

// 初始化第一行 (s1 不取字符)
for (int j = 1; j <= s2.length(); j++) {
dp[0][j] = dp[0][j - 1] && s2.charAt(j - 1) == s3.charAt(j - 1);
}

// 初始化第一列 (s2 不取字符)
for (int i = 1; i <= s1.length(); i++) {
dp[i][0] = dp[i - 1][0] && s1.charAt(i - 1) == s3.charAt(i - 1);
}

// 填充 DP 表
for (int i = 1; i <= s1.length(); i++) {
for (int j = 1; j <= s2.length(); j++) {
dp[i][j] = (dp[i - 1][j] && s1.charAt(i - 1) == s3.charAt(i + j - 1)) ||
(dp[i][j - 1] && s2.charAt(j - 1) == s3.charAt(i + j - 1));
}
}
return dp[s1.length()][s2.length()];
}
}

最长公共前缀 (Longest Common Prefix)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class StringProblems {
/**
* 找出字符串数组中的最长公共前缀。
*
* @param strs 字符串数组
* @return 最长公共前缀
*/
public static String longestCommonPrefix(String[] strs) {
if (strs == null || strs.length == 0) {
return "";
}

String prefix = strs[0];
for (int i = 1; i < strs.length; i++) {
// 循环比较,直到找到子串
while (strs[i].indexOf(prefix) != 0) {
// 每次缩短 prefix
prefix = prefix.substring(0, prefix.length() - 1);
if (prefix.isEmpty()) {
return "";
}
}
}
return prefix;
}
}

字符串压缩 (String Compression)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class StringProblems {
/**
* 实现基本的字符串压缩,例如 aabcccccaaa 压缩为 a2b1c5a3。
* 如果压缩后的字符串没有变短,则返回原字符串。
*
* @param str 待压缩的字符串
* @return 压缩后的字符串
*/
public static String compressString(String str) {
if (str == null || str.isEmpty()) {
return "";
}

StringBuilder compressed = new StringBuilder();
int count = 1;

for (int i = 1; i <= str.length(); i++) {
if (i < str.length() && str.charAt(i) == str.charAt(i - 1)) {
count++;
} else {
compressed.append(str.charAt(i - 1)).append(count);
count = 1;
}
}

if (compressed.length() >= str.length()) {
return str; // 如果压缩后没有变短,返回原字符串
}
return compressed.toString();
}
}

寻找重复数 (Find the Duplicate Number)

给定一个包含 n+1 个整数的数组,数字都在 1n 的范围内。找出这个重复的数字。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class ArrayProblems {
/**
* 使用**快慢指针**(类似于检测链表环)来寻找重复数。
* 假设数组是一个链表,索引 `i` 指向 `nums[i]`
*
* @param nums 包含 n+1 个整数的数组
* @return 重复的数字
*/
public static int findDuplicate(int[] nums) {
// 1. 寻找环的入口
int slow = nums[0];
int fast = nums[nums[0]];
while (slow != fast) {
slow = nums[slow];
fast = nums[nums[fast]];
}

// 2. 找到环后,从头开始,快慢指针以相同速度前进,相遇点即为环的入口(重复数)
int p1 = nums[0];
int p2 = slow;
while (p1 != p2) {
p1 = nums[p1];
p2 = nums[p2];
}
return p1;
}
}

链表、栈与队列

合并两个有序链表

使用双指针方法,创建一个新的链表,遍历两个输入链表,比较节点值并依次添加到新链表。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class ListNode {
int val;
ListNode next;
ListNode(int x) { val = x; }
}

public class LinkedListProblems {
public static ListNode mergeTwoLists(ListNode l1, ListNode l2) {
// 创建一个哑节点,简化操作
ListNode dummy = new ListNode(0);
ListNode current = dummy;

while (l1 != null && l2 != null) {
if (l1.val <= l2.val) {
current.next = l1;
l1 = l1.next;
} else {
current.next = l2;
l2 = l2.next;
}
current = current.next;
}

// 将剩余的节点添加到新链表末尾
if (l1 != null) {
current.next = l1;
}
if (l2 != null) {
current.next = l2;
}

return dummy.next;
}
}

链表倒数第 k 个节点

使用快慢指针,只遍历一次。快指针先走 k 步,然后快慢指针同时前进,当快指针到达末尾时,慢指针正好在倒数第 k 个位置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class LinkedListProblems {
public static ListNode findKthFromEnd(ListNode head, int k) {
if (head == null || k <= 0) {
return null;
}

ListNode fast = head;
ListNode slow = head;

// 快指针先走 k 步
for (int i = 0; i < k; i++) {
if (fast == null) {
return null; // 链表长度小于 k
}
fast = fast.next;
}

// 快慢指针同时前进,直到快指针到达末尾
while (fast != null) {
fast = fast.next;
slow = slow.next;
}

return slow;
}
}

删除重复节点

给定一个已排序的链表,删除所有重复的节点,使得每个元素只出现一次。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class LinkedListProblems {
public static ListNode deleteDuplicates(ListNode head) {
if (head == null) {
return null;
}

ListNode current = head;
while (current != null && current.next != null) {
if (current.val == current.next.val) {
current.next = current.next.next; // 删除下一个重复节点
} else {
current = current.next; // 前进到下一个不重复的节点
}
}
return head;
}
}

判断链表是否有环

使用快慢指针。如果链表有环,快指针最终会追上慢指针。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class LinkedListProblems {
public static boolean hasCycle(ListNode head) {
if (head == null || head.next == null) {
return false;
}

ListNode slow = head;
ListNode fast = head.next;

while (slow != fast) {
if (fast == null || fast.next == null) {
return false; // 已经到达末尾,没有环
}
slow = slow.next;
fast = fast.next.next;
}
return true;
}
}

链表求和

两个非负整数由链表表示,每个节点包含一个数字。计算它们的和。这里假设链表按逆序存储数字(个位在前)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class LinkedListProblems {
public static ListNode addTwoNumbers(ListNode l1, ListNode l2) {
ListNode dummy = new ListNode(0);
ListNode current = dummy;
int carry = 0; // 进位

while (l1 != null || l2 != null || carry > 0) {
int sum = carry;
if (l1 != null) {
sum += l1.val;
l1 = l1.next;
}
if (l2 != null) {
sum += l2.val;
l2 = l2.next;
}

carry = sum / 10;
current.next = new ListNode(sum % 10);
current = current.next;
}

return dummy.next;
}
}

栈实现队列

使用两个栈,一个用于入队 (inStack),一个用于出队 (outStack)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import java.util.Stack;

class MyQueue {
private Stack<Integer> inStack;
private Stack<Integer> outStack;

public MyQueue() {
inStack = new Stack<>();
outStack = new Stack<>();
}

public void push(int x) {
inStack.push(x);
}

public int pop() {
if (outStack.isEmpty()) {
transfer();
}
return outStack.pop();
}

public int peek() {
if (outStack.isEmpty()) {
transfer();
}
return outStack.peek();
}

private void transfer() {
while (!inStack.isEmpty()) {
outStack.push(inStack.pop());
}
}

public boolean empty() {
return inStack.isEmpty() && outStack.isEmpty();
}
}

队列实现栈

使用两个队列,一个用于入栈 (q1),一个用于辅助 (q2)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import java.util.LinkedList;
import java.util.Queue;

class MyStack {
private Queue<Integer> q1;
private Queue<Integer> q2;

public MyStack() {
q1 = new LinkedList<>();
q2 = new LinkedList<>();
}

public void push(int x) {
q1.offer(x);
}

public int pop() {
while (q1.size() > 1) {
q2.offer(q1.poll());
}
int result = q1.poll();
Queue<Integer> temp = q1;
q1 = q2;
q2 = temp;
return result;
}

public int top() {
int result = pop();
q1.offer(result);
return result;
}

public boolean empty() {
return q1.isEmpty();
}
}

有效括号

使用一个来检查括号是否匹配。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import java.util.Stack;

public class StackProblems {
public static boolean isValid(String s) {
Stack<Character> stack = new Stack<>();
for (char c : s.toCharArray()) {
if (c == '(' || c == '[' || c == '{') {
stack.push(c);
} else {
if (stack.isEmpty()) {
return false;
}
char top = stack.pop();
if ((c == ')' && top != '(') ||
(c == ']' && top != '[') ||
(c == '}' && top != '{')) {
return false;
}
}
}
return stack.isEmpty();
}
}

树与递归

定义一个简单的二叉树节点类:

1
2
3
4
5
6
public class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode(int x) { val = x; }
}

二叉树前序遍历 (Preorder Traversal)

递归方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import java.util.ArrayList;
import java.util.List;

public class TreeTraversal {
public static List<Integer> preorderTraversalRecursive(TreeNode root) {
List<Integer> result = new ArrayList<>();
preorder(root, result);
return result;
}

private static void preorder(TreeNode node, List<Integer> list) {
if (node == null) {
return;
}
list.add(node.val);
preorder(node.left, list);
preorder(node.right, list);
}
}

非递归方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;

public class TreeTraversal {
public static List<Integer> preorderTraversalIterative(TreeNode root) {
List<Integer> result = new ArrayList<>();
if (root == null) {
return result;
}
Stack<TreeNode> stack = new Stack<>();
stack.push(root);

while (!stack.isEmpty()) {
TreeNode node = stack.pop();
result.add(node.val);
// 先压入右子节点,再压入左子节点,保证弹出时先处理左子节点
if (node.right != null) {
stack.push(node.right);
}
if (node.left != null) {
stack.push(node.left);
}
}
return result;
}
}

二叉树中序遍历 (Inorder Traversal)

递归方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import java.util.ArrayList;
import java.util.List;

public class TreeTraversal {
public static List<Integer> inorderTraversalRecursive(TreeNode root) {
List<Integer> result = new ArrayList<>();
inorder(root, result);
return result;
}

private static void inorder(TreeNode node, List<Integer> list) {
if (node == null) {
return;
}
inorder(node.left, list);
list.add(node.val);
inorder(node.right, list);
}
}

非递归方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;

public class TreeTraversal {
public static List<Integer> inorderTraversalIterative(TreeNode root) {
List<Integer> result = new ArrayList<>();
Stack<TreeNode> stack = new Stack<>();
TreeNode current = root;

while (current != null || !stack.isEmpty()) {
// 将所有左子节点压入栈
while (current != null) {
stack.push(current);
current = current.left;
}
// 弹出栈顶节点,并处理其右子树
current = stack.pop();
result.add(current.val);
current = current.right;
}
return result;
}
}

二叉树后序遍历 (Postorder Traversal)

递归方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import java.util.ArrayList;
import java.util.List;

public class TreeTraversal {
public static List<Integer> postorderTraversalRecursive(TreeNode root) {
List<Integer> result = new ArrayList<>();
postorder(root, result);
return result;
}

private static void postorder(TreeNode node, List<Integer> list) {
if (node == null) {
return;
}
postorder(node.left, list);
postorder(node.right, list);
list.add(node.val);
}
}

非递归方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Stack;

public class TreeTraversal {
public static List<Integer> postorderTraversalIterative(TreeNode root) {
List<Integer> result = new ArrayList<>();
if (root == null) {
return result;
}
Stack<TreeNode> stack = new Stack<>();
stack.push(root);

while (!stack.isEmpty()) {
TreeNode node = stack.pop();
result.add(node.val); // 倒序插入
if (node.left != null) {
stack.push(node.left);
}
if (node.right != null) {
stack.push(node.right);
}
}
Collections.reverse(result); // 将结果反转
return result;
}
}

二叉树层序遍历 (Level Order Traversal)

使用队列实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;

public class TreeTraversal {
public static List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> result = new ArrayList<>();
if (root == null) {
return result;
}

Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);

while (!queue.isEmpty()) {
int levelSize = queue.size();
List<Integer> currentLevel = new ArrayList<>();
for (int i = 0; i < levelSize; i++) {
TreeNode node = queue.poll();
currentLevel.add(node.val);

if (node.left != null) {
queue.offer(node.left);
}
if (node.right != null) {
queue.offer(node.right);
}
}
result.add(currentLevel);
}
return result;
}
}

对称二叉树 (Symmetric Tree)

使用递归,检查根节点的左右子树是否是镜像对称的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class TreeProblems {
public static boolean isSymmetric(TreeNode root) {
if (root == null) {
return true;
}
return isMirror(root.left, root.right);
}

private static boolean isMirror(TreeNode t1, TreeNode t2) {
if (t1 == null && t2 == null) {
return true;
}
if (t1 == null || t2 == null) {
return false;
}
return (t1.val == t2.val) && isMirror(t1.right, t2.left) && isMirror(t1.left, t2.right);
}
}

最大深度 (Maximum Depth of Binary Tree)

使用递归(深度优先搜索)。

1
2
3
4
5
6
7
8
9
10
public class TreeProblems {
public static int maxDepth(TreeNode root) {
if (root == null) {
return 0;
}
int leftDepth = maxDepth(root.left);
int rightDepth = maxDepth(root.right);
return Math.max(leftDepth, rightDepth) + 1;
}
}

验证二叉搜索树 (Validate Binary Search Tree)

使用递归中序遍历。二叉搜索树的中序遍历结果是升序的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class TreeProblems {
public static boolean isValidBST(TreeNode root) {
return isValidBST(root, Long.MIN_VALUE, Long.MAX_VALUE);
}

private static boolean isValidBST(TreeNode node, long min, long max) {
if (node == null) {
return true;
}
// 检查当前节点的值是否在有效范围内
if (node.val <= min || node.val >= max) {
return false;
}
// 递归检查左右子树,并缩小有效范围
return isValidBST(node.left, min, node.val) && isValidBST(node.right, node.val, max);
}
}

其他

最大子数组和 (Maximum Subarray Sum)

使用动态规划 (Kadane’s algorithm)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class ArrayProblems {
public static int maxSubArray(int[] nums) {
if (nums == null || nums.length == 0) {
return 0;
}

int maxSoFar = nums[0]; // 迄今为止的最大和
int maxEndingHere = nums[0]; // 以当前位置结尾的最大和

for (int i = 1; i < nums.length; i++) {
// maxEndingHere 的值是:(当前元素) 或 (当前元素 + 之前的最大和)
maxEndingHere = Math.max(nums[i], maxEndingHere + nums[i]);
maxSoFar = Math.max(maxSoFar, maxEndingHere);
}
return maxSoFar;
}
}

两数相加 (Large Number Addition)

将两个超长正整数字符串相加,模拟小学加法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class StringProblems {
public static String addStrings(String num1, String num2) {
StringBuilder sb = new StringBuilder();
int i = num1.length() - 1;
int j = num2.length() - 1;
int carry = 0;

while (i >= 0 || j >= 0 || carry > 0) {
int digit1 = (i >= 0) ? num1.charAt(i--) - '0' : 0;
int digit2 = (j >= 0) ? num2.charAt(j--) - '0' : 0;

int sum = digit1 + digit2 + carry;
sb.append(sum % 10);
carry = sum / 10;
}
return sb.reverse().toString();
}
}

位运算实现加法 (Addition with Bitwise Operators)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class MathProblems {
public static int add(int a, int b) {
int sum;
int carry;

while (b != 0) {
// 计算无进位的和(异或)
sum = a ^ b;
// 计算进位(与&左移)
carry = (a & b) << 1;
a = sum;
b = carry;
}
return a;
}
}

全排列 (Permutations)

使用回溯算法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import java.util.ArrayList;
import java.util.List;

public class BacktrackingProblems {
public static List<List<Integer>> permute(int[] nums) {
List<List<Integer>> result = new ArrayList<>();
backtrackPermute(result, new ArrayList<>(), nums, new boolean[nums.length]);
return result;
}

private static void backtrackPermute(List<List<Integer>> result, List<Integer> tempList, int[] nums, boolean[] used) {
if (tempList.size() == nums.length) {
result.add(new ArrayList<>(tempList));
return;
}
for (int i = 0; i < nums.length; i++) {
if (used[i]) {
continue;
}
used[i] = true;
tempList.add(nums[i]);
backtrackPermute(result, tempList, nums, used);
tempList.remove(tempList.size() - 1);
used[i] = false;
}
}
}

子集 (Subsets)

使用回溯算法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import java.util.ArrayList;
import java.util.List;

public class BacktrackingProblems {
public static List<List<Integer>> subsets(int[] nums) {
List<List<Integer>> result = new ArrayList<>();
backtrackSubsets(result, new ArrayList<>(), nums, 0);
return result;
}

private static void backtrackSubsets(List<List<Integer>> result, List<Integer> tempList, int[] nums, int start) {
result.add(new ArrayList<>(tempList));
for (int i = start; i < nums.length; i++) {
tempList.add(nums[i]);
backtrackSubsets(result, tempList, nums, i + 1);
tempList.remove(tempList.size() - 1);
}
}
}

跳台阶 (Climbing Stairs)

这是经典的动态规划问题,与斐波那契数列类似。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class MathProblems {
public static int climbStairs(int n) {
if (n <= 2) {
return n;
}
int[] dp = new int[n + 1];
dp[1] = 1;
dp[2] = 2;
for (int i = 3; i <= n; i++) {
dp[i] = dp[i - 1] + dp[i - 2];
}
return dp[n];
}
}

实现一个简单的 Trie (前缀树)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
import java.util.HashMap;
import java.util.Map;

class TrieNode {
Map<Character, TrieNode> children;
boolean isEndOfWord;

public TrieNode() {
children = new HashMap<>();
isEndOfWord = false;
}
}

class Trie {
private TrieNode root;

public Trie() {
root = new TrieNode();
}

public void insert(String word) {
TrieNode current = root;
for (char c : word.toCharArray()) {
current = current.children.computeIfAbsent(c, k -> new TrieNode());
}
current.isEndOfWord = true;
}

public boolean search(String word) {
TrieNode current = root;
for (char c : word.toCharArray()) {
if (!current.children.containsKey(c)) {
return false;
}
current = current.children.get(c);
}
return current.isEndOfWord;
}

public boolean startsWith(String prefix) {
TrieNode current = root;
for (char c : prefix.toCharArray()) {
if (!current.children.containsKey(c)) {
return false;
}
current = current.children.get(c);
}
return true;
}
}

反转链表 II (Reverse Linked List II)

反转链表从位置 mn 的部分。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class LinkedListProblems {
public static ListNode reverseBetween(ListNode head, int m, int n) {
ListNode dummy = new ListNode(0);
dummy.next = head;
ListNode pre = dummy;

for (int i = 0; i < m - 1; i++) {
pre = pre.next; // 移动到反转部分的前一个节点
}

ListNode start = pre.next; // 反转部分的起始节点
ListNode then = start.next; // 待反转的第二个节点

for (int i = 0; i < n - m; i++) {
start.next = then.next;
then.next = pre.next;
pre.next = then;
then = start.next;
}

return dummy.next;
}
}

LRU 缓存 (LRU Cache)

使用 LinkedHashMap 可以非常方便地实现 LRU 缓存,因为它本身就维护了插入顺序或访问顺序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import java.util.LinkedHashMap;
import java.util.Map;

class LRUCache<K, V> extends LinkedHashMap<K, V> {
private final int capacity;

public LRUCache(int capacity) {
super(capacity, 0.75f, true); // true 表示按访问顺序排序
this.capacity = capacity;
}

@Override
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
// 当容量超过限制时,自动移除最老的(最久未访问的)条目
return size() > capacity;
}

public V get(K key) {
return super.getOrDefault(key, null);
}

public void put(K key, V value) {
super.put(key, value);
}
}

判断回文链表 (Palindrome Linked List)

可以使用快慢指针找到中点,然后反转后半部分,最后比较两部分是否相等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public class LinkedListProblems {
public static boolean isPalindrome(ListNode head) {
if (head == null || head.next == null) {
return true;
}

// 1. 快慢指针找中点
ListNode slow = head;
ListNode fast = head;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
}

// 2. 反转后半部分
ListNode secondHalf = reverseList(slow);

// 3. 比较前半部分和反转后的后半部分
ListNode firstHalf = head;
while (secondHalf != null) {
if (firstHalf.val != secondHalf.val) {
return false;
}
firstHalf = firstHalf.next;
secondHalf = secondHalf.next;
}
return true;
}

private static ListNode reverseList(ListNode head) {
ListNode prev = null;
ListNode current = head;
while (current != null) {
ListNode nextTemp = current.next;
current.next = prev;
prev = current;
current = nextTemp;
}
return prev;
}
}

ReadWriteLock

ReadWriteLock 允许多个线程同时进行读操作,但只允许一个线程进行写操作,从而提高读多写少的场景下的并发性能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

class SharedResource {
private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
private String data = "初始数据";

// 读操作:允许多个线程同时访问
public String readData() {
rwLock.readLock().lock(); // 获取读锁
try {
System.out.println(Thread.currentThread().getName() + " 正在读取数据...");
Thread.sleep(100); // 模拟耗时操作
return data;
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return null;
} finally {
rwLock.readLock().unlock(); // 释放读锁
}
}

// 写操作:同一时刻只能有一个线程访问
public void writeData(String newData) {
rwLock.writeLock().lock(); // 获取写锁
try {
System.out.println(Thread.currentThread().getName() + " 正在写入数据...");
Thread.sleep(1000); // 模拟耗时操作
this.data = newData;
System.out.println(Thread.currentThread().getName() + " 写入完成。");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
rwLock.writeLock().unlock(); // 释放写锁
}
}
}

CountDownLatch

CountDownLatch 允许一个线程等待,直到其他线程都完成了某项工作。它就像一个倒计时器,一旦计数为零,等待的线程就会被唤醒。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
final int workerCount = 5;
// 创建一个计数器,初始值为 5
CountDownLatch latch = new CountDownLatch(workerCount);
ExecutorService executor = Executors.newFixedThreadPool(workerCount);

System.out.println("主线程:启动 " + workerCount + " 个工作线程...");
for (int i = 0; i < workerCount; i++) {
final int workerId = i;
executor.execute(() -> {
System.out.println("工作线程 " + workerId + " 开始执行任务...");
try {
Thread.sleep((long) (Math.random() * 2000)); // 模拟任务执行
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
System.out.println("工作线程 " + workerId + " 任务完成。");
latch.countDown(); // 任务完成后,计数器减 1
}
});
}

System.out.println("主线程:等待所有工作线程完成...");
latch.await(); // 阻塞主线程,直到计数器为 0
System.out.println("主线程:所有工作线程已完成,继续执行下一步。");

executor.shutdown();
}
}

CyclicBarrier

CyclicBarrier 允许一组线程在到达一个共同的屏障点后,再一起继续执行。它就像赛跑的起跑线,所有选手都准备好后,发令枪才响。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

class CyclicBarrierDemo {
public static void main(String[] args) {
final int runnerCount = 3;
// 创建一个屏障,当 3 个线程都到达时,执行一个屏障动作
CyclicBarrier barrier = new CyclicBarrier(runnerCount, () -> {
System.out.println("\n所有赛跑者都已就位,发令枪响!\n");
});
ExecutorService executor = Executors.newFixedThreadPool(runnerCount);

for (int i = 0; i < runnerCount; i++) {
final int runnerId = i;
executor.execute(() -> {
try {
System.out.println("赛跑者 " + runnerId + " 正在走向起跑线...");
Thread.sleep((long) (Math.random() * 3000)); // 模拟准备时间
System.out.println("赛跑者 " + runnerId + " 到达起跑线,准备就绪。");
barrier.await(); // 线程在此处等待,直到所有线程都到达

System.out.println("赛跑者 " + runnerId + " 开始跑步!");
} catch (InterruptedException | BrokenBarrierException e) {
Thread.currentThread().interrupt();
}
});
}
executor.shutdown();
}
}

Semaphore

Semaphore(信号量)用来控制对某个资源的并发访问数量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import java.util.concurrent.Semaphore;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

class SemaphoreDemo {
// 停车场最多允许 3 辆车停放
private final Semaphore semaphore = new Semaphore(3);

public void park(int carId) {
try {
System.out.println("汽车 " + carId + " 正在寻找车位...");
semaphore.acquire(); // 获取一个许可
System.out.println("汽车 " + carId + " 成功进入停车场。");
Thread.sleep((long) (Math.random() * 3000)); // 模拟停车时间
System.out.println("汽车 " + carId + " 离开停车场。");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
semaphore.release(); // 释放许可
}
}

public static void main(String[] args) {
SemaphoreDemo demo = new SemaphoreDemo();
ExecutorService executor = Executors.newFixedThreadPool(10); // 10 辆车

for (int i = 0; i < 10; i++) {
final int carId = i;
executor.execute(() -> demo.park(carId));
}

executor.shutdown();
}
}

死锁预防 (按锁顺序)

通过资源有序分配来打破死锁的循环等待条件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
class DeadlockPrevention {
private static final Object lockA = new Object();
private static final Object lockB = new Object();

public void threadOne() {
synchronized (lockA) {
System.out.println("线程 1: 已获得 lockA,尝试获取 lockB...");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
synchronized (lockB) {
System.out.println("线程 1: 已获得 lockB。");
}
}
}

public void threadTwo() {
// 两个线程都按相同的顺序(先 A 后 B)获取锁
synchronized (lockA) {
System.out.println("线程 2: 已获得 lockA,尝试获取 lockB...");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
synchronized (lockB) {
System.out.println("线程 2: 已获得 lockB。");
}
}
}

public static void main(String[] args) {
DeadlockPrevention demo = new DeadlockPrevention();
new Thread(demo::threadOne, "Thread-1").start();
new Thread(demo::threadTwo, "Thread-2").start();
}
}

线程池异常

使用 Future.get() 或在任务中捕获异常来处理线程池中任务抛出的异常。execute() 方法无法直接捕获异常。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import java.util.concurrent.*;

class ThreadPoolExceptionDemo {
public static void main(String[] args) throws InterruptedException {
ExecutorService executor = Executors.newFixedThreadPool(2);

// 提交一个会抛出运行时异常的任务
Future<?> future = executor.submit(() -> {
System.out.println("任务开始执行...");
throw new RuntimeException("这是一个模拟的任务执行异常。");
});

// 尝试获取任务结果来捕获异常
try {
future.get();
} catch (ExecutionException e) {
System.err.println("捕获到任务执行异常:" + e.getCause().getMessage());
}

// 使用 execute() 提交任务,异常会被吞掉,除非自定义 UncaughtExceptionHandler
executor.execute(() -> {
System.out.println("另一个任务开始执行...");
throw new IllegalArgumentException("这个异常会被吞掉。");
});

executor.shutdown();
executor.awaitTermination(1, TimeUnit.SECONDS);
}
}

ThreadLocal

ThreadLocal 为每个线程提供了独立的变量副本,实现了数据隔离。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class ThreadLocalDemo {
private static final ThreadLocal<String> threadLocal = new ThreadLocal<>();

public void setAndPrint(String value) {
threadLocal.set(value); // 设置当前线程的变量副本
try {
Thread.sleep(1000); // 模拟任务
System.out.println(Thread.currentThread().getName() + " 的变量值: " + threadLocal.get());
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
threadLocal.remove(); // 最佳实践:使用完后移除,防止内存泄漏
}
}

public static void main(String[] args) {
ThreadLocalDemo demo = new ThreadLocalDemo();
new Thread(() -> demo.setAndPrint("线程 A 的数据"), "Thread-A").start();
new Thread(() -> demo.setAndPrint("线程 B 的数据"), "Thread-B").start();
}
}

LockSupport

LockSupport.park()LockSupport.unpark() 提供了更灵活的线程阻塞和唤醒机制,类似于 wait()notify(),但不需要依赖锁。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import java.util.concurrent.locks.LockSupport;

class LockSupportDemo {
public static void main(String[] args) throws InterruptedException {
Thread workerThread = new Thread(() -> {
System.out.println("工作线程: 任务准备就绪,即将阻塞...");
LockSupport.park(); // 阻塞当前线程
System.out.println("工作线程: 被唤醒,继续执行。");
});

workerThread.start();
Thread.sleep(2000); // 确保工作线程已执行 park()

System.out.println("主线程: 唤醒工作线程。");
LockSupport.unpark(workerThread); // 唤醒指定线程
}
}

线程通信 (按顺序打印)

使用 wait()notifyAll() 实现三个线程按顺序打印。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
class SequentialPrinter {
private int state = 0; // 0: A, 1: B, 2: C
private final Object lock = new Object();

public void printA() {
printLetter("A", 0);
}

public void printB() {
printLetter("B", 1);
}

public void printC() {
printLetter("C", 2);
}

private void printLetter(String letter, int expectedState) {
for (int i = 0; i < 10; i++) {
synchronized (lock) {
while (state != expectedState) {
try {
lock.wait();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}
}
System.out.print(letter);
state = (state + 1) % 3; // 切换到下一个状态
lock.notifyAll();
}
}
}

public static void main(String[] args) throws InterruptedException {
SequentialPrinter printer = new SequentialPrinter();
Thread threadA = new Thread(printer::printA);
Thread threadB = new Thread(printer::printB);
Thread threadC = new Thread(printer::printC);

threadA.start();
threadB.start();
threadC.start();

threadA.join();
threadB.join();
threadC.join();
System.out.println("\n打印完成。");
}
}

线程池

ThreadPoolExecutor 的七个参数

  1. corePoolSize: 核心线程数。线程池中常驻的线程数量,即使空闲也不会被销毁。
  2. maximumPoolSize: 最大线程数。当工作队列已满,且任务量继续增加时,线程池可以创建的最大线程数。
  3. keepAliveTime: 空闲线程存活时间。当线程数大于 corePoolSize 时,非核心线程的空闲存活时间。
  4. unit: keepAliveTime 的时间单位。
  5. workQueue: 工作队列。用于存放等待执行的任务,常用的有 ArrayBlockingQueueLinkedBlockingQueue 等。
  6. threadFactory: 线程工厂。用于创建新线程,可以自定义线程的名称、优先级等。
  7. handler: 拒绝策略。当线程池和工作队列都已满时,用于处理新来的任务,例如抛出异常、由调用者执行等。

手写一个自定义线程池

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import java.util.concurrent.*;

public class CustomThreadPool {
public static void main(String[] args) {
// 自定义线程池
ExecutorService executor = new ThreadPoolExecutor(
2, // 核心线程数
5, // 最大线程数
60L, // 空闲线程存活时间
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(10), // 工作队列容量为 10
Executors.defaultThreadFactory(), // 默认线程工厂
new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略:由调用者线程执行
);

for (int i = 0; i < 20; i++) {
final int taskId = i;
executor.execute(() -> {
System.out.println("任务 " + taskId + " 正在由线程 " + Thread.currentThread().getName() + " 执行。");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
}
executor.shutdown();
}
}

原子类

使用 AtomicBooleanAtomicReference 解决并发问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import java.util.concurrent.atomic.AtomicBoolean;

class AtomicBooleanDemo {
// 确保只有一个线程执行初始化操作
private static final AtomicBoolean initialized = new AtomicBoolean(false);

public static void initialize() {
// 只有当 initialized 为 false 时,才将其设置为 true 并执行初始化
if (initialized.compareAndSet(false, true)) {
System.out.println(Thread.currentThread().getName() + ": 开始执行初始化操作...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
System.out.println(Thread.currentThread().getName() + ": 初始化完成。");
} else {
System.out.println(Thread.currentThread().getName() + ": 初始化已被其他线程执行,跳过。");
}
}

public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
new Thread(AtomicBooleanDemo::initialize, "Thread-" + i).start();
}
}
}

volatile 内存语义

volatile 确保了可见性有序性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class VolatileMemorySemantics {
// 1. 可见性:当一个线程修改了 ready 的值,其他线程能立即看到最新值
private static volatile boolean ready = false;
private static int number = 0;

public static class WriterThread extends Thread {
@Override
public void run() {
number = 42; // 修改 number
ready = true; // 修改 ready
}
}

public static class ReaderThread extends Thread {
@Override
public void run() {
while (!ready) {
// 等待 ready 变为 true
// 如果没有 volatile,这里可能会陷入死循环
}
// 2. 有序性:写 volatile 变量(ready = true)之前的操作(number = 42)
// 对其他线程都是可见的。保证了 number 的值是 42。
System.out.println("读取到的 number 值: " + number);
}
}

public static void main(String[] args) throws InterruptedException {
new WriterThread().start();
new ReaderThread().start();
}
}

中断机制

一个线程通过响应 interrupt() 调用来正确停止自身。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class InterruptibleThreadDemo {
public static void main(String[] args) {
Thread worker = new Thread(() -> {
while (!Thread.currentThread().isInterrupted()) { // 检查中断标志
try {
System.out.println("线程正在执行...");
// sleep()、wait() 等方法会抛出 InterruptedException 并清除中断标志
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println("线程被中断,即将退出...");
// 重新设置中断标志,以便外层循环能正确退出
Thread.currentThread().interrupt();
// 或者直接 break 或 return 退出
break;
}
}
System.out.println("线程已优雅地退出。");
});

worker.start();
try {
Thread.sleep(3500);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
System.out.println("主线程: 发送中断信号。");
worker.interrupt();
}
}

ConcurrentHashMap

ConcurrentHashMap 的并发原理是**分段锁( 7)**或 CAS + Synchronized( 8),只对操作的桶进行锁定,大大提高了并发性能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

class ConcurrentHashMapDemo {
public static void main(String[] args) throws InterruptedException {
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
ExecutorService executor = Executors.newFixedThreadPool(10);

// 启动 10 个线程,每个线程向 map 中添加 1000 个键值对
for (int i = 0; i < 10; i++) {
final int threadId = i;
executor.execute(() -> {
for (int j = 0; j < 1000; j++) {
String key = "key-" + (threadId * 1000 + j);
map.put(key, j);
}
});
}
executor.shutdown();
executor.awaitTermination(1, TimeUnit.MINUTES);

System.out.println("最终 map 的大小: " + map.size()); // 期望值为 10000
}
}

ForkJoinPool

ForkJoinPool 是一个用于分治任务的线程池,RecursiveTask 是可返回结果的分治任务。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveTask;

class SumTask extends RecursiveTask<Long> {
private final long[] array;
private final int start;
private final int end;
private static final int THRESHOLD = 10000; // 任务分解的阈值

public SumTask(long[] array, int start, int end) {
this.array = array;
this.start = start;
this.end = end;
}

@Override
protected Long compute() {
// 如果任务规模小于等于阈值,则直接计算
if (end - start <= THRESHOLD) {
long sum = 0;
for (int i = start; i < end; i++) {
sum += array[i];
}
return sum;
} else {
// 否则,将任务分解成两个子任务
int mid = start + (end - start) / 2;
SumTask leftTask = new SumTask(array, start, mid);
SumTask rightTask = new SumTask(array, mid, end);

leftTask.fork(); // 提交左子任务
long rightResult = rightTask.compute(); // 同步计算右子任务
long leftResult = leftTask.join(); // 等待左子任务结果

return leftResult + rightResult;
}
}

public static void main(String[] args) {
long[] array = new long[1000000];
for (int i = 0; i < array.length; i++) {
array[i] = i;
}

ForkJoinPool pool = new ForkJoinPool();
SumTask task = new SumTask(array, 0, array.length);
long result = pool.invoke(task);
System.out.println("大数组的和为: " + result);

pool.shutdown();
}
}

1. 语言基础

变量与数据类型

的基本数据类型决定了变量可以存储的数据范围。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class DataTypesDetailed {
public static void main(String[] args) {
// 整数类型:默认为 int
byte b = 100; // 占用 1 字节,-128 到 127
short s = 10000; // 占用 2 字节
int i = 100000; // 占用 4 字节
long l = 10000000000L; // 占用 8 字节,需要 L 或 l 后缀

// 浮点类型:默认为 double
float f = 3.14f; // 占用 4 字节,需要 f 或 F 后缀
double d = 3.1415926535; // 占用 8 字节

// 字符类型
char c1 = 'A'; // 单个字符,占用 2 字节
char c2 = 65; // 也可以使用 ASCII 码或 Unicode
System.out.println("c1 和 c2 是否相等? " + (c1 == c2));

// 布尔类型
boolean isFun = true;
System.out.println("学Java有趣吗?" + isFun);
}
}

类型转换

中,从小范围类型向大范围类型转换是自动的(隐式转换);从大范围向小范围转换需要强制转换(显式转换),可能造成数据丢失。

1
2
3
4
5
6
7
8
9
10
11
12
13
public class TypeCasting {
public static void main(String[] args) {
// 隐式转换:int -> long
int myInt = 100;
long myLong = myInt;
System.out.println("隐式转换后的 long 类型: " + myLong);

// 显式转换:double -> int
double myDouble = 9.99;
int myInteger = (int) myDouble; // 强制转换,小数部分被丢弃
System.out.println("显式转换后的 int 类型: " + myInteger); // 输出 9
}
}

数组

数组是存储固定大小同类型元素的集合。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class ArrayExample {
public static void main(String[] args) {
// 声明并初始化一个整数数组
int[] numbers = new int[5]; // 创建一个长度为 5 的数组
numbers[0] = 10;
numbers[1] = 20;

// 声明、初始化并赋值
String[] fruits = {"Apple", "Banana", "Cherry"};

// 遍历数组
System.out.println("所有水果:");
for (String fruit : fruits) { // 增强 for 循环
System.out.println(fruit);
}
}
}

2. 面向对象编程 (OOP)

构造方法与方法重载

构造方法是一种特殊方法,用于创建对象时初始化。方法重载是指在同一个类中,方法名相同但参数列表不同(参数类型、数量或顺序)的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class Student {
String name;
int age;
String id;

// 无参构造方法
public Student() {
this("未知", 0, "000"); // 调用本类的三参构造方法
}

// 重载构造方法
public Student(String name, int age) {
this.name = name;
this.age = age;
this.id = "000";
}

// 重载构造方法
public Student(String name, int age, String id) {
this.name = name;
this.age = age;
this.id = id;
}

// 方法重载: 计算两个数的和
public int add(int a, int b) {
return a + b;
}

public double add(double a, double b) {
return a + b;
}
}

继承、多态与抽象

继承实现代码复用,多态实现行为多样化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
// 父类:抽象类
abstract class Vehicle {
protected String brand; // 子类可以访问

public Vehicle(String brand) {
this.brand = brand;
}

public abstract void run(); // 抽象方法,子类必须实现

public void displayBrand() {
System.out.println("品牌是: " + brand);
}
}

// 子类:继承 Vehicle
class Car extends Vehicle {
public Car(String brand) {
super(brand); // 调用父类的构造方法
}

@Override
public void run() {
System.out.println("汽车正在路上行驶...");
}
}

// 子类:继承 Vehicle
class Bicycle extends Vehicle {
public Bicycle(String brand) {
super(brand);
}

@Override
public void run() {
System.out.println("自行车正在骑行...");
}
}

public class PolymorphismDemo {
public static void main(String[] args) {
Vehicle myCar = new Car("BMW"); // 多态引用
Vehicle myBicycle = new Bicycle("Giant");

// 同一个方法调用,不同行为
myCar.run(); // 输出 "汽车正在路上行驶..."
myBicycle.run(); // 输出 "自行车正在骑行..."
}
}

3. 核心类库

好的,这次我们将把各种方法的使用代码直接嵌入到每个知识点的解释中,让您在学习概念的同时,就能看到具体的代码实现和效果。我们将专注于数组、字符串和集合这三大核心部分,把它们的创建、遍历、和各种常用方法的代码示例都清晰地展示出来。


1. 数组(Array)

数组是一种固定大小的、用于存储同类型元素的容器。

创建和遍历

这里展示两种最常见的创建数组的方式,并使用两种循环进行遍历。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import java.util.Arrays;

public class ArrayExample {
public static void main(String[] args) {
// 方式一:声明并分配空间,系统赋默认值
int[] intArray = new int[3]; // 创建一个包含3个整数的数组,默认值都是0
intArray[0] = 10;
intArray[1] = 20;
intArray[2] = 30;

System.out.println("数组 intArray 的第一个元素是: " + intArray[0]); // 输出: 10

// 方式二:声明并直接初始化
String[] stringArray = {"Hello", "World", "Java"};

// 遍历方式一:使用 for 循环
System.out.println("\n--- 使用 for 循环遍历 ---");
for (int i = 0; i < stringArray.length; i++) {
System.out.println("stringArray[" + i + "] = " + stringArray[i]);
}

// 遍历方式二:使用增强 for 循环(更简洁)
System.out.println("\n--- 使用增强 for 循环遍历 ---");
for (String element : stringArray) {
System.out.println("元素: " + element);
}
}
}

Arrays 类的常用方法

java.util.Arrays 类提供了很多静态方法,方便我们对数组进行操作,比如排序、查找、复制等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import java.util.Arrays;

public class ArraysMethodExample {
public static void main(String[] args) {
int[] numbers = {4, 2, 8, 1, 6};

// 1. 排序:Arrays.sort()
Arrays.sort(numbers);
System.out.println("排序后: " + Arrays.toString(numbers)); // 输出: [1, 2, 4, 6, 8]

// 2. 查找:Arrays.binarySearch() (必须先排序)
int index = Arrays.binarySearch(numbers, 6);
System.out.println("元素 6 的索引是: " + index); // 输出: 3

// 3. 填充:Arrays.fill()
int[] newArray = new int[5];
Arrays.fill(newArray, 99);
System.out.println("填充后: " + Arrays.toString(newArray)); // 输出: [99, 99, 99, 99, 99]

// 4. 复制:Arrays.copyOf()
int[] copiedArray = Arrays.copyOf(numbers, 3); // 复制前3个元素
System.out.println("复制前3个元素: " + Arrays.toString(copiedArray)); // 输出: [1, 2, 4]

// 5. 比较:Arrays.equals()
int[] anotherArray = {1, 2, 4, 6, 8};
System.out.println("两个数组是否相等: " + Arrays.equals(numbers, anotherArray)); // 输出: true
}
}

2. 字符串(String)

String 是一个不可变的字符序列,这意味着一旦创建,它的内容就不能被修改。所有修改操作都会返回一个新的 String 对象。

创建和常用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
public class StringExample {
public static void main(String[] args) {
// 创建方式
String str1 = " Java is a great language. "; // 字面量
String str2 = new String("Java is a great language."); // 使用 new 关键字

// 常用方法
// 1. 获取长度
int length = str1.length();
System.out.println("字符串长度: " + length); // 输出: 29

// 2. 访问字符
char firstChar = str1.charAt(2);
System.out.println("第3个字符是: " + firstChar); // 输出: J

// 3. 截取子串
String sub = str1.substring(5, 7);
System.out.println("截取子串: " + sub); // 输出: is

// 4. 查找位置
int index = str1.indexOf("great");
System.out.println("'great' 的索引: " + index); // 输出: 11

// 5. 判断
boolean contains = str1.contains("language");
System.out.println("是否包含 'language': " + contains); // 输出: true
boolean startsWith = str1.startsWith(" Java");
System.out.println("是否以 ' Java' 开头: " + startsWith); // 输出: true

// 6. 替换
String replacedStr = str1.replace("great", "wonderful");
System.out.println("替换后: " + replacedStr); // 输出: Java is a wonderful language.

// 7. 大小写转换和去空格
String trimmedStr = str1.trim();
System.out.println("去除首尾空格: '" + trimmedStr + "'"); // 输出: 'Java is a great language.'
System.out.println("转为大写: " + trimmedStr.toUpperCase());

// 8. 分割和连接
String data = "apple,banana,orange";
String[] fruits = data.split(",");
System.out.println("分割后: " + Arrays.toString(fruits)); // 输出: [apple, banana, orange]

String joinedString = String.join(" - ", fruits);
System.out.println("连接后: " + joinedString); // 输出: apple - banana - orange
}
}

StringBuilderStringBuffer

对于需要频繁修改字符串的场景,应使用 StringBuilderStringBuffer 以提高性能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class StringBuilderExample {
public static void main(String[] args) {
StringBuilder sb = new StringBuilder("Hello");

// 1. 追加内容
sb.append(" World");
System.out.println("追加后: " + sb); // 输出: Hello World

// 2. 插入内容
sb.insert(6, "Beautiful ");
System.out.println("插入后: " + sb); // 输出: Hello Beautiful World

// 3. 删除内容
sb.delete(6, 15);
System.out.println("删除后: " + sb); // 输出: Hello World
}
}

3. 集合(Collections)

Java 集合框架提供了强大的数据结构来存储和操作对象。

List(列表)

List 是一种有序、可重复的集合。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

public class ListExample {
public static void main(String[] args) {
// 创建 ArrayList(查询快)
List<String> fruits = new ArrayList<>();
fruits.add("Apple"); // 添加元素
fruits.add("Banana");
fruits.add("Apple"); // 允许重复

System.out.println("列表元素: " + fruits); // 输出: [Apple, Banana, Apple]
System.out.println("第一个元素: " + fruits.get(0)); // 获取元素
System.out.println("列表大小: " + fruits.size());

fruits.set(1, "Grape"); // 替换第二个元素
System.out.println("替换后: " + fruits); // 输出: [Apple, Grape, Apple]

fruits.remove(1); // 删除第二个元素
System.out.println("删除后: " + fruits); // 输出: [Apple, Apple]
}
}

Set(集)

Set 是一种无序、不可重复的集合。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import java.util.HashSet;
import java.util.Set;

public class SetExample {
public static void main(String[] args) {
// 创建 HashSet
Set<String> uniqueFruits = new HashSet<>();
uniqueFruits.add("Apple");
uniqueFruits.add("Banana");
uniqueFruits.add("Apple"); // 添加重复元素,会失败

System.out.println("集合元素: " + uniqueFruits); // 输出: [Apple, Banana] (顺序不定)

boolean containsBanana = uniqueFruits.contains("Banana");
System.out.println("是否包含 'Banana': " + containsBanana); // 输出: true

uniqueFruits.remove("Banana");
System.out.println("删除后: " + uniqueFruits); // 输出: [Apple]
}
}

Map(映射)

Map 存储键值对,键是唯一的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import java.util.HashMap;
import java.util.Map;

public class MapExample {
public static void main(String[] args) {
// 创建 HashMap
Map<String, Integer> studentScores = new HashMap<>();
studentScores.put("Alice", 95); // 添加键值对
studentScores.put("Bob", 88);
studentScores.put("Alice", 100); // 键已存在,会覆盖旧值

System.out.println("Alice 的分数: " + studentScores.get("Alice")); // 输出: 100

// 遍历 Map 的三种方式
// 方式一:遍历键集
System.out.println("\n--- 遍历键集 ---");
for (String name : studentScores.keySet()) {
System.out.println("姓名: " + name + ", 分数: " + studentScores.get(name));
}

// 方式二:遍历值集
System.out.println("\n--- 遍历值集 ---");
for (Integer score : studentScores.values()) {
System.out.println("分数: " + score);
}

// 方式三:遍历键值对集 (Entry Set),最常用且高效
System.out.println("\n--- 遍历键值对集 ---");
for (Map.Entry<String, Integer> entry : studentScores.entrySet()) {
System.out.println("姓名: " + entry.getKey() + ", 分数: " + entry.getValue());
}
}
}