一文弄懂synchronized

简述

synchronized是什么?

synchronized 关键字是一种同步锁,它可以保证在一个时刻只有一个线程可以执行某段代码。synchronized 关键字可以用在方法、代码块、静态方法和静态代码块上。

synchronized怎么用?

synchronized是Java中用于实现线程同步的关键字,它可以修饰方法或代码块。

  1. 修饰方法:当一个方法被synchronized修饰时,表示该方法是一个同步方法。同一时间只能有一个线程执行该方法,其他线程需要等待。
public synchronized void method() {
  // 这里是同步代码块
}
  1. 修饰代码块:当一个代码块被synchronized修饰时,表示该代码块是一个同步代码块。同一时间只能有一个线程进入该代码块,其他线程需要等待。
public class Example {

    public static void main(String[] args) {
        final int[] number = {1, 2, 3, 4, 5};

        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                synchronized (number) {
                    // 这里是同步代码块
                    System.out.println(number[i]);
                }
            }).start();
        }
    }
}

锁的是什么

普通同步方法>锁的是当前实力对象。
静态同步方法>锁的是当前类的Class对象。
同步方法快>锁的是synchonized括号里配置的对象。

注意事项

当使用synchronized修饰代码块时,应该尽量控制同步代码块的范围,避免锁的竞争过于频繁,以提高程序的性能。同时,锁对象的选择也很重要,应该选择合适的锁对象以避免不必要的线程等待和资源竞争。

原理

synchronized修饰代码块和方法的底层原理类似,都是通过对象头中的锁标记位来实现的。

修饰方法

进入方法:当线程调用一个被synchronized修饰的方法时,会尝试获取该方法所属对象的锁。
获取锁:如果对象的锁标记位为"unlocked"状态,则当前线程可以获取到锁,并将锁标记位设置为"locked"状态,表示该对象被当前线程锁定。
执行方法体:线程获取到锁后,会执行synchronized修饰的方法体中的代码。
释放锁:当方法执行完毕或抛出异常时,会自动释放对象的锁,将锁标记位重新设置为"unlocked"状态。

注意点

需要注意的是,synchronized修饰方法时,默认锁定的是当前对象实例(this),即当前方法所属的对象。如果是静态方法,锁定的是当前类的Class对象。

//1.静态方法 
public static synchronized void method() {
    // 该方法被当前类的Class对象锁定
    // ...
}
//2.普通方法
public synchronized void method() {
    // 该方法被当前实例对象锁定
    // ...
}

修饰代码块

进入代码块:当线程进入一个被synchronized修饰的代码块时,会尝试获取对象的锁。
获取锁:如果对象的锁标记位为"unlocked"状态,则当前线程可以获取到锁,并将锁标记位设置为"locked"状态,表示该对象被当前线程锁定。
阻塞或执行:如果对象的锁标记位为"locked"状态,表示该对象已被其他线程锁定。当前线程会被阻塞,直到锁标记位变为"unlocked"状态时才能继续执行。
释放锁:当线程执行完synchronized代码块后,会释放对象的锁,将锁标记位重新设置为"unlocked"状态,使其他线程能够获取到锁并执行。

public void method() {
    synchronized (lock) {
        // 该代码块被lock对象锁定
        // ...
    }
}

锁的高级特征

JVM在实现锁的过程中采用了多种锁的机制,包括偏向锁、轻量级锁和重量级锁,并且会根据锁竞争的情况自动进行锁升级和降级。下面是对这些锁的机制和锁升级的简要说明:

偏向锁(Bias Locking):

简介

偏向锁是一种乐观锁策略,适用于大部分情况下只有一个线程对锁进行竞争的场景。
偏向锁的目标是减少无竞争的情况下对锁的开销,提高程序的性能。
当一个线程获取到偏向锁后,JVM会将线程的标识记录在对象头中,之后该线程再次获取锁时无需进行同步操作,从而提高了程序的执行效率。

实现原理

轻量级锁(Lightweight Locking):

简介

当多个线程对同一个锁进行竞争时,JVM会将锁升级为轻量级锁。
轻量级锁使用CAS(Compare and Swap)操作来实现对锁的获取和释放,避免了线程阻塞和唤醒的开销。
如果竞争激烈,多个线程同时尝试获取锁,那么轻量级锁会膨胀为重量级锁。

实现原理

重量级锁(Heavyweight Locking):

简介

当轻量级锁膨胀失败或竞争过于激烈时,JVM会将锁升级为重量级锁。
重量级锁使用操作系统的互斥量来实现对锁的获取和释放,需要涉及线程的阻塞和唤醒。
线程在获取重量级锁时会进入阻塞状态,当锁被释放时,JVM会从阻塞的线程中选择一个进行唤醒。
锁的升级和降级是根据锁竞争的情况动态进行的,以提高程序的性能和吞吐量。当JVM检测到锁竞争较少时,会尝试将重量级锁降级为轻量级锁,以提高并发性能。反之,如果锁竞争激烈,JVM会将轻量级锁膨胀为重量级锁,以避免不必要的自旋和消耗。

实现原理

需要注意的是,锁的升级和降级过程对于开发者来说是透明的,无需手动干预。JVM会根据实际情况自动进行锁的升级和降级操作,以达到更好的性能和可伸缩性。

这些锁的机制和锁升级是JVM内部的实现细节,对于开发者来说,只需要了解它们的存在和基本原理,以正确地使用synchronized关键字来实现线程安全的同步。

优秀文章传送门:
https://zhuanlan.zhihu.com/p/571793506

你可能感兴趣的:(java,jvm)