跳转至

Java中的同步方法(Synchronized Method)

简介

在多线程编程中,同步是一个关键概念,用于确保多个线程安全地访问共享资源。Java提供了synchronized关键字来实现同步机制,其中同步方法是一种常用的同步方式。本文将深入探讨Java中的同步方法,包括其基础概念、使用方法、常见实践以及最佳实践。

目录

  1. 基础概念
  2. 使用方法
    • 实例方法同步
    • 静态方法同步
  3. 常见实践
    • 保护共享资源
    • 避免死锁
  4. 最佳实践
    • 最小化同步范围
    • 合理选择锁对象
  5. 小结
  6. 参考资料

基础概念

在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类中,depositwithdraw方法都被声明为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关键字,我们可以确保多个线程在访问共享资源时的互斥性,从而避免数据不一致和其他并发问题。在实际应用中,我们需要理解同步方法的基础概念,掌握其使用方法,并遵循最佳实践来提高代码的性能和可靠性。

参考资料