Java中的synchronized:使用与锁升级机制

在Java并发编程中,synchronized关键字是实现线程同步的重要工具。它能够确保多个线程在访问共享资源时的线程安全性。随着Java版本的更新,synchronized的底层实现也在不断优化,尤其是引入了锁升级机制,显著提高了性能。本文将详细介绍synchronized的使用方法、底层原理以及锁升级机制。

目录

1.1 什么是synchronized?

1.2 synchronized的三种使用方式

1.2.1 修饰实例方法

1.2.2 修饰静态方法

1.2.3 修饰代码块

2. synchronized的底层原理

2.1 对象头与Monitor

2.2 Monitor的工作原理

3. 锁升级机制

3.1 锁的四种状态

3.2 锁升级的过程

3.2.1 偏向锁

3.2.2 轻量级锁

3.2.3 重量级锁

3.2.4 锁升级的流程图

4. synchronized的性能优化

4.1 减少锁的粒度

4.2 避免锁的嵌套

4.3 使用java.util.concurrent工具包

5. 示例代码

6. 总结


1.1 什么是synchronized

synchronized是Java中的关键字,用于实现线程同步。它可以修饰方法或代码块,确保同一时刻只有一个线程能够执行被修饰的代码。

1.2 synchronized的三种使用方式

1.2.1 修饰实例方法
public class Counter {
    private int count = 0;

    public synchronized void increment() {
        count++;
    }

    public int getCount() {
        return count;
    }
}
  • 锁对象是当前实例(this)。

  • 同一实例的多个线程会竞争同一把锁。

1.2.2 修饰静态方法
public class Counter {
    private static int count = 0;

    public static synchronized void increment() {
        count++;
    }

    public static int getCount() {
        return count;
    }
}
  • 锁对象是当前类的Class对象(Counter.class)。

  • 所有实例的线程都会竞争同一把锁。

1.2.3 修饰代码块
public class Counter {
    private int count = 0;
    private final Object lock = new Object();

    public void increment() {
        synchronized (lock) {
            count++;
        }
    }

    public int getCount() {
        return count;
    }
}
  • 锁对象是自定义的对象(lock)。

  • 可以更灵活地控制锁的范围。

2. synchronized的底层原理

2.1 对象头与Monitor

在JVM中,每个对象都有一个对象头,其中包含以下信息:

  • Mark Word:存储对象的哈希码、锁状态等。

  • Klass Pointer:指向对象的类元数据。

synchronized的实现依赖于对象头中的Mark Word和一个称为Monitor(监视器)的机制。每个Java对象都与一个Monitor关联,Monitor是线程同步的核心。

2.2 Monitor的工作原理

  • 当线程进入synchronized代码块时,它会尝试获取对象的Monitor

  • 如果Monitor未被占用,线程将成功获取锁并进入临界区。

  • 如果Monitor已被其他线程占用,当前线程会被阻塞,进入EntryList等待。

  • 当持有锁的线程释放锁时,会唤醒EntryList中的线程重新竞争锁。


3. 锁升级机制

在Java 6之前,synchronized的实现是完全基于重量级锁(依赖于操作系统的互斥锁),性能较差。为了优化性能,Java 6引入了锁升级机制,包括偏向锁、轻量级锁和重量级锁。

3.1 锁的四种状态

  1. 无锁状态:对象未被任何线程锁定。

  2. 偏向锁:适用于只有一个线程访问同步代码块的场景。

  3. 轻量级锁:适用于多个线程交替访问同步代码块的场景。

  4. 重量级锁:适用于多个线程竞争同一把锁的场景。

3.2 锁升级的过程

3.2.1 偏向锁
  • 适用场景:只有一个线程访问同步代码块。

  • 实现原理

    • 在对象头中记录线程ID。

    • 如果同一线程再次访问同步代码块,无需加锁,直接进入。

  • 优点:减少锁操作的开销。

3.2.2 轻量级锁
  • 适用场景:多个线程交替访问同步代码块,但没有竞争。

  • 实现原理

    • 使用CAS(Compare-And-Swap)操作尝试获取锁。

    • 如果成功,对象头中存储指向线程栈中锁记录的指针。

    • 如果失败,说明存在竞争,升级为重量级锁。

  • 优点:避免线程阻塞,减少上下文切换。

3.2.3 重量级锁
  • 适用场景:多个线程竞争同一把锁。

  • 实现原理

    • 使用操作系统的互斥锁(Mutex)实现。

    • 未获取锁的线程会被阻塞,进入EntryList等待。

  • 缺点:性能开销较大,涉及用户态和内核态的切换。

3.2.4 锁升级的流程图

无锁状态 -> 偏向锁 -> 轻量级锁 -> 重量级锁

4. synchronized的性能优化

4.1 减少锁的粒度

  • 尽量缩小同步代码块的范围,减少锁的持有时间。

  • 例如,使用细粒度锁(如ConcurrentHashMap中的分段锁)。

4.2 避免锁的嵌套

  • 锁嵌套容易导致死锁,应尽量避免。

4.3 使用java.util.concurrent工具包

  • 在高并发场景下,可以使用ReentrantLockReadWriteLock等更灵活的锁机制。


5. 示例代码

以下是一个使用synchronized的完整示例,展示了锁的竞争与释放:

public class SynchronizedDemo {
    private static int count = 0;
    private static final Object lock = new Object();

    public static void main(String[] args) throws InterruptedException {
        Runnable task = () -> {
            for (int i = 0; i < 1000; i++) {
                synchronized (lock) {
                    count++;
                }
            }
        };

        Thread thread1 = new Thread(task);
        Thread thread2 = new Thread(task);

        thread1.start();
        thread2.start();

        thread1.join();
        thread2.join();

        System.out.println("Final count: " + count); // 输出 2000
    }
}

6. 总结

synchronized是Java中实现线程同步的重要工具,通过锁升级机制(偏向锁、轻量级锁、重量级锁),它在保证线程安全的同时,显著提高了性能。在实际开发中,合理使用synchronized并结合其他并发工具,可以有效提升程序的并发性能。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值