什么是同步锁?

devtools/2024/10/20 19:28:18/

同步锁(Synchronized Lock)是多线程编程中的一个关键概念,用来解决多个线程同时访问共享资源时可能引发的数据不一致问题。同步锁通过限制多个线程并发执行关键代码的能力,确保在同一时刻只有一个线程能够访问临界区(即共享资源的代码段),从而保证数据的一致性。

我们可以通过 Java 的 synchronized 关键字实现同步锁。

1. 为什么需要同步锁?

在多线程环境下,如果多个线程同时操作同一个共享资源(比如全局变量或对象),很可能会出现竞态条件(Race Condition)。即,当多个线程在没有适当同步的情况下并发执行时,某个线程的执行结果会依赖于其他线程的执行顺序,这种不确定性会导致数据不一致的情况。

举例说明:

假设我们有一个共享变量 counter,多个线程同时对它进行自增操作(counter++)。在没有同步锁的情况下,两个线程可能会同时读取到相同的 counter 值,然后都尝试对它进行加一操作,这样会导致有一次加一操作被丢失,最终的 counter 值比预期的要小。

2. 什么是同步锁?

同步锁的目的是控制多个线程对共享资源的并发访问。通过同步锁,只有一个线程可以进入临界区,其他线程必须等待该线程执行完毕并释放锁后,才能继续执行。

在 Java 中,synchronized 关键字用于加锁。它可以用来锁住代码块或整个方法,保证在同一时刻只有一个线程可以进入这个同步代码块或方法。

同步锁的核心概念
  • 锁(Lock):每个对象都隐式关联着一个锁。线程必须获得对象的锁才能执行被 synchronized 保护的代码。获得锁的线程执行完同步代码后会释放锁,其他线程才能继续获得锁并执行。
  • 同步代码块(Synchronized Block):被 synchronized 修饰的代码就是同步代码块。只有一个线程能够进入该代码块,其他线程会被阻塞,直到该线程退出同步代码块并释放锁。

3. 如何使用 synchronized

Java 中的 synchronized 可以用于方法或代码块,分为两种常见的用法:

a. 同步实例方法

将整个方法声明为同步方法。这样在调用这个方法时,线程必须获得当前对象的锁。

java">public class Counter {private int count = 0;// synchronized 修饰实例方法public synchronized void increment() {count++;}public int getCount() {return count;}
}
  • 当线程 A 调用 increment() 方法时,它会锁定 Counter 对象,其他线程(比如线程 B)如果也试图调用 increment(),就必须等待线程 A 执行完释放锁之后才能执行。
  • 锁的粒度是整个方法,意味着整个方法都只能由一个线程执行。
b. 同步代码块

同步代码块允许我们锁定某个特定对象,而不是锁定整个方法。可以只同步一部分关键代码,来提高性能,因为这样可以让非关键代码并发执行。

java">public class Counter {private int count = 0;public void increment() {// synchronized 锁住代码块,锁定当前对象(this)synchronized (this) {count++;}}public int getCount() {return count;}
}
  • 这里 synchronized(this) 意味着在当前对象上加锁。这样,只有一个线程能够进入 synchronized 代码块。
  • 锁的粒度是代码块,而不是整个方法。
c. 同步静态方法

静态方法中的 synchronized 锁定的是类对象本身(Class 对象),而不是具体的实例。

java">public class Counter {private static int count = 0;// synchronized 修饰静态方法public static synchronized void increment() {count++;}public static int getCount() {return count;}
}
  • 锁的对象是类的 Class 对象,因此所有访问该静态方法的线程都必须获得类级别的锁。
  • 这意味着无论多少实例,都只有一个静态锁用于整个类的所有线程。

4. 如何选择锁对象?

a. 锁住当前对象 (this)

如果同步代码块只关心当前对象的状态,那么锁定 this 对象是常见的选择。

java">synchronized (this) {// 临界区代码
}
b. 锁住类对象 (Class)

如果需要同步的是类级别的资源(如静态变量),应该锁定 Class 对象。

java">synchronized (Counter.class) {// 临界区代码
}
c. 锁住某个特定对象

有时我们只想锁住某个特定对象,而不是整个类或实例。可以使用一个特定的对象作为锁。

java">public class Counter {private Object lock = new Object();public void increment() {synchronized (lock) {// 临界区代码}}
}
  • 这种方式灵活性更强,可以将同步锁的粒度控制得更加精确,减少不必要的阻塞。

5. 线程安全 vs. 性能权衡

虽然同步锁可以保证线程安全,但也会带来性能问题。因为 synchronized 会导致线程在进入同步代码块时阻塞(锁竞争),这可能会降低系统性能,特别是在高并发场景下。

如何降低同步对性能的影响?
  • 减少锁的范围:尽量只锁住需要同步的代码,而不是整个方法或大块代码。同步代码块能有效提高并发性能。
  • 使用双重检查锁定(Double-Checked Locking):这在单例模式中比较常见,只有在第一次检查发现实例为空时才加锁,减少了不必要的同步开销。
  • 使用更高效的并发工具:在 JDK 中,java.util.concurrent 包提供了一些高效的并发工具类,比如 ReentrantLockReadWriteLock,这些可以在特定场景下替代 synchronized,提供更灵活的并发控制。

6. 示例:线程不安全 vs. 线程安全

以下是一个简单的对比,展示没有同步和使用同步锁的区别:

线程不安全示例:
java">public class Counter {private int count = 0;public void increment() {count++;}public int getCount() {return count;}
}

如果多个线程同时调用 increment(),可能会导致竞态条件,最终 count 值不正确。

线程安全示例:
java">public class Counter {private int count = 0;// 使用 synchronized 保证线程安全public synchronized void increment() {count++;}public int getCount() {return count;}
}

通过 synchronized 确保了 increment() 方法是线程安全的,每次只有一个线程能够执行它。


http://www.ppmy.cn/devtools/127361.html

相关文章

【DBA Part03】国产Linux上Oracle RAC安装-升级-ADG-迁移

本阶段内容如下: 01.国产统信UOS-Oracle19c安装配置 02.国产龙蜥AnolisOS-Oracle19c RAC集群安装配置 03.Linux-Oracle11gR2数据库升级到Oracle19C 04.Linux-Oracle11gR2 RAC数据库升级到Oracle19c RAC 05.Linux-Oracle19c ADG容灾配置(111级联) 06.Oracle XT…

目标检测系统中需要【重新训练模型】说明

上百种【基于YOLOv8/v10/v11的目标检测系统】目录(pythonpyside6界面系统源码可训练的数据集也完成的训练模型)-CSDN博客 目标检测系统操作说明【用户使用指南】(pythonpyside6界面系统源码可训练的数据集也完成的训练模型)-CSDN…

C语言:函数指针与指针函数的区别*

文章目录 一、函数指针定义语法 二、指针函数定义语法用途 三、函数指针与指针函数的区别本质不同:声明方式: 四、结论 C语言:函数指针与指针函数的区别 在C语言这个充满灵活性和强大表达力的编程世界中,函数指针和指针函数是两个…

PDT 数据集:首个基于无人机的高精密度树木病虫害目标检测数据集

2024-09-24,由中国山东计算机科学中心、北京大学等机构联合创建了Pests and Diseases Tree(PDT)数据集,目的解决农业领域中病虫害检测模型开发中专业数据集缺失的问题。通过集成公共数据和网络数据,进一步推出了Common…

Kafka 为什么要抛弃 Zookeeper?

嗨,你好,我是猿java 在很长一段时间里,ZooKeeper都是 Kafka的标配,现如今,Kafka官方已经在慢慢去除ZooKeeper,Kafka 为什么要抛弃 Zookeeper?这篇文章我们来聊聊其中的缘由。 Kafka 和 ZooKee…

前端开发攻略---取消已经发出但是还未响应的网络请求

目录 注意&#xff1a; 1、Axios实现 2、Fetch实现 3、XHR实现 注意&#xff1a; 当请求被取消时&#xff0c;只会本地停止处理此次请求&#xff0c;服务器仍然可能已经接收到了并处理了该请求。开发时应当及时和后端进行友好沟通。 1、Axios实现 <!DOCTYPE html> &…

【VUE】Vue中的 keep-alive 组件

Vue2中的keep-alive组件主要用来缓存组件实例,以便在切换时保留其状态。这样能够提高应用程序的性能,避免了在多个页面之间频繁地创建和销毁组件实例。常用于:多表单切换,对表单内数据进行保存。 使用keep-alive组件时需要注意以下几点: keep-alive组件只能用于包含动态组…

基于yolov5_7.0 pyside6 active_learning 开发的人工智能主动学习外周血细胞目标检测系统

基于YOLOv5的图像识别与主动学习应用程序 项目介绍 本项目是一个使用PySide6库开发的基于YOLOv5框架的图像识别应用程序。该应用程序不仅支持用户选择图像文件进行目标物体检测&#xff0c;还具备主动学习功能&#xff0c;允许用户手动标记错误的检测结果以优化模型。此外&am…