java多线程(八)深入解析Java中的synchronized关键字:用法、原理与最佳实践

synchronized 详细介绍

synchronized是Java语言中的一个关键字,代表同步锁,用于解决多线程环境下的线程安全问题。

它可以确保在同一时刻,只有一个线程可以执行被synchronized修饰的代码块或方法,从而避免多线程同时访问共享资源时可能出现的数据不一致问题。

一、synchronized的作用

  1. 原子性:保证一个操作或多个操作要么全部执行,要么全部不执行,中间不会被其他线程打断。

  2. 可见性:确保线程在释放锁之前对共享变量的修改对其他线程可见。

  3. 有序性:保证程序按照代码的顺序执行,防止指令重排导致的问题。

二、synchronized的用法

synchronized主要有三种用法:

  1. 修饰实例方法

    • 在方法声明时使用,放在范围操作符(如public)之后,返回类型声明(如void)之前。此时,线程获得的是成员锁,即一次只能有一个线程进入该方法。

    • 示例:public synchronized void method() { ... }

  2. 修饰静态方法

    • 作用于类的所有对象实例,进入同步代码前要获得当前类的Class对象的锁。

    • 示例:public static synchronized void staticMethod() { ... }

  3. 修饰代码块

    • 可以指定加锁对象,对给定对象或类加锁。

    • 使用方式:synchronized(lockObject) { ... },其中lockObject可以是任意对象或类的Class对象。

    • 示例:synchronized(this) { ... } 或 synchronized(SomeClass.class) { ... }

三、synchronized的实现原理

  1. Monitor机制

    • 在Java虚拟机(HotSpot)中,每个对象都与一个监视器(Monitor)关联,synchronized通过Monitor来实现同步。

    • 当线程进入synchronized代码块时,会尝试获取对象的Monitor锁;获取成功后,线程进入同步代码块执行;执行完毕后,线程释放Monitor锁。

  2. 锁升级过程

    • Java SE 1.6及以后版本中,synchronized引入了锁升级机制,包括无锁状态、偏向锁状态、轻量级锁状态和重量级锁状态。

    • 锁升级的目的是为了减少线程获得锁和释放锁带来的性能消耗,提高程序的并发性能。

四、注意事项

  1. 避免死锁:在多线程环境下使用synchronized时,要注意避免死锁的发生。例如,两个线程分别持有对方需要的锁,并等待对方释放锁,从而导致双方都无法继续执行。

  2. 性能考虑:虽然synchronized提供了线程安全的保证,但在高并发场景下,其性能可能会受到影响。因此,在性能要求较高的场景下,可以考虑使用其他并发工具类(如ReentrantLock)来替代synchronized。

  3. 锁的粒度:锁的粒度越细,系统的并发性能就越好。因此,在使用synchronized时,应尽量避免对整个对象加锁,而是只对需要同步的代码块加锁。

示例

示例一:synchronized修饰实例方法

假设有一个银行账户类BankAccount,其中包含存款和取款的方法,我们需要确保这些方法在多线程环境下是线程安全的。

public class BankAccount {
    private double balance;

    public BankAccount(double initialBalance) {
        this.balance = initialBalance;
    }

    // synchronized修饰实例方法,确保同一时间只有一个线程能执行该方法
    public synchronized void deposit(double amount) {
        if (amount > 0) {
            balance += amount;
            System.out.println(Thread.currentThread().getName() + " Deposited: " + amount);
        }
    }

    // synchronized修饰另一个实例方法,同样确保线程安全
    public synchronized void withdraw(double amount) {
        if (balance >= amount && amount > 0) {
            balance -= amount;
            System.out.println(Thread.currentThread().getName() + " Withdrew: " + amount);
        } else {
            System.out.println("Insufficient funds");
        }
    }

    // 假设还有其他非同步的方法...
}

在这个例子中,depositwithdraw方法都被synchronized修饰,这意味着当一个线程正在执行其中一个方法时,其他线程将无法执行这两个方法中的任何一个,直到当前线程执行完毕并释放锁。这里的锁是当前对象的实例锁。

示例二:synchronized修饰静态方法

考虑一个静态工具类Counter,它用于统计某个事件的次数。

public class Counter {
    private static int count = 0;

    // synchronized修饰静态方法,确保同一时间只有一个线程能执行该方法
    public static synchronized void increment() {
        count++;
    }

    public static synchronized int getCount() {
        return count;
    }
}

在这个例子中,incrementgetCount方法都被synchronized修饰,但它们修饰的是静态方法。

这意味着锁是类锁(Class Lock),而不是实例锁。因此,当一个线程正在执行这两个方法中的任何一个时,其他线程将无法执行这两个方法中的任何一个,直到当前线程执行完毕并释放锁。这确保了count变量的线程安全性。

示例三:synchronized修饰代码块

有时候,我们可能只需要同步方法中的一小部分代码,而不是整个方法。这时,可以使用synchronized修饰代码块。

public class DataStructure {
    private final Object lock = new Object(); // 专门的锁对象
    private int data = 0;

    public void updateData(int newData) {
        // 只同步需要同步的代码块
        synchronized (lock) {
            // 假设这里有一些复杂的逻辑需要同步
            data = newData;
            // ...
        }

        // 其他不需要同步的代码
        System.out.println("Data updated by " + Thread.currentThread().getName());
    }
}

在这个例子中,updateData方法内部使用synchronized修饰了一个代码块,并使用了一个专门的锁对象lock。这意味着只有持有lock锁的线程才能执行该代码块中的代码。这种方式比整个方法同步更高效,因为它减少了锁的持有时间,从而减少了线程等待锁的时间。

总结

  • synchronized修饰实例方法:锁是当前对象的实例锁。

  • synchronized修饰静态方法:锁是当前类的类锁。

  • synchronized修饰代码块:可以指定锁对象,更灵活地控制同步范围。

在使用synchronized时,需要注意避免死锁、减少锁的粒度以提高性能等问题。

这块实际操作过,还是没理解。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值