Java中synchronized:特性、使用、锁机制与策略简析

news2024/9/27 19:27:43

目录

    • synchronized的特性
      • 互斥性
      • 可见性
      • 可重入性
    • synchronized的使用方法
    • synchronized的锁机制
    • 常见锁策略
      • 乐观锁与悲观锁
      • 重量级锁与轻量级锁
      • 公平锁与非公平锁
      • 可重入锁与不可重入锁
      • 自旋锁
      • 读写锁

synchronized的特性

互斥性

synchronized确保同一时间只有一个线程可以进入同步块或同步方法,避免了多线程并发访问共享资源的冲突问题。
synchronized 会起到互斥效果,某个线程执行到某个对象的 synchronized 中时, 其他线程如果也执行到同一个对象 synchronized 就会阻塞等待。
下面我们来看一个例子,两个线程获取同一个锁,锁被占用后,剩下的那个线程就会进行阻塞等待。

public class test2 {
    public static void main(String[] args) {
        Object object = new Object();
        Thread t1 =  new Thread(()->{
        //进入 synchronized 修饰的代码块, 相当于 加锁
          synchronized (object) {
              for (int i = 0; i < 5; i++) {
                  System.out.println("线程t1获取锁");
                  try {
                      Thread.sleep(1000);
                  } catch (InterruptedException e) {
                      throw new RuntimeException(e);
                  }
              }
          }
        //退出 synchronized 修饰的代码块, 相当于 解锁  
        });
        Thread t2 = new Thread(()->{
            synchronized (object) {
                for (int i = 0; i < 5; i++) {
                    System.out.println("线程特t2获取锁");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        });
        t1.start();
        t2.start();
    }
}

在这里插入图片描述
由结果我们可以知道,线程一释放锁后,由操作系统唤醒线程二才能获取到锁。

synchronized的底层是使用操作系统的mutex lock实现的。

可见性

内存可见性是指当一个线程修改了共享变量的值后,其他线程能够立即看到修改的值。在多线程环境中,由于多个线程同时访问共享变量,每个线程都有自己的工作内存,而工作内存中保存了主内存中的部分数据副本。因此,当一个线程修改了共享变量的值,但这个修改尚未被刷新到主内存时,其他线程可能无法立即看到这个修改,而继续使用自己工作内存中的旧值,造成了内存不可见性。

synchronized 既能保证原子性,也能保证内存可见性,一个线程对共享变量的修改对于其他线程是可见的。

class Counter {
    public static int flag = 0;
}

public class test3 {
    public static void main(String[] args) {
        Object object = new Object();
        Thread t1 = new Thread(() -> {
            while (true) {
                synchronized (object) {
                    if (Counter.flag != 0) {
                        break;
                    }
                }
            }
            System.out.println("线程一知道了共享变量改为" + Counter.flag);
        });
        Thread t2 = new Thread(() -> {
            Scanner scanner = new Scanner(System.in);
            System.out.println("输入一个整数:");
            Counter.flag = scanner.nextInt();
        });
        t1.start();
        t2.start();
    }
}

在这里插入图片描述
如果线程一不加synchronized,那么共享变量的改变它就感知不到,以至于程序一直在运行中。
在这里插入图片描述

可重入性

synchronized 同步块对同一条线程来说是可重入的,不会出现自己把自己锁死的问题。
可以理解为一个线程没有释放锁,然后又尝试再次加锁。
按照之前对锁的理解就是,锁没有释放,进行再次加锁就会进行阻塞,直到第一次的锁被释放,才能获取到第二个锁,但释放第一个锁也由该线程来进行,结果现在这个线程啥都干不了,也就只能形成死锁了。
这样的锁称其为不可重入锁。

我们的synchronized是可重入锁。
在重入锁的内部有两个信息,分别为“程序计数器”和“线程持有者”

  • 如果某个线程加锁的时候,发现锁已经被人占用,但是恰好占用的正是自己, 那么仍然可以继续获取到锁,并让计数器自增。
  • 解锁的时候计数器递减为 0 的时候,才真正释放锁。

synchronized的使用方法

  1. 直接修饰普通方法: 锁的 SynchronizedDemo 对象
public synchronized void methond() {
}
  1. 修饰静态方法: 锁的 SynchronizedDemo 类的对象
public synchronized static void method() {
}
  1. 修饰代码块: 明确指定锁哪个对象
  • 锁当前对象
public void method() {
synchronized (this) {
}
}
  • 锁类对象
public void method() {
synchronized (SynchronizedDemo.class) {
}
}

synchronized的锁机制

  1. 对象锁:可以将synchronized关键字直接应用于实例方法或实例代码块上。当一个线程进入被synchronized修饰的实例方法或实例代码块时,它会自动获取该对象的内置锁。只有当线程释放锁之后,其他线程才能进入同步块。

  2. 类锁:可以将synchronized关键字应用于静态方法或类代码块上。当一个线程进入被synchronized修饰的静态方法或类代码块时,它会自动获取该类的Class对象的内置锁。类锁是属于整个类的,对于同一个类的不同实例,他们共享同一个类锁。

  3. 锁对象:可以使用synchronized关键字加锁指定的对象。通过指定一个对象作为锁,多个线程可以根据这个对象来实现同步。当一个线程进入synchronized代码块时,它会尝试获取指定对象的内置锁,只有当线程释放锁之后,其他线程才能获得锁并执行同步代码。

常见锁策略

乐观锁与悲观锁

悲观锁是在数据被使用前加锁,防止数据被其他线程修改。
乐观锁则是在更新数据时检查数据是否被其他线程修改过,如果没有则更新成功,否则返回失败。
Synchronized 初始使用乐观锁策略,当发现锁竞争比较频繁的时候, 就会自动切换成悲观锁策略。

重量级锁与轻量级锁

轻量级锁是一种优化的锁,它在CAS操作时使用CPU的自旋机制,如果自旋成功则获取到锁,否则进入睡眠状态。
重量级锁是一种传统的锁,它依赖于操作系统的MutexLock(互斥锁)来实现,当有多个线程竞争同一个锁时,会阻塞其他线程等待释放。

公平锁与非公平锁

假设有A,B,C三个线程依次进行同一把锁的获取,线程A获取成功了,线程B与C获取失败。
等待线程A释放锁后,线程B与C,如何获取锁在这里插入图片描述
公平锁策略: 遵守 “先来后到”。B 比 C 先来的。当 A 释放锁的之后,B 就能先于 C 获取到锁。
非公平锁策略:不遵守 “先来后到”。B 和 C 都有可能获取到锁。
synchronized 是非公平锁

可重入锁与不可重入锁

可重入锁的意思就是允许同一个线程多次获取同一把锁。
Java里只要以Reentrant开头命名的锁都是可重入锁,而且JDK提供的所有现成的Lock实现类,包括synchronized关键字锁都是可重入的。
可以理解为一个线程没有释放锁,然后又尝试再次加锁。
按照之前对锁的理解就是,锁没有释放,进行再次加锁就会进行阻塞,直到第一次的锁被释放,才能获取到第二个锁,但释放第一个锁也由该线程来进行,结果现在这个线程啥都干不了,也就只能形成死锁了。
这样的锁称其为不可重入锁。

synchronized 是可重入锁

自旋锁

为防止线程在抢锁失败后进入阻塞状态,经过很久才能再次被调度的情况。

while (!locked.compareAndSet(false, true)) {
            // 不断循环直到获取到锁
        }

如果获取锁失败,立即再尝试获取锁, 无限循环,直到获取到锁为止。 第一次获取锁失败, 第二次的尝试会在极短的时间内到来。
缺点:如果锁被其他线程持有的时间比较久, 那么就会持续的消耗 CPU 资源。
synchronized 中的轻量级锁策略大概率就是通过自旋锁的方式实现的

读写锁

一个线程对于数据的访问, 主要存在两种操作: 读数据 和 写数据.

  • 两个线程都只是读一个数据, 此时并没有线程安全问题. 直接并发的读取即可.

  • 两个线程都要写一个数据, 有线程安全问题.

  • 一个线程读另外一个线程写, 也有线程安全问题.

    读写锁就是把读操作和写操作区分对待。 Java 标准库提供了ReentrantReadWriteLock 类,实现了读写锁。

  • ReentrantReadWriteLock.ReadLock 类表示一个读锁。这个对象提供了 lock / unlock 方法进行加锁解锁。

  • ReentrantReadWriteLock.WriteLock 类表示一个写锁。 这个对象也提供了 lock / unlock 方法进行加锁解锁

读加锁和读加锁之间, 不互斥.
写加锁和写加锁之间, 互斥.
读加锁和写加锁之间, 互斥

Synchronized 不是读写锁

想了解更多也可以看我的笔记专栏哈哈在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1026285.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

函数扩展之——内存函数

前言&#xff1a;小伙伴们又见面啦。 本篇文章&#xff0c;我们将讲解C语言中比较重要且常用的内存函数&#xff0c;并尝试模拟实现它们的功能。 让我们一起来学习叭。 目录 一.什么是内存函数 二.内存函数有哪些 1.memcpy &#xff08;1&#xff09;库函数memcpy &…

交换机端口镜像详解

交换机端口镜像是一种网络监控技术&#xff0c;它允许将一个或多个交换机端口的网络流量复制并重定向到另一个端口上&#xff0c;以便进行流量监测、分析和记录。通过端口镜像&#xff0c;管理员可以实时查看特定端口上的流量&#xff0c;以进行网络故障排查、安全审计和性能优…

已解决 Microservice Error: Circuit Breaker: Service is temporarily unavailable

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页: &#x1f405;&#x1f43e;猫头虎的博客&#x1f390;《面试题大全专栏》 &#x1f995; 文章图文并茂&#x1f996…

【操作系统】聊聊磁盘IO是如何工作的

磁盘 机械磁盘 主要是由盘片和读写磁头组成。数据存储在盘片的的环状磁道上&#xff0c;读写数据前需要移动磁头&#xff0c;先找到对应的磁道&#xff0c;然后才可以访问数据。 如果数据都在同一磁道上&#xff0c;不需要在进行切换磁道&#xff0c;这就是连续IO&#xff0c;可…

离散数学之 一阶逻辑等值演算与推理

一阶逻辑等值式与置换规则 基本等值式 这里用到了量词辖域的收缩 未完待续

电工三级证(高级)实战项目:PLC控制步进电机正反转

实训目的 了解使用PLC代替传统继电器控制回路的方法及编程技巧&#xff0c;理解并掌握步进电动机的运行方式及其实现方法。通过实验进一步加深理解步进电机控制的特点以及在实际中的应用。 控制要求 PLC设备:Siemens S7-200 要求:打开开关K0(I0.0)得电&#xff0c;启动PLC程…

【xshell和xftp连接Ubuntu教程】

一、下载xshell和xftp 下载地址 https://www.xshell.com/zh/free-for-home-school/ 二、连接xshell 输入ip&#xff0c;端口号 输入用户名&#xff0c;密码 出现这个使用就行了 三、连接xftp 同上&#xff0c;输入ip&#xff0c;端口&#xff0c;用户名&#xff0c;密码 连接成…

拓扑关系如何管理?

在设备对接涂鸦的云端过程中&#xff0c;一部分设备由于自身资源或硬件配置&#xff0c;无法直接连接云端。而是需要通过网关进行中转&#xff0c;由网关代理实现和云端进行数据交互&#xff0c;间接实现设备接入云端。这样的设备也称为子设备。 要想实现网关代理子设备接入云…

C++跳坑记:位移超出范围的处理

在C编程中&#xff0c;数据类型的选择不仅影响内存占用和性能&#xff0c;还可以对某些操作的结果产生意想不到的影响。今天&#xff0c;我将分享一个关于C在不同变量类型下位移操作结果的发现。 位移操作是C中常见的对整数的高效操作之一。然而&#xff0c;我们可能会忽视一个…

单播与多播mac地址

MAC 地址&#xff08;Media Access Control Address&#xff09;是一个用于识别网络设备的唯一标识符。每个网络设备都有一个独特的 MAC 地址&#xff0c;用于在局域网中进行通信。 单播MAC地址&#xff1a;单播MAC地址用于单播通信&#xff0c;即一对一的通信模式。当设备发送…

day4_QT

day4_QT qt绘制钟表 qt绘制钟表 #include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this);this->resize(1000,1000);this->setStyleSheet("background-color:…

Word中对象方法(Methods)的理解及示例(下)

【分享成果&#xff0c;随喜正能量】当你的见识多了&#xff0c;眼界宽了&#xff0c;格局大了&#xff0c;所有的磨难都将不再是磨难&#xff0c;而是助你成长的阶梯。 。 《VBA之Word应用》&#xff08;10178982&#xff09;&#xff0c;是我推出第八套教程&#xff0c;教程…

pnpm入门教程

一、概述 1、更小 使用 npm 时&#xff0c;依赖每次被不同的项目使用&#xff0c;都会重复安装一次。 而在使用 pnpm 时&#xff0c;依赖会被存储在内容可寻址的存储中。 2、更快 依赖解析。 仓库中没有的依赖都被识别并获取到仓库。目录结构计算。 node_modules 目录结构是…

编程(47)----------Spring AOP

AOP是Spring中, 个人认为较为抽象的一个思想. 一般来说, 学习一个新东西, 第一件事是先看看这个知识点的定义是什么. 同时要注意, 同一事物的定义可以有很多, 毕竟定义没有绝对的对与错, 只有准确与否. 而初次接触AOP的定义, 第一感觉可能就是抽象, 或者说看不懂, 这里面也有…

刷题日记——将x减到0的最小操作数

将x减到0的最小操作数 题目链接&#xff1a;https://leetcode.cn/problems/minimum-operations-to-reduce-x-to-zero/ 题目解读 题目要求移除元素总和等于参数x&#xff0c;这道题给我的第一感觉就是从数组的两边入手&#xff0c;对数据进行加和删除&#xff0c;但是这里有一…

SVN状态图标不显示

问题可能点1&#xff1a;图标覆盖 1、右键找到设置 2、找到图标覆盖 3、重启TortoiseSVN 问题可能点2&#xff1a;注册表图标顺序太靠下&#xff0c;被占用 1、windowsr, 输入regedit进入注册表 2、找到一下目录 计算机\HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\Cu…

servlet中doGet方法无法读取body中的数据

servlet中doGet方法不支持读取body中的数据。

警惕!多本SCI/SSCI被剔除,9月SCI/SSCI期刊目录已更新~(附下载)

【SciencePub学术】 2023年9月20日&#xff0c;科睿唯安更新了Web of Science核心期刊目录。 继上次SCI期刊目录和SSCI期刊目录更新之后&#xff0c;本次9月更新共有9本期刊发生变动&#xff1a; • SCIE&#xff1a;有3本期刊不再被SCIE期刊目录收录(Editorial De-listing/Pr…

Python 之 shadow 爆破密码脚本编写

文章目录 Linux shadow 爆破脚本Linux shadow 爆破初探Linux shadow 爆破进阶 Linux shadow 爆破脚本 Linux shadow 爆破初探 目的是为了明白其shadow爆破原理 # Linux shadow爆破初探 1import crypt#shadow文件中的一条用户数据 shadow_line "ghui:$y$j9T$DQ2d2fD138…

(JavaEE)(多线程案例)线程池 (简单介绍了工厂模式)(含经典面试题ThreadPoolExector构造方法)

线程诞生的意义&#xff0c;是因为进程的创建/销毁&#xff0c;太重了&#xff08;比较慢&#xff09;&#xff0c;虽然和进程比&#xff0c;线程更快了&#xff0c;但是如果进一步提高线程创建销毁的频率&#xff0c;线程的开销就不能忽视了。 这时候我们就要找一些其他的办法…