好的,接下来我们学习第四部分:线程同步。
4. 线程同步
在多线程环境中,多个线程可能会同时访问共享资源,这会导致数据不一致和其他并发问题。为了保证数据的正确性和一致性,Java 提供了多种同步机制来控制线程对共享资源的访问。
4.1 共享资源和竞争条件
- 共享资源:指多个线程可能同时访问的变量、对象或数据结构。
- 竞争条件:当两个或多个线程在没有适当同步的情况下同时访问共享资源时,可能会导致数据不一致的问题。
4.2 同步方法和同步块
1. 同步方法
在方法上使用 synchronized
关键字,可以确保同一时刻只有一个线程能够执行该方法。synchronized
可以用于实例方法或静态方法。
示例代码:
class Counter {
private int count = 0;
// 同步实例方法
public synchronized void increment() {
count++;
}
public int getCount() {
return count;
}
}
public class SyncMethodExample {
public static void main(String[] args) {
Counter counter = new Counter();
// 创建多个线程来增加计数器
Thread t1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) counter.increment();
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) counter.increment();
});
t1.start();
t2.start();
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Final count: " + counter.getCount()); // 应该是2000
}
}
2. 同步块
与同步方法相比,同步块提供了更细粒度的控制。可以选择在特定的代码块中使用 synchronized
,而不是整个方法。使用 synchronized
块时,可以指定锁对象。
示例代码:
class Counter {
private int count = 0;
public void increment() {
// 使用同步块
synchronized (this) {
count++;
}
}
public int getCount() {
return count;
}
}
4.3 静态同步方法
如果将 synchronized
用于静态方法,锁定的是类的 Class
对象。这意味着该类的所有实例共享同一把锁。
示例代码:
class StaticCounter {
private static int count = 0;
public static synchronized void increment() {
count++;
}
public static int getCount() {
return count;
}
}
4.4 锁对象
可以使用任意对象作为锁。当多个线程使用同一个对象作为锁时,访问该对象的同步代码块将会互斥。
示例代码:
class Counter {
private int count = 0;
private final Object lock = new Object();
public void increment() {
synchronized (lock) { // 使用 lock 对象作为锁
count++;
}
}
public int getCount() {
return count;
}
}
4.5 死锁
死锁是指两个或多个线程在等待彼此持有的锁,导致无法继续执行的情况。防止死锁的一些策略包括:
- 资源分配顺序:确保所有线程以相同的顺序获取锁。
- 使用
tryLock()
:使用Lock
类的tryLock()
方法,可以尝试获取锁并设置超时。 - 避免嵌套锁:尽量减少在持有一个锁的情况下再去获取另一个锁。
示例代码:
class A {
synchronized void methodA(B b) {
System.out.println("Thread 1: Holding lock A...");
try { Thread.sleep(100); } catch (InterruptedException e) {}
System.out.println("Thread 1: Waiting for lock B...");
b.last();
}
synchronized void last() {
System.out.println("Inside A.last()");
}
}
class B {
synchronized void methodB(A a) {
System.out.println("Thread 2: Holding lock B...");
try { Thread.sleep(100); } catch (InterruptedException e) {}
System.out.println("Thread 2: Waiting for lock A...");
a.last();
}
synchronized void last() {
System.out.println("Inside B.last()");
}
}
// 产生死锁的示例
public class DeadlockExample {
public static void main(String[] args) {
final A a = new A();
final B b = new B();
new Thread(() -> a.methodA(b)).start();
new Thread(() -> b.methodB(a)).start();
}
}
总结
通过同步方法和同步块,我们可以有效控制线程对共享资源的访问,从而避免数据不一致的问题。在多线程编程中,死锁是一个常见问题,了解死锁的原理和防止措施也非常重要。
下一步,我们将学习 Java 中的并发工具,包括 Lock
接口、Semaphore
、CountDownLatch
和 CyclicBarrier
等,并发工具的使用方法和适用场景。