跳转至

Java Synchronize 深度解析

简介

在多线程编程中,线程安全是一个至关重要的问题。Java 中的 synchronized 关键字为我们提供了一种简单而有效的方式来实现线程同步,确保在同一时间只有一个线程可以访问被同步的代码块或方法,从而避免数据竞争和不一致的问题。本文将深入探讨 synchronized 的基础概念、使用方法、常见实践以及最佳实践,帮助读者更好地掌握这一重要的同步机制。

目录

  1. 基础概念
  2. 使用方法
    • 同步方法
    • 同步代码块
  3. 常见实践
    • 保护共享资源
    • 实现线程安全的单例模式
  4. 最佳实践
    • 尽量缩小同步范围
    • 避免死锁
  5. 小结
  6. 参考资料

基础概念

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) {
                // 执行一些操作
            }
        }
    }
}

在上述代码中,method1method2 方法都按照相同的顺序获取锁,从而避免了死锁的发生。

小结

本文深入探讨了 Java 中的 synchronized 关键字,包括其基础概念、使用方法、常见实践以及最佳实践。synchronized 是一种简单而有效的线程同步机制,可以确保在同一时间只有一个线程可以访问被同步的代码块或方法,从而保证线程安全。在使用 synchronized 时,应该尽量缩小同步范围,避免死锁,以提高程序的性能和稳定性。

参考资料

  • 《Effective Java》
  • 《Java 核心技术》
  • Oracle Java 官方文档