深入理解 Java 中的 synchronized 方法
简介
在多线程编程的世界里,数据的一致性和线程安全是至关重要的。synchronized
关键字是 Java 提供的一种内置机制,用于确保在多线程环境下,对共享资源的访问是线程安全的。本文将聚焦于 synchronized
方法,深入探讨其基础概念、使用方法、常见实践以及最佳实践,帮助你更好地掌握多线程编程中的这一关键特性。
目录
- 基础概念
- 使用方法
- 实例方法的同步
- 静态方法的同步
- 常见实践
- 解决线程安全问题
- 实现线程间的同步协作
- 最佳实践
- 尽量缩小同步范围
- 避免死锁
- 性能考量
- 小结
- 参考资料
基础概念
synchronized
关键字用于修饰方法或代码块,以保证在同一时刻,只有一个线程能够访问被修饰的方法或代码块。当一个线程进入被 synchronized
修饰的方法或代码块时,它会自动获取对象的锁(monitor)。其他线程在该锁被释放之前,无法进入该方法或代码块。
使用方法
实例方法的同步
当 synchronized
修饰一个实例方法时,它锁的是调用该方法的对象实例。也就是说,对于同一个对象实例,在同一时刻只能有一个线程能够调用该同步实例方法。
public class SynchronizedInstanceMethodExample {
private int count = 0;
// 同步实例方法
public synchronized void increment() {
count++;
}
public int getCount() {
return count;
}
}
在上述代码中,increment
方法被声明为 synchronized
,这意味着当多个线程同时调用 increment
方法时,只有一个线程能够进入该方法执行 count++
操作,从而保证了 count
变量的线程安全。
静态方法的同步
当 synchronized
修饰一个静态方法时,它锁的是该类的 Class
对象。由于 Class
对象在 JVM 中是唯一的,所以对于该类的所有实例,同一时刻只能有一个线程能够调用该同步静态方法。
public class SynchronizedStaticMethodExample {
private static int count = 0;
// 同步静态方法
public static synchronized void increment() {
count++;
}
public static int getCount() {
return count;
}
}
在这个例子中,increment
是一个静态同步方法,锁的是 SynchronizedStaticMethodExample.class
。无论创建多少个该类的实例,在同一时刻只有一个线程可以调用 increment
方法。
常见实践
解决线程安全问题
在多线程环境下,对共享资源的并发访问可能导致数据不一致等线程安全问题。通过使用 synchronized
方法,可以有效解决这些问题。
public class ThreadSafeCounter {
private int count = 0;
public synchronized void increment() {
count++;
}
public synchronized int getCount() {
return count;
}
}
public class ThreadSafeCounterTest {
public static void main(String[] args) {
ThreadSafeCounter counter = new ThreadSafeCounter();
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
counter.increment();
}
});
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
counter.increment();
}
});
thread1.start();
thread2.start();
try {
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Final count: " + counter.getCount());
}
}
在上述代码中,ThreadSafeCounter
类的 increment
和 getCount
方法都被声明为 synchronized
,确保了在多线程环境下 count
变量的线程安全。
实现线程间的同步协作
synchronized
方法还可以用于实现线程间的同步协作。例如,通过 wait()
和 notify()
方法,可以让线程在特定条件下等待或唤醒。
public class ThreadCommunication {
private static final Object lock = new Object();
private static boolean flag = false;
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
synchronized (lock) {
while (!flag) {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("Thread 1 is notified and continues execution.");
}
});
Thread thread2 = new Thread(() -> {
synchronized (lock) {
flag = true;
lock.notify();
System.out.println("Thread 2 notifies Thread 1.");
}
});
thread1.start();
thread2.start();
}
}
在这个例子中,thread1
在 flag
为 false
时调用 lock.wait()
进入等待状态,thread2
改变 flag
的值并调用 lock.notify()
唤醒 thread1
,实现了线程间的同步协作。
最佳实践
尽量缩小同步范围
虽然 synchronized
方法能够保证线程安全,但过多的同步可能会导致性能下降。因此,应尽量缩小同步范围,只对需要保证线程安全的代码部分进行同步。
public class NarrowSynchronizationExample {
private int count = 0;
public void increment() {
// 只对关键代码进行同步
synchronized (this) {
count++;
}
}
public int getCount() {
return count;
}
}
避免死锁
死锁是多线程编程中常见的问题,当两个或多个线程相互等待对方释放锁时,就会发生死锁。为了避免死锁,应遵循以下原则: - 尽量减少锁的嵌套使用。 - 按照固定顺序获取锁。
// 错误示例,可能导致死锁
public class DeadlockExample {
private static final Object lock1 = new Object();
private static final Object lock2 = new Object();
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
synchronized (lock1) {
System.out.println("Thread 1 holds lock1.");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock2) {
System.out.println("Thread 1 holds lock2.");
}
}
});
Thread thread2 = new Thread(() -> {
synchronized (lock2) {
System.out.println("Thread 2 holds lock2.");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock1) {
System.out.println("Thread 2 holds lock1.");
}
}
});
thread1.start();
thread2.start();
}
}
// 正确示例,按照固定顺序获取锁
public class NoDeadlockExample {
private static final Object lock1 = new Object();
private static final Object lock2 = new Object();
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
synchronized (lock1) {
System.out.println("Thread 1 holds lock1.");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock2) {
System.out.println("Thread 1 holds lock2.");
}
}
});
Thread thread2 = new Thread(() -> {
synchronized (lock1) {
System.out.println("Thread 2 holds lock1.");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock2) {
System.out.println("Thread 2 holds lock2.");
}
}
});
thread1.start();
thread2.start();
}
}
性能考量
在高并发场景下,频繁使用 synchronized
方法可能会导致性能瓶颈。可以考虑使用更高级的并发工具,如 java.util.concurrent
包中的类,来替代 synchronized
方法,以提高性能和可伸缩性。
小结
synchronized
方法是 Java 多线程编程中保证线程安全和实现线程同步的重要工具。通过理解其基础概念、掌握使用方法、遵循常见实践和最佳实践,能够编写出更加健壮、高效的多线程程序。在实际应用中,需要根据具体的业务场景和性能需求,合理选择和使用 synchronized
方法。
参考资料
希望本文对你理解和使用 synchronized
方法有所帮助。如果你有任何疑问或建议,欢迎在评论区留言。