Java Synchronize 深度解析
简介
在多线程编程中,线程安全是一个至关重要的问题。Java 中的 synchronized
关键字为我们提供了一种简单而有效的方式来实现线程同步,确保在同一时间只有一个线程可以访问被同步的代码块或方法,从而避免数据竞争和不一致的问题。本文将深入探讨 synchronized
的基础概念、使用方法、常见实践以及最佳实践,帮助读者更好地掌握这一重要的同步机制。
目录
- 基础概念
- 使用方法
- 同步方法
- 同步代码块
- 常见实践
- 保护共享资源
- 实现线程安全的单例模式
- 最佳实践
- 尽量缩小同步范围
- 避免死锁
- 小结
- 参考资料
基础概念
synchronized
是 Java 中的一个关键字,用于实现线程同步。当一个线程访问被 synchronized
修饰的代码块或方法时,它会首先尝试获取对象的锁。如果锁没有被其他线程持有,该线程将获得锁并执行同步代码;如果锁已经被其他线程持有,该线程将被阻塞,直到锁被释放。
在 Java 中,每个对象都有一个关联的锁,也称为监视器锁。当一个线程进入 synchronized
代码块或方法时,它会自动获取该对象的锁;当线程退出时,它会自动释放锁。这种机制确保了同一时间只有一个线程可以执行被同步的代码,从而保证了线程安全。
使用方法
同步方法
同步方法是指被 synchronized
关键字修饰的方法。当一个线程调用同步方法时,它会自动获取该对象的锁。示例代码如下:
public class SynchronizedMethodExample {
private int count = 0;
// 同步方法
public synchronized void increment() {
count++;
}
public int getCount() {
return count;
}
public static void main(String[] args) throws InterruptedException {
SynchronizedMethodExample example = new SynchronizedMethodExample();
Thread t1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
example.increment();
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
example.increment();
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("Count: " + example.getCount());
}
}
在上述代码中,increment
方法被 synchronized
修饰,因此同一时间只有一个线程可以调用该方法,从而保证了 count
变量的线程安全。
同步代码块
同步代码块是指使用 synchronized
关键字修饰的代码块。同步代码块可以指定要锁定的对象,从而更加灵活地控制同步范围。示例代码如下:
public class SynchronizedBlockExample {
private int count = 0;
private final Object lock = new Object();
public void increment() {
// 同步代码块
synchronized (lock) {
count++;
}
}
public int getCount() {
return count;
}
public static void main(String[] args) throws InterruptedException {
SynchronizedBlockExample example = new SynchronizedBlockExample();
Thread t1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
example.increment();
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
example.increment();
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("Count: " + example.getCount());
}
}
在上述代码中,increment
方法中的同步代码块使用 lock
对象作为锁,确保同一时间只有一个线程可以执行 count++
操作。
常见实践
保护共享资源
在多线程环境中,多个线程可能会同时访问和修改共享资源,从而导致数据不一致的问题。使用 synchronized
可以保护共享资源,确保同一时间只有一个线程可以访问和修改它。示例代码如下:
public class SharedResourceExample {
private static int sharedResource = 0;
public static synchronized void increment() {
sharedResource++;
}
public static int getSharedResource() {
return sharedResource;
}
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
increment();
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
increment();
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("Shared Resource: " + getSharedResource());
}
}
在上述代码中,increment
方法被 synchronized
修饰,确保同一时间只有一个线程可以修改 sharedResource
变量。
实现线程安全的单例模式
单例模式是一种常见的设计模式,用于确保一个类只有一个实例。使用 synchronized
可以实现线程安全的单例模式。示例代码如下:
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
在上述代码中,getInstance
方法被 synchronized
修饰,确保同一时间只有一个线程可以创建单例实例。
最佳实践
尽量缩小同步范围
同步操作会带来一定的性能开销,因此应该尽量缩小同步范围,只对需要同步的代码进行同步。示例代码如下:
public class NarrowSynchronizationExample {
private int count = 0;
private final Object lock = new Object();
public void increment() {
// 非同步操作
// ...
// 同步代码块,只对需要同步的代码进行同步
synchronized (lock) {
count++;
}
// 非同步操作
// ...
}
}
在上述代码中,只对 count++
操作进行了同步,其他非同步操作可以并行执行,从而提高了性能。
避免死锁
死锁是指两个或多个线程在执行过程中,因争夺资源而造成的一种互相等待的现象,导致这些线程都无法继续执行。为了避免死锁,应该遵循以下原则: - 按顺序获取锁:确保所有线程按照相同的顺序获取锁。 - 避免嵌套锁:尽量避免在一个同步代码块中嵌套另一个同步代码块。
示例代码如下:
public class DeadlockAvoidanceExample {
private final Object lock1 = new Object();
private final Object lock2 = new Object();
public void method1() {
synchronized (lock1) {
// 执行一些操作
synchronized (lock2) {
// 执行一些操作
}
}
}
public void method2() {
// 按相同的顺序获取锁
synchronized (lock1) {
// 执行一些操作
synchronized (lock2) {
// 执行一些操作
}
}
}
}
在上述代码中,method1
和 method2
方法都按照相同的顺序获取锁,从而避免了死锁的发生。
小结
本文深入探讨了 Java 中的 synchronized
关键字,包括其基础概念、使用方法、常见实践以及最佳实践。synchronized
是一种简单而有效的线程同步机制,可以确保在同一时间只有一个线程可以访问被同步的代码块或方法,从而保证线程安全。在使用 synchronized
时,应该尽量缩小同步范围,避免死锁,以提高程序的性能和稳定性。
参考资料
- 《Effective Java》
- 《Java 核心技术》
- Oracle Java 官方文档