Java中的同步方法(Synchronized Method)
简介
在多线程编程中,同步是一个关键概念,用于确保多个线程安全地访问共享资源。Java提供了synchronized
关键字来实现同步机制,其中同步方法是一种常用的同步方式。本文将深入探讨Java中的同步方法,包括其基础概念、使用方法、常见实践以及最佳实践。
目录
- 基础概念
- 使用方法
- 实例方法同步
- 静态方法同步
- 常见实践
- 保护共享资源
- 避免死锁
- 最佳实践
- 最小化同步范围
- 合理选择锁对象
- 小结
- 参考资料
基础概念
在Java中,每个对象都有一个内置锁(也称为监视器锁)。当一个线程访问被synchronized
关键字修饰的方法时,它首先需要获取该方法所属对象的内置锁。如果该锁已经被其他线程持有,那么当前线程将被阻塞,直到锁可用。一旦线程获取了锁,它就可以执行该方法的代码,执行完毕后释放锁,让其他线程有机会获取锁并执行该方法。
使用方法
实例方法同步
当synchronized
关键字修饰一个实例方法时,该方法的调用需要获取调用该方法的对象的内置锁。
public class SynchronizedExample {
private int count = 0;
public synchronized void increment() {
count++;
}
public int getCount() {
return count;
}
}
在上述示例中,increment
方法被声明为synchronized
,这意味着当多个线程调用increment
方法时,只有一个线程能够获取SynchronizedExample
对象的内置锁并执行该方法,从而避免了多线程环境下对count
变量的并发访问问题。
静态方法同步
当synchronized
关键字修饰一个静态方法时,该方法的调用需要获取该类的Class
对象的内置锁。
public class StaticSynchronizedExample {
private static int count = 0;
public static synchronized void increment() {
count++;
}
public static int getCount() {
return count;
}
}
在这个例子中,increment
是一个静态同步方法。由于静态方法属于类,而不是对象实例,因此调用increment
方法时需要获取StaticSynchronizedExample.class
对象的内置锁。
常见实践
保护共享资源
同步方法的主要用途之一是保护共享资源,确保多个线程不会同时修改共享资源,从而避免数据不一致问题。
public class BankAccount {
private double balance;
public BankAccount(double initialBalance) {
this.balance = initialBalance;
}
public synchronized void deposit(double amount) {
if (amount > 0) {
balance += amount;
}
}
public synchronized void withdraw(double amount) {
if (amount > 0 && amount <= balance) {
balance -= amount;
}
}
public double getBalance() {
return balance;
}
}
在上述BankAccount
类中,deposit
和withdraw
方法都被声明为synchronized
,以保护balance
这个共享资源。
避免死锁
死锁是多线程编程中一个常见的问题,当两个或多个线程相互等待对方释放锁时就会发生死锁。通过合理使用同步方法,可以避免死锁的发生。
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 acquired lock1");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock2) {
System.out.println("Thread 1 acquired lock2");
}
}
});
Thread thread2 = new Thread(() -> {
synchronized (lock2) {
System.out.println("Thread 2 acquired lock2");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock1) {
System.out.println("Thread 2 acquired lock1");
}
}
});
thread1.start();
thread2.start();
}
}
在上述示例中,如果两个线程按照不同的顺序获取锁,就可能导致死锁。为了避免死锁,应该确保所有线程以相同的顺序获取锁。
最佳实践
最小化同步范围
尽量减少同步代码块或方法的大小,只同步需要保护的关键代码,这样可以提高并发性能。
public class MinimizeSynchronization {
private int count = 0;
public void increment() {
// 只同步关键操作
synchronized (this) {
count++;
}
// 其他不需要同步的操作
System.out.println("Incremented count to: " + count);
}
}
合理选择锁对象
选择合适的锁对象可以提高性能和代码的可读性。通常,尽量使用实例对象作为锁,而不是this
对象,这样可以避免意外的锁竞争。
public class SmartLocking {
private final Object lock = new Object();
private int count = 0;
public void increment() {
synchronized (lock) {
count++;
}
}
}
小结
同步方法是Java多线程编程中实现线程安全的重要手段。通过使用synchronized
关键字,我们可以确保多个线程在访问共享资源时的互斥性,从而避免数据不一致和其他并发问题。在实际应用中,我们需要理解同步方法的基础概念,掌握其使用方法,并遵循最佳实践来提高代码的性能和可靠性。
参考资料
- Oracle Java Documentation - Synchronized
- 《Effective Java》 by Joshua Bloch
- 《Java Concurrency in Practice》 by Brian Goetz et al.