1. 使用同步机制
1.1 synchronized 关键字
synchronized 是Java中最基础的同步机制,用于确保某个代码块或方法在同一时间只能被一个线程执行。synchronized 可以应用于方法或方法中的代码块。
-
同步方法:将整个方法用
synchronized修饰,保证同一时间只有一个线程可以执行该方法。public class Counter { private int count = 0; public synchronized void increment() { count++; } public synchronized int getCount() { return count; } } -
同步代码块:使用
synchronized块对特定代码段进行同步,可以减少锁的粒度,提高并发性能。public class Counter { private int count = 0; public void increment() { synchronized (this) { count++; } } public int getCount() { synchronized (this) { return count; } } }
在上述示例中,synchronized 确保 count 的增量操作是原子性的,避免了多线程同时修改 count 时出现不一致的问题。
1.2 静态同步方法
static 方法上的 synchronized 关键字会锁定类的 Class 对象,而不是实例对象。这意味着对于该类的所有实例,synchronized 方法在同一时间只能由一个线程执行。
public class StaticCounter {
private static int count = 0;
public static synchronized void increment() {
count++;
}
public static synchronized int getCount() {
return count;
}
}
在这个示例中,increment() 和 getCount() 方法会在同一时间内被所有类的实例同步。
2. 使用 volatile 关键字
volatile 关键字用于修饰变量,确保多个线程访问该变量时看到的是一致的值。volatile 保证变量的可见性,即当一个线程修改了 volatile 变量的值,其他线程会立即看到修改后的值。
public class VolatileExample {
private volatile boolean flag = true;
public void stop() {
flag = false;
}
public void doWork() {
while (flag) {
// 执行一些操作
}
}
}
在这个示例中,当一个线程调用 stop() 方法修改 flag 为 false 时,其他线程立即能看到 flag 的变化,停止 doWork() 方法的执行。
3. 使用显式锁 (ReentrantLock)
Java 提供了 java.util.concurrent.locks.ReentrantLock 类,它是比 synchronized 更灵活的锁机制。ReentrantLock 提供了显式锁管理的能力,可以在不同的作用域内锁定和解锁代码,并且支持公平锁(按线程等待的顺序获得锁)和非公平锁。
import java.util.concurrent.locks.ReentrantLock;
public class LockExample {
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 确保 increment() 和 getCount() 方法在多线程环境下安全地访问和修改 count 变量。
3.1 公平锁和非公平锁
-
公平锁:线程获取锁的顺序与线程请求锁的顺序相同,先请求锁的线程优先获取锁。
private final ReentrantLock lock = new ReentrantLock(true); // 公平锁 -
非公平锁:锁的获取顺序不一定按线程请求的顺序,可以提高吞吐量,但可能导致某些线程长期得不到锁。
private final ReentrantLock lock = new ReentrantLock(); // 非公平锁(默认)
4. 使用 Atomic 类
Java提供了一些原子操作类(如 AtomicInteger, AtomicLong, AtomicReference 等),这些类通过硬件层面的原子操作来保证线程安全,而不需要使用锁。
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicExample {
private AtomicInteger count = new AtomicInteger(0);
public void increment() {
count.incrementAndGet();
}
public int getCount() {
return count.get();
}
}
AtomicInteger 确保了 incrementAndGet() 操作是线程安全的,即使多个线程同时执行该方法,也不会出现数据不一致的问题。
5. 使用 java.util.concurrent 包中的并发工具
5.1 ExecutorService 和线程池
ExecutorService 提供了比直接创建和管理线程更好的方法,可以通过线程池来管理线程的生命周期,避免频繁创建和销毁线程带来的性能开销。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolExample {
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(10);
for (int i = 0; i < 100; i++) {
executor.submit(() -> {
// 执行任务
System.out.println(Thread.currentThread().getName() + " is running");
});
}
executor.shutdown();
}
}
在这个示例中,ExecutorService 创建了一个固定大小的线程池,线程池中的线程可以被重复利用,从而提高性能。
5.2 CountDownLatch
CountDownLatch 是一个同步辅助类,允许一个或多个线程等待其他线程完成操作。通常用于协调多个线程的启动或等待多个线程完成工作。
import java.util.concurrent.CountDownLatch;
public class CountDownLatchExample {
public static void main(String[] args) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(3);
Runnable task = () -> {
System.out.println(Thread.currentThread().getName() + " is running");
latch.countDown();
};
new Thread(task).start();
new Thread(task).start();
new Thread(task).start();
latch.await(); // 主线程等待所有任务完成
System.out.println("All tasks completed.");
}
}
5.3 CyclicBarrier
CyclicBarrier 用于让一组线程彼此等待,直到所有线程都到达某个公共屏障点。
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierExample {
public static void main(String[] args) {
int parties = 3;
CyclicBarrier barrier = new CyclicBarrier(parties, () -> {
System.out.println("All parties have arrived, continue processing...");
});
Runnable task = () -> {
try {
System.out.println(Thread.currentThread().getName() + " is waiting");
barrier.await();
System.out.println(Thread.currentThread().getName() + " is running");
} catch (InterruptedException | BrokenBarrierException e) {
Thread.currentThread().interrupt();
}
};
new Thread(task).start();
new Thread(task).start();
new Thread(task).start();
}
}
6. 避免死锁
死锁是多线程编程中的常见问题,通常发生在多个线程相互等待彼此持有的锁时。为了避免死锁,可以采取以下措施:
- 避免嵌套锁定:尽量减少锁的嵌套,避免多个线程同时持有多个锁。
- 锁的顺序:确保所有线程在获取多个锁时,按照相同的顺序获取锁。
- 使用
tryLock:ReentrantLock提供的tryLock方法可以尝试获取锁,如果获取不到锁,可以避免死锁。
import java.util.concurrent.locks.ReentrantLock;
public class TryLockExample {
private final ReentrantLock lock1 = new ReentrantLock();
private final ReentrantLock lock2 = new ReentrantLock();
public void safeMethod() {
if (lock1.tryLock()) {
try {
if (lock2.tryLock()) {
try {
// 执行任务
} finally {
lock2.unlock();
}
}
} finally {
lock1.unlock();
}
} else {
System.out.println("Could not acquire lock, avoid deadlock");
}
}
}
7. 使用无锁数据结构
Java提供了一些无锁数据结构(如 ConcurrentHashMap, ConcurrentLinkedQueue),它们通过高效的无锁算法实现了线程安全,适用于高并发场景。
import java.util.concurrent.ConcurrentHashMap;
public class ConcurrentMapExample {
public static void main(String[] args) {
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
map.put("key", 1);
map.compute("key", (k, v) -> v + 1); // 线程安全的更新操作
System.out.println("Value for key: " + map.get("key"));
}
}
ConcurrentHashMap 通过分段锁机制,实现了在高并发情况下的高效操作。
总结
确保多线程安全是Java并发编程中的关键任务。通过使用以下技术和工具,可以有效地避免并发问题:
- 同步机制:使用
synchronized关键字或显式锁来保护共享资源。 volatile关键字:保证变量的可见性,避免缓存不一致问题。- 显式锁 (
ReentrantLock):提供了比synchronized更灵活的锁机制。 Atomic类:提供了无需锁的线程安全操作。- 并发工具 (
ExecutorService,CountDownLatch,CyclicBarrier等):简化并发编程中的线程管理和协调工作。 - 避免死锁:通过减少锁嵌套、使用锁顺序等策略避免死锁。
- 无锁数据结构:使用高效的无锁数据结构,如
ConcurrentHashMap,在高并发场景中提高性能。
通过合理应用这些技术和工具,可以构建出高效、安全的多线程Java程序。在实际开发中,根据具体场景选择合适的并发工具和策略,以确保应用的稳定性和性能。
485

被折叠的 条评论
为什么被折叠?



