Java 中 synchronized 的使用方式和锁升级
目录
- 一、synchronized 的使用方式
- (一)修饰普通方法
- (二)修饰静态方法
- (三)修饰代码块
- 二、synchronized 的锁升级
- (一)无锁
- (二)偏向锁
- (三)轻量级锁
- (四)重量级锁
在 Java 并发编程中,synchronized
是一个非常重要的关键字,用于实现线程同步,保证在同一时刻只有一个线程可以访问被同步的代码块或方法,从而避免多线程带来的数据不一致等问题。同时,Java 虚拟机(JVM)为了提高ynchronized
的性能,引入了锁升级机制。下面我们就来详细介绍ynchronized
的使用和锁升级过程。
一、synchronized 的使用方式
(一)修饰普通方法
当synchronized
修饰一个普通方法时,锁对象是当前对象(this
)。也就是说,当一个线程进入www.devze.com该方法时,会自动获取当前对象的锁,其他线程想要进入该方法,必须等待当前线程释放锁。例如:
public class SynchronizedExample { public synchronized void synchronizedMethod() { // 线程同步的代码逻辑 System.out.println("线程 " + Thread.currentThread().getName() + " 进入同步方法"); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("线程 " + Thread.currentThread().getName() + " 退出同步方法"); } }
在上述代码中,synchronizedMethod
是一个同步方法,多个线程同时调用该方法时,会依次排队执行,保证了方法内代码的线程安全性。
(二)修饰静态方编程客栈法
当s编程客栈ynchronized
修饰静态方法时,锁对象是该类的Class
对象。因为静态方法属于类级别,不依赖于具体的对象实例,所以使用类的Class
对象作为锁。示例如下:
public class StaticSynchronizedExample { public static synchronized void staticSynchronizedMethod() { System.out.println("线程 " + Thread.currentThread().getName() + " 进入静态同步方法"); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("线程 " + Thread.currentThread().getName() + " 退出静态同步方法"); } }
多个线程调用staticSynchronizedMethod
时,会对类的Class
对象进行加锁,从而实现线程同步。
(三)修饰代码块
synchronized
还可以修饰代码块,此时需要显式指定锁对象。锁对象可以是任意对象,只要保证在需要同步的代码块中使用相同的锁对象即可。比如:
public class blockSynchronizedExample { private Object lock = new Object(); public void blockSynchronized() { synchronized (lock) { System.out.println("线程 " + Thread.currentThread().getName() + " 进入同步代码块"); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(jQXsLqau); } System.out.println("线程 " + Thread.currentThread().getName() + " 退出同步代码块"); } } }
在这个例子中,lock
对象作为同步代码块的锁,多个线程访问blockSynchronized
方法时,会竞争lock
对象的锁。
二、synchronized 的锁升级
为了提高synchronized
的性能,JVM 引入了锁升级机制,从无锁状态开始,根据竞争情况逐步升级为偏向锁、轻量级锁和重量级锁。
(一)无锁
无锁状态是最基本的状态,当没有线程竞争锁时,对象处于无锁状态。此时,线程可以直接访问被synchronized
修饰的代码,无需进行任何加锁操作,因此性能最高。
(二)偏向锁
当一个线程首次访问被synchronized
修饰的代码时,JVM 会将对象头中的锁标志位设置为偏向锁模式,并将该线程的 ID 记录在对象头中。此后,当该线程再次访问同一对象的同步代码时,无需进行额外的加锁操作,直接进入同步代码块,因为 JVM 认为该线程很可能会再次访问。偏向锁的存在减少了无竞争情况下的锁开销。
(三)轻量级锁
当有第二个线程试图访问同一个对象的同步代码时,偏向锁会升级为轻量级锁。JVM 会在当前线程的栈帧中创建一个锁记录(Lock Record),并将对象头中的 Mark Word 复制到锁编程客栈记录中,然后尝试使用 CAS(Compare - And - Swap,比较并交换)操作将对象头的 Mark Word 替换为指向锁记录的指针。如果 CAS 操作成功,当前线程就获得了轻量级锁,可以进入同步代码块;如果失败,说明存在竞争,轻量级锁会升级为重量级锁。
(四)重量级锁
当轻量级锁竞争失败,即多个线程同时竞争锁时,会升级为重量级锁。此时,JVM 会使用操作系统的互斥量(Mutex)来实现锁机制,线程会进入阻塞状态,等待锁的释放。重量级锁的性能相对较低,因为线程的阻塞和唤醒需要操作系统的干预,会带来较大的开销。
锁升级的过程是 JVM 根据实际的线程竞争情况动态调整的,目的是在保证线程安全性的同时,尽可能提高synchronized
的性能。
总之,synchronized
是 Java 并发编程中实现线程同步的重要手段,理解其使用方式和锁升级机制,对于编写高效、安全的多线程程序具有重要意义。在实际开发中,我们应该根据具体的场景合理使用synchronized
,并注意锁的粒度和性能优化,以充分发挥多线程的优势。
到此这篇关于Java 中 synchronized 的使用和锁升级的文章就介绍到这了,更多相关Java synchronized 使用内容请搜索编程客栈(www.devze.com)以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程客栈(www.devze.com)!
精彩评论