youyichannel

志于道,据于德,依于仁,游于艺!

0%

synchronized关键字详解

概念

synchronized是Java中的一个关键字,主要作用是解决多个线程之间访问资源的同步性,它可以保证被其修饰的方法或者代码块在任意时刻只有一个线程执行。

使用

synchronized关键字的使用方式:

  • 修饰实例方法
  • 修饰静态方法
  • 修饰代码块

1)修饰实例方法

加锁的对象是当前对象实例this,进入同步代码块钱要获取当前实例对象的锁

synchronized void func() {
// 同步代码
}

2)修饰静态方法

加锁的对象是当前类的Class对象,作用于所有类实例对象。

原因:因为静态成员不属于实例对象,而是属于类。

synchronized static void func() {
// 同步代码
}

问题:静态同步方法和普通同步方法之间互斥吗?

答:不互斥,二者加锁的对象不同。

3)修饰代码块

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

  • synchronized(obj):进入同步代码块之前要获取实例对象obj的锁
  • synchronized(类.class):进入同步代码块之前要获取类的Class的锁
// 等价于 修饰实例方法
void func() {
synchronized(this) {
// 同步代码
}
}

synchronized可以修饰构造器吗?

答案:不可以,参考https://www.cnblogs.com/datamining-bio/p/13067776.html

synchronized底层原理

想要了解这部分知识,我们需要从字节码层面去看。

同步语句情况

示例代码:

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

执行:

javac SynchronizedDemo.java

javap -c -s -v -l SynchronizedDemo

从执行结果可以看出,synchronized同步语句块使用的字节码指令是monitorenter(同步代码块开始位置)和monitorexit(同步代码块结束位置)。

上述的字节码中包含了一个monitorenter和两个monitorexit指令,目的是为了保证锁在同步代码块正常执行和出现异常的情况都可以被正常释放。

执行monitorenter指令时,线程尝试获取锁,也就是获取 对象监视器monitor的持有权。

wait/notify方法也依赖于monitor对象 => 只有在同步代码块中才能调用这两个方法。

如果此时锁的计数器为0,表示该锁可以被获取,获取后将锁的计数器加一,获取锁成功。如果获取对象锁失败,当前线程需要阻塞等待,直到锁被持有锁的线程释放为止。

只有拥有对象锁的持有权的线程才可以执行monitorexit指令来释放锁。执行该指令后,锁计数器减一,表示锁释放,当计数器为0时,其他线程可以尝试获取锁。

同步方法情况

示例代码:

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

执行:

javac SynchronizedDemo.java

javap -c -s -v -l SynchronizedDemo

从字节码层面可以看出synchronized修饰的方法没有monitorentermonitorexit指令,在方法上存在了一个标识ACC_SYNCHRONIZED,该标识表明该方法是一个同步方法,此时JVM底层会执行相应的同步调用,判断如果是实例方法,JVM会尝试获取实例对象的锁;如果是静态方法,JVM会尝试获取当前类的Class对象的锁。

JDK6及以上版本对于synchronized的优化

参考:https://www.cnblogs.com/wuqinglong/p/9945618.html

JDK6对锁的实现引入了像偏向锁、轻量级锁、自旋锁、适应性自旋锁、锁消除、锁粗化等技术来优化锁,减少锁操作的开销。

此版本之后,锁的状态变成了四种:

  • 无锁
  • 偏向锁
  • 轻量级锁
  • 重量级锁

从上至下竞争逐渐激烈。锁可以升级不可降级,这种策略是为了提高获得锁和释放锁的效率。

synchronized和volatile的关系

在Java中,这两个关键字是互补的关系。

  • volatile关键字是线程同步的轻量级实现,因此volatile的性能是要优于synchronized关键字的
  • volatile作用于变量,synchronized作用于方法和代码块
  • volatile能够保证数据的可见性,但是不能保证数据的原子性;synchronized二者都能保证
  • volatile主要解决变量在多个线程之间的可见性;synchronized主要解决多个线程之间访问资源的同步性

参考文章

  • https://blog.csdn.net/TZ845195485/article/details/109211725
  • https://blog.csdn.net/TZ845195485/article/details/109398066
  • https://javaguide.cn/java/concurrent/java-concurrent-questions-02.html
  • https://www.cnblogs.com/wuqinglong/p/9945618.html