多线程与高并发- Synchronized锁

简介

synchronized 是 Java 语言的一个关键字,它允许多个线程同时访问共享的资源,以避免多线程编程中的竞争条件和死锁问题。synchronized可以用来给对象或者方法进行加锁,当对某个对象或者代码块加锁时,同时就只能有一个线程去执行。这种就是互斥关系,被加锁的区域称为临界区,而里面的资源就是临界资源。当一个线程进入临界区的时候,另一个线程就必须等待。
在这里插入图片描述
synchronized可以限制对某个资源的访问,但是它锁的并不是资源本身,可以锁住某个对象,只有线程拿到这把锁之后才能够去访问临界资源。如下代码,在我们想执行对count变量进行操作的时候,线程需要拿到o这个对象。

public class T1_Synchronized01 {
    private int count = 1;
    private Object o = new Object();

    public void m1() {
        synchronized (o) { // 必须先拿到o这个锁
            count++;
            System.out.println(Thread.currentThread().getName() + " count = " + count);
        }
    }
}

synchronized基础用法

1、通过对象进行锁

在代码里,可以通过创建一个对象,这样要想拿到临界资源,就必须先获得到这个对象的锁。

public class T1_Synchronized01 {
    private int count = 1;
    private Object o = new Object();

    public void m1() {
        synchronized (o) { // 必须先拿到o这个锁
            count++;
            System.out.println(Thread.currentThread().getName() + " count = " + count);
        }
    }
}
2、通过this

使用this代表锁住的是当前对象,这种方法等同直接把synchronized关键字加在方法前。

public class T1_Synchronized01 {
    private int count = 1;

    public void m2() {
        synchronized (this) { // 必须先拿到this的锁
            count++;
            System.out.println(Thread.currentThread().getName() + " count = " + count);
        }
    }

    public synchronized void m3() { // 与m2一样
        count++;
        System.out.println(Thread.currentThread().getName() + " count = " + count);
    }

}
3、锁定静态方法

锁定静态方法需要通过类.class,或者直接在静态方法上加上关键字。但是,类.class不能使用this来代替。注:在同一个类加载器中,class是单例的,这也就能保证synchronized能够只让一个线程访问临界资源。

public class T1_Synchronized01 {
    public static void m4() { // 静态方法
        synchronized (T1_Synchronized01.class) {
            System.out.println(Thread.currentThread().getName());
        }
    }

    public synchronized static void m5() {
       System.out.println(Thread.currentThread().getName());
    }
}
4、实验测试

①、首先测试一下,同步和非同步是否可以相互调用
定义两个线程,一个执行同步方法,一个执行非同步方法,如果不能够互相调用,那么,非同步线程就需要等待同步线程执行完之后在继续执行。

public class T3_Synchronized03 {
    public synchronized void one() {
        System.out.println(Thread.currentThread().getName() + " start one method");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println(Thread.currentThread().getName() + " end one method");
    }

    public void two() {
        System.out.println(Thread.currentThread().getName() + " execute two method");
    }

    public static void main(String[] args) {
        T3_Synchronized03 t = new T3_Synchronized03();
        new Thread(t::one, "第一个线程").start();
        new Thread(t::two, "第二个线程").start();
    }
}

从运行的结果可以看出是可以的。
在这里插入图片描述
②、读写不全加锁会怎样
通过购票与查询票数来进行模拟读写加锁问题。
首先,看以下代码是给读写都进行加锁了,在扣掉票数的时候,休眠了2秒,当线程执行了购票之后,通过多个线程去查询票数,每次启动线程会睡眠0.5秒。

public class T4_Synchronized {
    private int ticket = 100; // 模拟100张票

    public synchronized int getTicket() { // 读
        return this.ticket;
    }

    public synchronized void buy(int number) { // 写
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        ticket = ticket - number;
    }

    public static void main(String[] args) throws InterruptedException {
        T4_Synchronized bus = new T4_Synchronized();
        System.out.println("刚开始有票数:" + bus.getTicket());
        new Thread(() -> bus.buy(1)).start();
        for (int i = 1; i <= 10; i++) {
            Thread.sleep(500);
            int finalI = i;
            new Thread(() -> System.out.println("第" + finalI + "次查询余票数:" + bus.getTicket())).start();
        }

    }
}

运行之后,我们可以发现,数据是正确的,尽管是在查询的时候并没有睡眠0.5秒,显示数据依然是期望数据。
在这里插入图片描述
然而,当我们把读的锁去掉,运行代码,会发现,数据读出来了脏数据,为了更好的显示,查询票数的睡眠不要去掉。
在这里插入图片描述
③、synchronized的可重入性
定义一个类,类中有两个同步方法,他们锁的对象都是当前类,如果不能够重入,在one线程启动的时候就会死锁。在同步方法one中去调用同步方法two,当线程启动的时候,已经获取了对象的锁,等调用two方法的时候,同样是拿到了这个对象的锁。所以synchronized是可重入的。

public class T5_Synchronized {

    synchronized void one() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        two();
        System.out.println("one - thread-" + Thread.currentThread().getName() + " end");
    }

    synchronized void two() {
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("two - thread-" + Thread.currentThread().getName() + " end");
    }

    public static void main(String[] args) {
        T5_Synchronized t5 = new T5_Synchronized();
        new Thread(t5::one, "one1").start();
        new Thread(t5::one, "one2").start();
        new Thread(t5::one, "one3").start();
    }
}

实验结果
在这里插入图片描述
④、异常会释放锁
当线程执行过程中出现了异常,synchronized的锁会被释放,这样其他需要访问这个临界资源的线程就能进入执行。

public class T6_Synchronized {
    int count = 0;
    synchronized void add() {
        System.out.println("线程 " + Thread.currentThread().getName() + " start");
        while (true) {
            count++;
            System.out.println("线程 " + Thread.currentThread().getName() + " now count = " + count);
            if (count == 3) {
                throw new NullPointerException("人为异常");
            }
            if (count == 10) {
                throw new NullPointerException("测试结束");
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        T6_Synchronized t = new T6_Synchronized();
        new Thread(t::add, "1").start();
        Thread.sleep(1000);
        new Thread(t::add, "2").start();
    }
}

当第一次异常抛出时,线程2就立即进入执行。
在这里插入图片描述

synchronized锁的底层原理

synchronized实现锁的基础就是Java对象头,synchronized锁会将线程ID存入mark word(对象头由标记字)。关于mark word,先简要了解一下Java对象。
在Hotspot 虚拟机中,对象在内存中的存储布局,可以分为三个区域:对象头(Header)、实例数据(Instance Data)、对齐填充(Padding)。synchronized主要是跟对象头有关系,在对象头中包含了标记字(mark word)、类指针(klass word)和 数组长度(array length)。也就是通过mark word的字节位数来表示各种锁状态。
在这里插入图片描述
synchronized锁在线程第一次访问的时候,实际上是没有加锁的,只是在mark word中记录了线程ID,这种就是偏向锁,默认是认为不会有多个线程抢着用,mark word是通过64bit来表示的,通过最低2位也就是锁标志位,偏向锁与无锁的值是01,轻量级锁用00表示,重量级锁用10表示,标记了GC的用11表示,无锁与偏向锁低2位是一致的,在倒数第3位有1位来表示偏向锁位:值为1表示偏向锁。
在这里插入图片描述

这里引用一张掘金博客上的图:https://juejin.cn/post/6978882583492821023

synchronized锁升级

● synchronized锁在线程第一次访问的时候,实际上是没有加锁的,只是在mark word中记录了线程ID,默认也就是使用偏向锁。
● 当第二个线程来争用的时候,此时第二个线程会占用cpu,循环等待锁的释放,这时候偏向锁也就升级为自旋锁。
● 当自旋10次之后,就会升级为重量级锁,重量级锁是不占用cpu,他是使用OS的。
当线程数较少、运行时间较短的时候是比较适合使用自旋锁,反之则比较适合重量级锁。


---------------------
作者:一个有梦有戏的人
来源:CSDN
原文:https://blog.csdn.net/qq_43843951/article/details/129107202
版权声明:本文为作者原创文章,转载请附上博文链接!
内容解析By:CSDN,CNBLOG博客文章一键转载插件

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Apple - Core Bluetooth Programming Guide

本文翻译整理自&#xff1a;Core Bluetooth Programming Guide&#xff08;更新日期&#xff1a;2013-09-18 https://developer.apple.com/library/archive/documentation/NetworkingInternetWeb/Conceptual/CoreBluetooth_concepts/AboutCoreBluetooth/Introduction.html#//ap…

Vue中子传父通讯实现颜色换行添加删除

如图&#xff1a;列表是写在父组件中&#xff0c;input框和按钮是写在子组件中的 按照以上示例图有这两个文件 父组件中&#xff1a;AboutView.vue <template><div class"about"><!-- <h1>This is an about page</h1> --><!-- &…

【Unity拖拽物体】实现对点中的3D物体进行拖拽的功能

场景结构&#xff0c;两个普通模型 第一种 脚本所挂载的物体才可以被拖拽 【PC鼠标版本】 using UnityEngine;// 这个脚本实现了&#xff0c;本脚本所在的游戏物体能够被拖拽 public class DragObjectT : MonoBehaviour {private Vector3 screenPoint; // 存储物体在屏幕上的位…

【Seata】Seata——分布式事务框架(理论篇)

目录 解释Seata的三大角色Seata的分布式事务解决方案&#xff1a;AT 模式整体机制一阶段二阶段 完整图例 XA模式DTP模型Seata的XA模式Seata-XA的价值小结XA协议的问题 TCC模式解释Seata的TCC模式Seata-TCC特点 Saga模式Saga的价值Saga状态机基本原理Saga状态机设计器 四种模式的…

JavaFX 下拉框

组合框允许用户选择几个选项之一。用户可以滚动到下拉列表。组合框可以是可编辑和不可编辑的。 创建组合框 以下代码将选项列表包装到ObservableList中&#xff0c;然后使用observable列表实例化ComboBox类。 ObservableList<String> options FXCollections.observab…

情系端午,爱暖精诚 | 我院开展温情献礼端午慰问活动

端午佳节将至&#xff0c;为感谢全体员工在医院发展中的无私奉献和辛勤努力&#xff0c;传递对大家的深情关怀&#xff0c;提升员工的归属感与凝聚力。6月6日&#xff0c;医院特别为全体员工精心准备了节日福利&#xff0c;为每位员工送上饱含爱意的节日礼品。 一盒盒满载心意的…

【Docker实战】jenkins卡在编译Dockerfile的问题

我们的项目是标准的CI/CD流程&#xff0c;也即是GitlabJenkinsHarborDocker的容器自动化部署。 经历了上上周的docker灾难&#xff0c;上周的服务器磁盘空间灾难&#xff0c;这次又发生了jenkins卡住的灾难。 当然&#xff0c;这些灾难有一定的连锁反应&#xff0c;是先发生的d…

vue3-父子通信

一个简单的vue3子组件调用父组件方法的demo <template> <div> <h2>Parent Component父组件</h2> <ChildComponent notify-parent"handleParentMethod" /> </div> </template> <script> import { ref } fr…

hugging face:大模型时代的github介绍

1. Hugging Face是什么&#xff1a; Hugging Face大模型时代的“github”&#xff0c;很多人有个这样的认知&#xff0c;但是我觉得不完全准确&#xff0c;他们相似的地方在于资源丰富&#xff0c;github有各种各样的软件代码和示例&#xff0c;但是它不是系统的&#xff0c;没…

HTML5基本语法

文章目录 HTML5基本语法一、基础标签1、分级标题2、段标签3、换行及水平线标签4、文本格式标签 二、图片标签1、格式2、属性介绍 三、音频标签1、格式2、属性介绍 四、视频标签1、格式2、属性介绍 五、链接标签1、格式2、显示特点3、属性介绍4、补充&#xff08;空链接&#xf…

计算机毕业设计Python+Flask弹幕情感分析 B站视频数据可视化 B站爬虫 机器学习 深度学习 人工智能 NLP文本分类 数据可视化 大数据毕业设计

首先安装需要的python库&#xff0c; 安装完之后利用navicat导入数据库文件bili100.sql到mysql中&#xff0c; 再在pycharm编译器中连接mysql数据库&#xff0c;并在设置文件中将密码修改成你的数据库密码。最后运行app.py&#xff0c;打开链接&#xff0c;即可运行。 B站爬虫数…

【博士每天一篇文献-算法】Memory aware synapses_ Learning what (not) to forget

阅读时间&#xff1a;2023-12-13 1 介绍 年份&#xff1a;2018 作者&#xff1a;Rahaf Aljundi,丰田汽车欧洲公司研究员;阿卜杜拉国王科技大学(KAUST)助理教授;Marcus Rohrbach德国达姆施塔特工业大学多模式可靠人工智能教授 会议&#xff1a; Proceedings of the European c…

物联网协议应用

目录 前言一、WIFI简介二、NTP协议2.1 NTP简介2.2 NTP实现 三、HTTP协议3.1 HTTP协议简介3.2 HTTP服务器 四、MQTT协议4.1 MQTT协议简介4.1.1 MQTT通信模型4.1.2 MQTT协议实现原理4.1.3 MQTT 控制报文 4.2 移植MQTT协议 前言 本文主要介绍一下物联网协议如NTP协议、HTTP协议和M…

Redis 内存策略

一、Redis 内存回收 Redis 之所以性能强&#xff0c;最主要的原因就是基于内存存储。然而单节点的 Redis 其内存大小不宜过大&#xff0c;会影响持久化或主从同步性能。 我们可以通过修改配置文件来设置 Redis 的最大内存&#xff1a; # 格式&#xff1a; # maxmemory <byt…

高等数学笔记(二):极限

一、数列极限的定义 以下符号表示 “对于任意给定的” 以下符号表示 “存在” 以下符号表示 “如果什么&#xff08;箭头左&#xff09;&#xff0c;则什么&#xff08;箭头右&#xff09;” 二、收敛数列的性质 2.1 唯一性 2.2 有界性 2.3 保号性 2.4 子数列收敛性 三、函数…

无痛接入图像生成风格迁移能力:GAN生成对抗网络

AI应用开发相关目录 本专栏包括AI应用开发相关内容分享&#xff0c;包括不限于AI算法部署实施细节、AI应用后端分析服务相关概念及开发技巧、AI应用后端应用服务相关概念及开发技巧、AI应用前端实现路径及开发技巧 适用于具备一定算法及Python使用基础的人群 AI应用开发流程概…

网络安全:探索云安全的最佳实践

文章目录 网络安全&#xff1a;探索云安全的最佳实践引言云安全简介云安全面临的挑战云安全的最佳实践数据加密身份和访问管理定期安全审计 结语 网络安全&#xff1a;探索云安全的最佳实践 引言 在我们之前的文章中&#xff0c;我们讨论了网络安全的多个方面&#xff0c;包括…

卫生间毫米波雷达跌倒检测,飞睿智能人体存在感应器,智能识别老人跌倒守护安全

在智能家居飞速发展的今天&#xff0c;雷达技术已经悄然走进了我们的生活&#xff0c;尤其在卫生间这样的特殊场景中&#xff0c;毫米波雷达人体存在感应器和跌倒检测技术的应用&#xff0c;通过及时识别老年人跌倒等意外情况&#xff0c;及时发送警报信息&#xff0c;不仅为我…

数学建模基础:线性模型

目录 前言 一、线性方程组 二、线性规划 三、线性回归 四、线性模型的应用 五、实例示范&#xff1a;医疗成本预测 步骤 1&#xff1a;导入数据 步骤 2&#xff1a;数据预处理 步骤 3&#xff1a;建立多元线性回归模型 步骤 4&#xff1a;模型验证 步骤 5&#xff1…

数据库物理计划执行指南

一、背景介绍 伴随信息技术地迅猛发展和应用范围地逐步扩大&#xff0c;数据库已成为企业存储与管理数据的重要工具。但数据量激增以及用户访问需求的与日剧增&#xff0c;数据库性能也将面临巨大挑战。 好在数据库物理计划执行是解决数据库性能问题的重要手段之一&#xff0…