Java核心技术【二十四】Java多线程同步:核心技术与实战指南
遇见即是缘分,关注🌟、点赞👍、收藏📚,让这份美好延续下去!
🌟 晋升管理 请通过文末二维码,关注 技术管理修行 ,定期分享技术管理知识与经验
在现代软件开发中,多线程编程是一个重要的概念,尤其在处理并发任务、提高程序性能和响应速度方面。然而,多线程编程也引入了新的复杂性,比如数据共享和线程安全问题。本文将深入探讨Java中的多线程同步技术,通过示例代码详细讲解关键概念,并探讨实际应用场景。
一、多线程同步基础
1. 线程安全问题
在多线程环境中,当多个线程访问共享资源时,可能会产生线程安全问题。例如,两个线程同时修改同一个共享变量,可能会导致数据不一致或数据损坏。
2. 同步机制
Java提供了多种同步机制,用于解决线程安全问题。主要机制包括:
- synchronized关键字:可以用于方法或代码块,确保同一时刻只有一个线程可以执行某个方法或代码块。
- volatile关键字:确保变量的可见性和顺序性,但不保证原子性。
- 重入锁(ReentrantLock):比synchronized更灵活的锁机制,支持公平锁和非公平锁。
- 信号量(Semaphore):用于控制对共享资源的访问数量。
- 其他并发工具:如CountDownLatch、CyclicBarrier、Exchanger等。
二、synchronized关键字
1. synchronized方法
synchronized可以修饰非静态方法、静态方法和代码块。修饰非静态方法时,锁定的是调用该方法的对象;修饰静态方法时,锁定的是该类的Class对象;修饰代码块时,锁定的是括号里配置的对象。
1)示例代码:
public class Counter {
private int count = 0;
public synchronized void increment() {
count++; // 增加操作,需要同步
}
public synchronized int getCount() {
return count;
}
}
2)测试代码:
package java_core_24;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Test24 {
private static final int THREAD_COUNT = 100; // 线程数量
private static final int INCREMENT_COUNT = 1000; // 每个线程执行的次数
public static void main(String[] args) throws InterruptedException {
Counter counter = new Counter();
ExecutorService executor = Executors.newFixedThreadPool(THREAD_COUNT);
CountDownLatch latch = new CountDownLatch(THREAD_COUNT);
for (int i = 0; i < THREAD_COUNT; i++) {
executor.submit(() -> {
for (int j = 0; j < INCREMENT_COUNT; j++) {
counter.increment();
}
latch.countDown(); // 计数器减一
});
}
latch.await(); // 等待所有线程执行完毕
executor.shutdown();
int expectedCount = THREAD_COUNT * INCREMENT_COUNT;
int actualCount = counter.getCount();
System.out.println("Expected count: " + expectedCount);
System.out.println("Actual count: " + actualCount);
if (actualCount == expectedCount) {
System.out.println("Test passed: Synchronization works correctly.");
} else {
System.out.println("Test failed: Synchronization does not work correctly.");
}
}
}
在这个测试中,我们在 Counter 类中的 increment() 和 getCount() 方法都正确地使用了 synchronized 关键字。如果 synchronized 关键字没有正确地实现同步,那么多个线程同时执行 increment() 方法时,计数器的最终值很可能会小于预期值。如果测试通过,说明 synchronized 关键字有效地保护了对共享资源的访问。
3)测试结果:
使用 synchronized 关键字声明后的结果:
Expected count: 100000
Actual count: 100000
Test passed: Synchronization works correctly.
去掉synchronized 关键字声明后的结果:
Expected count: 100000
Actual count: 88162
Test failed: Synchronization does not work correctly.
2. synchronized代码块
在某些情况下,可能只需要对方法中的部分代码进行同步,这时可以使用synchronized代码块。
public void updateObject() {
synchronized (this) {
// 只有这部分代码是同步的
// 修改共享资源
}
// 其他非同步代码
}
在 synchronized 代码块中,括号里配置的对象是锁对象。当多个线程访问同一个锁对象的 synchronized 代码块时,这些线程会被阻塞,直到锁被释放。
三、volatile关键字
volatile关键字用于确保变量的可见性和顺序性,但不保证原子性。当一个变量被volatile修饰时,任何线程对该变量的修改都会立即被其他线程看到。
1. 示例代码:
public class FlagRunner implements Runnable {
private volatile boolean flag = false;
public void run() {
int i = 0;
while (!flag) {
i++;
}
System.out.println("Stopped");
}
public void stopRunning() {
flag = true;
}
}
在这个例子中,flag 变量被volatile修饰,确保在 stopRunning() 方法中修改 flag 的值时,其他线程能够立即看到这一变化。
2. 测试代码:
为了测试 FlagRunner 类中的 volatile 关键字是否正确地实现了线程间的可见性,我们需要编写一个测试方法来模拟多线程环境下的行为。在这个测试中,我们将创建一个 FlagRunner 实例,并在一个线程中运行它,同时在另一个线程中改变 flag 的值。这样我们就可以验证当 flag 被修改时,线程是否能够正确地检测到这一变化。下面是具体的测试代码:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class FlagRunnerTest {
public static void main(String[] args) throws InterruptedException {
FlagRunner flagRunner = new FlagRunner();
// 创建一个线程池来执行线程
ExecutorService executor = Executors.newSingleThreadExecutor();
// 提交一个线程来运行 FlagRunner
executor.submit(flagRunner::run);
// 等待一段时间,确保 FlagRunner 的线程已经开始运行
Thread.sleep(1000);
// 停止 FlagRunner 的线程
flagRunner.stopRunning();
// 关闭线程池
executor.shutdown();
}
}
3. 测试结果:
线程感知到了 flag 值的变化,同时打印了如下信息:
Stopped
四、重入锁(ReentrantLock)
ReentrantLock提供了比synchronized更灵活的锁机制。它支持公平锁和非公平锁,还支持条件变量(Condition)。
import java.util.concurrent.locks.ReentrantLock;
public class Counter {
private final ReentrantLock lock = new ReentrantLock();
private int count = 0;
public void increment() {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
public int getCount() {
lock.lock();
try {
return count;
} finally {
lock.unlock();
}
}
}
在这个例子中,使用 ReentrantLock 来确保线程安全。与 synchronized 不同,使用 ReentrantLock 时必须在 finally 块中释放锁,以避免死锁。
五、总结
多线程同步技术是并发编程中的重要概念,Java提供了多种同步机制来解决线程安全问题。synchronized 关键字可以用于方法或代码块,确保同一时刻只有一个线程可以执行特定代码。volatile 关键字用于确保变量的可见性和顺序性,但不保证原子性。ReentrantLock 提供了比 synchronized 更灵活的锁机制,支持公平锁和非公平锁,还支持条件变量。
在实际应用中,多线程同步技术广泛应用于线程安全的计数器、单例模式和队列等场景。通过合理使用这些同步机制,可以确保多线程程序的正确性和性能。
原文地址:https://blog.csdn.net/wcblog/article/details/140820664
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!