深入理解Java中synchronized三种使用方式:助您写出线程安全的代码

简介: `synchronized` 是 Java 中的关键字,用于实现线程同步,确保多个线程互斥访问共享资源。它通过内置的监视器锁机制,防止多个线程同时执行被 `synchronized` 修饰的方法或代码块。`synchronized` 可以修饰非静态方法、静态方法和代码块,分别锁定实例对象、类对象或指定的对象。其底层原理基于 JVM 的指令和对象的监视器,JDK 1.6 后引入了偏向锁、轻量级锁等优化措施,提高了性能。

添加图片注释,不超过 140 字(可选)


一、概念

synchronized 是一种内置的 Java 关键字,它用于实现线程的同步。当一个线程进入synchronized块或方法时,它获得了锁,这会阻止其他线程同时进入相同的synchronized块或方法,从而确保了共享资源的互斥访问。

synchronized 是 Java 中用于实现线程同步的关键字。它提供了一种独占锁的机制,用于确保多个线程之间的互斥访问共享资源。以下是关于synchronized的更详细解释:

使用方法:

synchronized关键字在Java中有三种主要的使用方式:


添加图片注释,不超过 140 字(可选)

1.修饰非静态方法(锁class的同一实例的此方法)

当您在实例的非静态方法上使用synchronized关键字时,它会将该方法变成同步方法,相当于对当前实例对象(this)加锁,this作为对象监视器。这意味着只有一个线程可以同时执行该实例方法,以确保对该实例的互斥访问。 当前类会创建多个实例对象,synchronized独立的控制每个实例对象的同步。

public synchronized void synchronizedInstanceMethod() {     // 同步的代码块 }

2. 修饰静态方法(锁class的所有实例的此方法)

在静态方法上使用synchronized关键字时,它会将该方法变为同步静态方法,相当于对当前类的Class对象加锁,当前类的Class对象作为对象监视器。这意味着只有一个线程可以同时执行该静态方法,以确保对该类的互斥访问。 当前类会创建多个实例对象,所以实例对应同一个静态方法,所以synchronized控制所以实例对象的同步。锁定的是类的 Class 对象,因此它会阻止不同实例以及静态方法之间的并发执行,因为它们共享相同的 Class 对象。

public static synchronized void synchronizedStaticMethod() {     // 同步的代码块 }

3.修饰代码块(锁定特定的对象)

可以使用synchronized关键字来创建同步代码块,这样可以指定要加锁的对象,括号中括起来的对象就是对象监视器。这允许更细粒度的控制,可以选择对某个特定对象进行同步,而不是整个方法或类。

方式一:

当你使用 synchronized(class) 时,你锁定的是整个类的对象,而不是实例对象。这意味着无论多少实例对象存在,它们都会竞争同一个锁。

Object lock = new Object(); synchronized (lock) {     // 同步的代码块 }

方式二:

当你使用 synchronized(this) 时,你锁定的是当前实例对象(this)。这意味着同一实例的不同方法调用会相互排斥,但不同实例之间的方法调用不会相互排斥。

synchronized (this) {     // 同步的代码块 }


二、实现原理

1、实现原理

synchronized底层原理是基于JVM的指令和对象的监视器(monitor)来实现的。synchronized可以修饰方法或者代码块,用来保证线程的同步和安全。

当一个线程要执行一个被synchronized修饰的方法或代码块时,它需要先获取该方法或代码块所属对象的监视器。如果获取成功,那么该线程就可以执行同步代码,并且监视器的计数器加一。如果获取失败,那么该线程就会阻塞,直到监视器被释放。

当一个线程执行完同步代码后,它会释放监视器,并且监视器的计数器减一。如果计数器为零,那么说明没有线程持有该监视器,其他线程就可以竞争获取该监视器。

synchronized修饰方法时,在字节码层面会有一个ACC_SYNCHRONIZED标志,用来表示该方法是同步的。synchronized修饰代码块时,在字节码层面会有monitorenter和monitorexit两个指令,分别用来进入和退出监视器。

synchronized 的底层实现原理可以概括为以下几点:

  • synchronized 通过监视器锁来实现线程同步。
  • 每个 Java 对象都有一个监视器锁。
  • 线程在获取了对象的监视器锁后,可以执行被修饰的代码。
  • 线程在释放了对象的监视器锁后,其他线程可以尝试获取监视器锁。

2、JDK底层的优化

synchronized在JDK1.6之后进行了优化,引入了偏向锁,轻量级锁,自旋锁等概念,用来提高性能和减少阻塞开销。以下是一些常见的优化详细说明:

  1. 偏向锁:JDK引入了偏向锁,它会将锁定的对象与线程相关联,当一个线程获得锁时,它会标记对象为已偏向该线程,以后再次进入同步块时,不需要竞争锁,而是直接获得。这对于减少无竞争情况下的锁开销非常有用。
  2. 轻量级锁:在低竞争情况下,JDK使用轻量级锁来减小锁开销。轻量级锁采用自旋方式来等待锁的释放,而不是进入阻塞状态。
  3. 自旋锁:当轻量级锁尝试获取锁失败时,JDK可以选择使用自旋锁。自旋锁不会使线程进入阻塞状态,而是一直尝试获取锁,通常在短时间内完成。这对于低竞争锁非常有用。
  4. 适应性自旋:JDK中的锁可以根据历史性能数据来调整自旋等待的次数,以达到更好的性能。

这些优化措施有助于提高synchronized的性能,使其在不同的竞争场景中更加高效。但请注意,优化是基于JVM和硬件平台的,因此在不同的环境中表现可能会有所不同。

三、相关题目

问题1:synchronized 是什么?它的作用是什么?

答案:synchronized 是Java中的关键字,用于实现多线程同步。它的主要作用是确保在同一时刻只有一个线程可以访问被synchronized修饰的代码块或方法,以避免多线程之间的竞态条件和数据不一致问题。

问题2:synchronized 有哪些用法?

答案:synchronized 可以用于以下用法:

  • 同步非静态实例方法:synchronized 修饰非静态方法,锁定的是实例对象。
  • 同步静态方法:synchronized 修饰静态方法,锁定的是类对象。
  • 同步代码块:synchronized 修饰代码块,可以手动指定锁对象,灵活控制同步范围。

问题3:什么是锁对象?

答案:锁对象是在synchronized代码块或方法中用于实现同步的对象。对于同步实例方法,锁对象是实例对象本身(this),而对于同步静态方法,锁对象是类的Class对象。对于同步代码块,你可以手动指定锁对象。

问题4:什么是重入锁?

答案:重入锁是指当一个线程持有锁时,它可以多次进入被锁定的代码块或方法而不会被阻塞。Java中的synchronized是可重入锁的一个例子,这意味着同一线程可以多次获取同一个锁,而不会造成死锁。

问题5:什么是偏向锁?

答案:偏向锁是一种优化,用于减少锁竞争的开销。它允许线程在没有竞争的情况下快速获得锁,而不必像重量级锁那样进入阻塞状态。只有在多线程竞争锁时,偏向锁才会升级为重量级锁。

问题6:什么是自旋锁?

答案:自旋锁是一种轻量级锁,它在尝试获取锁时不会立即阻塞线程,而是会在循环中不断尝试获取锁。这可以减少线程阻塞和恢复的开销,但如果锁竞争激烈,自旋锁可能会浪费大量CPU时间。

问题7:什么是锁粗化?

答案:锁粗化是一种优化,它将多个连续的synchronized块合并成一个更大的同步块,减少了锁操作的开销。这可以避免频繁地获取和释放锁,提高性能。

问题8:什么是锁消除?

答案:锁消除是一种优化,它通过静态分析来检测某些锁是不必要的,然后将其删除,以提高程序的性能。这通常发生在编译器级别。

问题9:synchronized 与 volatile 有什么区别?

答案:synchronized 用于实现多线程同步,确保线程之间的可见性和原子性。volatile 用于确保变量的可见性,但不能实现原子性。synchronized 具有更广泛的用途,而 volatile 主要用于标记变量以确保可见性。


目录
相关文章
|
2天前
|
SQL Java 数据库连接
如何在 Java 代码中使用 JSqlParser 解析复杂的 SQL 语句?
大家好,我是 V 哥。JSqlParser 是一个用于解析 SQL 语句的 Java 库,可将 SQL 解析为 Java 对象树,支持多种 SQL 类型(如 `SELECT`、`INSERT` 等)。它适用于 SQL 分析、修改、生成和验证等场景。通过 Maven 或 Gradle 安装后,可以方便地在 Java 代码中使用。
81 11
|
10天前
|
监控 Java
java异步判断线程池所有任务是否执行完
通过上述步骤,您可以在Java中实现异步判断线程池所有任务是否执行完毕。这种方法使用了 `CompletionService`来监控任务的完成情况,并通过一个独立线程异步检查所有任务的执行状态。这种设计不仅简洁高效,还能确保在大量任务处理时程序的稳定性和可维护性。希望本文能为您的开发工作提供实用的指导和帮助。
56 17
|
21天前
|
Java
Java—多线程实现生产消费者
本文介绍了多线程实现生产消费者模式的三个版本。Version1包含四个类:`Producer`(生产者)、`Consumer`(消费者)、`Resource`(公共资源)和`TestMain`(测试类)。通过`synchronized`和`wait/notify`机制控制线程同步,但存在多个生产者或消费者时可能出现多次生产和消费的问题。 Version2将`if`改为`while`,解决了多次生产和消费的问题,但仍可能因`notify()`随机唤醒线程而导致死锁。因此,引入了`notifyAll()`来唤醒所有等待线程,但这会带来性能问题。
Java—多线程实现生产消费者
|
6天前
|
JSON Java 数据挖掘
利用 Java 代码获取淘宝关键字 API 接口
在数字化商业时代,精准把握市场动态与消费者需求是企业成功的关键。淘宝作为中国最大的电商平台之一,其海量数据中蕴含丰富的商业洞察。本文介绍如何通过Java代码高效、合规地获取淘宝关键字API接口数据,帮助商家优化产品布局、制定营销策略。主要内容包括: 1. **淘宝关键字API的价值**:洞察用户需求、优化产品标题与详情、制定营销策略。 2. **获取API接口的步骤**:注册账号、申请权限、搭建Java开发环境、编写调用代码、解析响应数据。 3. **注意事项**:遵守法律法规与平台规则,处理API调用限制。 通过这些步骤,商家可以在激烈的市场竞争中脱颖而出。
|
6天前
|
缓存 安全 算法
Java 多线程 面试题
Java 多线程 相关基础面试题
|
23天前
|
安全 Java Kotlin
Java多线程——synchronized、volatile 保障可见性
Java多线程中,`synchronized` 和 `volatile` 关键字用于保障可见性。`synchronized` 保证原子性、可见性和有序性,通过锁机制确保线程安全;`volatile` 仅保证可见性和有序性,不保证原子性。代码示例展示了如何使用 `synchronized` 和 `volatile` 解决主线程无法感知子线程修改共享变量的问题。总结:`volatile` 确保不同线程对共享变量操作的可见性,使一个线程修改后,其他线程能立即看到最新值。
|
23天前
|
消息中间件 缓存 安全
Java多线程是什么
Java多线程简介:本文介绍了Java中常见的线程池类型,包括`newCachedThreadPool`(适用于短期异步任务)、`newFixedThreadPool`(适用于固定数量的长期任务)、`newScheduledThreadPool`(支持定时和周期性任务)以及`newSingleThreadExecutor`(保证任务顺序执行)。同时,文章还讲解了Java中的锁机制,如`synchronized`关键字、CAS操作及其实现方式,并详细描述了可重入锁`ReentrantLock`和读写锁`ReadWriteLock`的工作原理与应用场景。
|
23天前
|
NoSQL Redis
单线程传奇Redis,为何引入多线程?
Redis 4.0 引入多线程支持,主要用于后台对象删除、处理阻塞命令和网络 I/O 等操作,以提高并发性和性能。尽管如此,Redis 仍保留单线程执行模型处理客户端请求,确保高效性和简单性。多线程仅用于优化后台任务,如异步删除过期对象和分担读写操作,从而提升整体性能。
53 1
|
3月前
|
存储 消息中间件 资源调度
C++ 多线程之初识多线程
这篇文章介绍了C++多线程的基本概念,包括进程和线程的定义、并发的实现方式,以及如何在C++中创建和管理线程,包括使用`std::thread`库、线程的join和detach方法,并通过示例代码展示了如何创建和使用多线程。
68 1
|
3月前
|
Java 开发者
在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口
【10月更文挑战第20天】在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口。本文揭示了这两种方式的微妙差异和潜在陷阱,帮助你更好地理解和选择适合项目需求的线程创建方式。
47 3