在Java编程的宇宙中,有一个充满神秘与挑战的维度——并发编程。它如同一座错综复杂的迷宫,每个角落都潜藏着惊喜与陷阱。在这篇博客里,我们将一起探索这座迷宫的深处,揭开同步的魔法与死锁的诅咒。
第一章:同步魔法的诞生
同步,是并发编程中最基础也是最重要的概念之一。它就像是一座桥梁,连接着多个线程的世界,确保数据的一致性和完整性。在Java中,synchronized
关键字是最常见的同步魔法,它能将普通的方法或者代码块变成一把锁,保护共享资源免受多线程的侵扰。
示例代码:
public class MagicCounter {
private int count = 0;
public synchronized void increment() {
// 这里是一个临界区,每次只允许一个线程进入
count++;
}
public synchronized int getCount() {
// 同步读取count的值,确保数据一致性
return count;
}
}
第二章:死锁的诅咒
然而,同步魔法虽强,但若使用不当,便会招致死锁的诅咒。死锁是一种极端情况,发生在两个或多个线程无限期地等待彼此持有的资源释放,导致整个系统陷入僵局。
死锁示例:
class KeyA {
synchronized void useKeyB(KeyB b) {
System.out.println("KeyA trying to use KeyB");
try { Thread.sleep(100); } catch (InterruptedException e) {}
b.useKeyA(this);
}
}
class KeyB {
synchronized void useKeyA(KeyA a) {
System.out.println("KeyB trying to use KeyA");
try { Thread.sleep(100); } catch (InterruptedException e) {}
a.useKeyB(this);
}
}
在这个例子中,两个线程分别持有了KeyA
和KeyB
的锁,并试图获取对方的锁。由于它们都无法释放自己持有的锁,因此陷入了死锁。
第三章:解锁同步的智慧
要避免死锁的诅咒,我们需要掌握更高级的同步技巧。在Java中,java.util.concurrent
包提供了多种工具,如ReentrantLock
、Semaphore
和Condition
,它们提供了比synchronized
更精细的控制能力。
ReentrantLock示例:
import java.util.concurrent.locks.ReentrantLock;
public class SmartCounter {
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
不仅提供了可中断的锁获取,还允许公平锁和非公平锁的选择,使得同步策略更加灵活。
第四章:条件变量与信号量的魅力
除了锁之外,Java还提供了条件变量(Condition
)和信号量(Semaphore
),它们能够实现更复杂的同步模式。条件变量允许线程在特定条件下等待,而信号量则用于控制对一组相关资源的访问。
Semaphore示例:
import java.util.concurrent.Semaphore;
public class SemaphoreExample {
private static final Semaphore semaphore = new Semaphore(5);
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(() -> {
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + " is accessing a resource.");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release();
}
}).start();
}
}
}
在这个例子中,信号量限制了同时访问资源的线程数量,有效地避免了资源争抢。