跳转至

深入理解 Java 中的 synchronized 方法

简介

在多线程编程的世界里,数据的一致性和线程安全是至关重要的。synchronized 关键字是 Java 提供的一种内置机制,用于确保在多线程环境下,对共享资源的访问是线程安全的。本文将聚焦于 synchronized 方法,深入探讨其基础概念、使用方法、常见实践以及最佳实践,帮助你更好地掌握多线程编程中的这一关键特性。

目录

  1. 基础概念
  2. 使用方法
    • 实例方法的同步
    • 静态方法的同步
  3. 常见实践
    • 解决线程安全问题
    • 实现线程间的同步协作
  4. 最佳实践
    • 尽量缩小同步范围
    • 避免死锁
    • 性能考量
  5. 小结
  6. 参考资料

基础概念

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 类的 incrementgetCount 方法都被声明为 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();
    }
}

在这个例子中,thread1flagfalse 时调用 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 方法有所帮助。如果你有任何疑问或建议,欢迎在评论区留言。