跳到主要内容

2 篇博文 含有标签「Synchronized」

查看所有标签

Synchronized

  1. 是 Java 内置的关键字,用于提供对象或方法级别的同步机制
  2. 它能够确保在同一时刻只有一个线程可以执行 Synchronized 修饰的方法或代码块
  3. 不需要手动释放锁,当 Synchronized 方法或者代码块执行完毕后,锁自动释放
  4. 锁的获取和释放是隐式的 ,为开发者提供了便捷的线程同步机制
  5. 不支持公平锁、可中断锁等高级功能

ReentrantLock

  1. 属于java.util.concurrent.locks包下的一个类,提供了比 Synchronized 更广泛的锁操作
  2. 必须手动声明锁的获取和释放(通过lock()unlock()方法)
  3. 支持许多高级功能,如可中断的锁等待、公平锁、锁续租等
  4. 支持可中断的锁等待(`lockInterruptibly())
  5. 提供了条件变量(Condition)支持,允许分开管理线程间的通信
ReentrantLock lock = new ReentrantLock(); // 默认非公平锁
ReentrantLock lock = new ReentrantLock(true); // 启用公平锁
SynchronizedReentrantLock阅读需 1 分钟

Volitale关键字

在 Java 中,volitale关键字可以保证变量的可见性,当我们将变量声明为volitale时,这就是告诉 JVM,这个变量是共享且不稳定的,每次使用它都要到主存中去取

20240307215653

volitale关键字能保证数据的可见性,但不能保证数据的原子性

SynChronized关键字

synchronized关键字解决的是多个线程之间访问资源的同步性,可以保证被它修饰的方法或者代码块在任何时候只能有一个线程执行

使用方式

synchronized的使用方式有下面三种

修饰实例方法(锁当前对象实例)

给当前对象实例加锁,进入同步代码前,要先获取当前实例对象的锁

synchronized void method() {
//业务代码
}

修饰静态方法(锁当前类)

给当前类加锁,会作用于类的所有对象实例,进入同步代码前,要先获取当前class的锁

这是因为静态方法不属于任何一个实例对象,是属于类的,被类的所有实例共享

synchronized static void method() {
//业务代码
}

静态synchronized方法和非静态synchronized方法之间的调用互斥么?不互斥!

如果一个线程 A 调用一个实例对象的非静态synchronized方法,而线程 B 需要调用这个实例对象所属类的静态synchronized方法,是允许的,不会发生互斥现象,因为访问静态synchronized方法占用的锁是当前类的锁,而访问非静态synchronized方法占用的锁是当前实例对象锁

修饰代码块(锁指定类/对象)

对括号里指定的对象/类加锁:

  • synchronized(object)表示进入同步代码库前要获得给定对象的锁
  • synchronized(类.class)表示进入同步代码前要获得给定 Class 的锁
synchronized(this) {
//业务代码
}

总结:

  • synchronized关键字加到static静态方法和synchronized(class)代码块上都是是给Class类上锁
  • synchronized关键字加到实例方法上是给对象实例上锁
  • 尽量不要使用synchronized(String a)因为 JVM 中,字符串常量池具有缓存功能

底层原理

synchronized关键字底层原理属于 JVM 层面的东西

同步代码块例子:

public class SynchronizedDemo {
public void method() {
synchronized (this) {
System.out.println("synchronized 代码块");
}
}
}

通过JDK自带的javap命令查看SynchronizedDemo类的相关字节码信息:首先切换到类的对应目录执行javac SynchronizedDemo.java命令生成编译后的.class文件,然后执行javap -c -s -v -l SynchronizedDemo.class

20240307223545

从上面我们可以看出:synchronized同步语句块的实现使用的是monitorentermonitorexit指令,其中monitorenter指令指向同步代码块的开始位置,monitorexit指令则指明同步代码块的结束位置。上面的字节码中包含一个monitorenter指令以及两个 monitorexit指令,这是为了保证锁在同步代码块代码正常执行以及出现异常的这两种情况下都能被正确释放

在执行monitorenter时,会尝试获取对象的锁,如果锁的计数器为 0 则表示锁可以被获取,获取后将锁计数器设为 1 也就是加 1

20240307224012

对象锁的的拥有者线程才可以执行monitorexit指令来释放锁。在执行monitorexit指令后,将锁计数器设为 0,表明锁被释放,其他线程可以尝试获取锁

20240307224358

如果获取对象锁失败,那当前线程就要阻塞等待,直到锁被另外一个线程释放为止

修饰方法例子:

public class SynchronizedDemo2 {
public synchronized void method() {
System.out.println("synchronized 方法");
}
}

20240307224845

synchronized修饰的方法并没有monitorenter指令和monitorexit指令,取得代之的确实是ACC_SYNCHRONIZED标识,该标识指明了该方法是一个同步方法。JVM通过该ACC_SYNCHRONIZED访问标志来辨别一个方法是否声明为同步方法,从而执行相应的同步调用。如果是实例方法,JVM会尝试获取实例对象的锁。如果是静态方法,JVM会尝试获取当前class的锁

volatile和synchronized的区别

  • volatile关键字是线程同步的轻量级实现,所以volatile性能肯定比synchronized关键字要好,但是volatile关键字只能用于变量而synchronized关键字可以修饰方法以及代码块
  • volatile关键字能保证数据的可见性,但不能保证数据的原子性,synchronized关键字两者都能保证
  • volatile关键字主要用于解决变量在多个线程之间的可见性,而synchronized关键字解决的是多个线程之间访问资源的同步性
VolitaleSynchronized并发阅读需 4 分钟