8.21学会

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)的异常,主异常保持不变,
* 便于调试。
*/

线程创建

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();
}
}

死锁预防 (按锁顺序)

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

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());
}
}
}