synchronized 是 Java 中用于实现线程同步的关键字,它可以应用在方法和代码块上,用于保证多个线程之间对共享资源的安全访问,其底层原理是通过对象的锁信息和监视器来实现线程的互斥访问和同步操作。
目录
synchronized 是 Java 中用于实现线程同步的关键字,它可以应用在方法和代码块上,用于保证多个线程之间对共享资源的安全访问。每个Java对象都隐含有一把锁,这里称为Java内置锁(或者对象锁、隐式锁)。使用synchronized(syncObject)调用相当于获取syncObject的内置锁,所以可以使用内置锁对临界区代码段进行排他性保护。
synchronized有三种使用方法:同步方法、同步代码块、静态同步方法
理解synchronized之前,我们先了解下Java对象的知识,在内存中,Java对象分为三块区域:对象头、实例数据和对齐填充
对象头:hotspot虚拟机对象的对象头部分包括两类信息,一是用于存储对象的运行时数据,如哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等,这部分数据称作Mark Word
实例数据:是对象真正存储的有效信息,也就是我们在程序代码里面所定义的各种类型的字段内容,无论是从父类继承下来的还是在子类中定义的字段都不行记录下来。
对齐填充:由于hotspot虚拟机的自动内存管理系统要求对象起始地址必须是8字节的整数倍,如果对象实例数据部分没有对齐的话,就需要通过对齐填充来补全
在Java中,synchronized关键字的底层原理涉及到对象头(Object Header)和监视器(Monitor)的概念。
每个Java对象都有一个对象头,对象头包含一些用于对象管理的元数据信息,其中之一是用于实现同步的锁信息。当一个对象被synchronized关键字修饰时,对象头中的锁信息被用于实现同步操作。当线程进入一个synchronized方法或代码块时,它首先会尝试获取对象的锁。如果锁是可用的,那么当前线程会成功获取锁并继续执行同步代码并且Java会将锁的计数器加1,表示当前线程获得了锁。如果锁已经被其他线程持有,那么当前线程就会进入阻塞状态,并将线程会放置到对象的等待集(Wait Set)中,其他线程释放锁并通知它即可继续执行。这种等待和唤醒的机制由监视器来管理。当线程执行完synchronized方法或代码块中的代码后释放锁,锁计数器减1。如果计数器为0,表示当前线程完全释放了锁,其他线程可以竞争获取该锁。
监视器(Monitor)是Java中用于实现同步的基本机制,用于保护对象的互斥访问。每个Java对象都与一个监视器相关联,监视器包含了与该对象关联的锁和等待集。只有持有锁的线程才能执行同步代码,其他线程必须等待锁的释放。监视器包括以下几个主要组成部分:
这些组成部分共同构成了监视器的基本结构,实现了线程的同步和互斥访问。它们允许线程在临界区内操作共享资源时按照一定的顺序访问,并确保线程间的互斥性和协调性。监视器的实现和具体细节由JVM负责,开发人员可以使用synchronized关键字或显式地调用监视器相关的方法来实现对象的同步。
在JDK 8中,Mark Word(标记字段)中的标记锁状态信息使用不同的位来进行标记。具体的标记位包括以下几个:
标记字段的使用是为了支持JVM的锁优化技术,如偏向锁、轻量级锁和重量级锁,以提高同步操作的性能和效率。在Java中,锁状态的转换是由JVM自动处理的,根据线程的竞争情况和同步操作的结果来进行状态转换。锁状态的转换是为了提高同步操作的性能和效率,减少竞争带来的性能损失。锁状态按照如下顺序逐步增加:无锁-偏向锁-自旋锁-轻量级锁-重量级锁