synchronized 关键字

news2024/11/20 7:05:56

在这里插入图片描述

文章目录

  • 一、synchronized 的特性
    • 互斥
    • 可重入
  • 二、 synchronized 使用示例
  • 三、 java标准库的线程安全类
  • 四、 死锁
    • 可重入死锁
    • 相互争夺锁
    • 哲学家就餐问题
    • 死锁的四个必要条件

一、synchronized 的特性

互斥

synchronized 会起到互斥效果, 某个线程执行到某个对象的 synchronized 中时, 其他线程如果也执行到同一个对象 synchronized 就会阻塞等待.
在这里插入图片描述
进入该方法相当于针对该对象"加锁" ( lock )
执行完该方法相当于对该对象"解锁" ( unlock )
在这里插入图片描述
当有一个线程加锁之后,其他线程只能阻塞等待直到释放锁
注意:

  1. 上一个线程解锁之后, 下一个线程并不是立即就能获取到锁. 而是要靠操作系统来 “唤醒”. 这也就是操作系统线程调度的一部分工作.
  2. 假设有 A B C 三个线程, 线程 A 先获取到锁, 然后 B 尝试获取锁, 然后 C 再尝试获取锁, 此时 B 和 C 都在阻塞队列中排队等待. 但是当 A 释放锁之后, 虽然 B 比 C 先来的, 但是 B 不一定就能获取到锁, 而是和 C 重新竞争, 并不遵守先来后到的规则.

可重入

一个线程对同一个对象是否可以连续加锁两次,如果可以就是可重入的

public synchronized void add() {
        synchronized (this) {
            this.num++;
        }
    }

在这里插入图片描述
在这里插入图片描述
站在this锁对象,它认为自己已经被另外的线程占用的,这里的第二次加锁是否要阻塞等待?
但此处第二个线程和第一个线程是同一个线程,是否允许加锁,如果允许,这个操作就算可重入的,否则会导致死锁。
在这里插入图片描述
线程进入一个层锁,第二次加锁的时候会组设等待,直到第一次锁释放,才能获取第二个锁,但是我们会在锁对象里记录一下,当前的锁是那个对象是那个线程持有的,如果加锁线程和持有线程是同一个,就放过,否则阻塞。

上面的代码是完全没问题的. 因为 synchronized 是可重入锁.

二、 synchronized 使用示例

synchronized的3种使用方式:

  1. 修饰实例方法:作用于当前实例加锁
public synchronized void add() {
       
    }
  1. 修饰静态方法:作用于当前类对象加锁
public synchronized static void add() {
       
    }
  1. 修饰代码块:指定加锁对象,对给定对象加锁
    锁当前对象
public void add() {
        synchronized (this) {
            
        }
    }

锁类对象

public class SynchronizedDemo {
    public void method() {
        synchronized (SynchronizedDemo.class) {
       }
   }
}

我们需要注意的是,synchronized锁的是什么?
只有两个线程竞争获取同一把锁,才会阻塞等待。
在这里插入图片描述
两个线程分别获取不同的锁不会产生竞争。

三、 java标准库的线程安全类

之前我们一直是单线程操作线程,所以也不用太注重线程安全问题,但当我们多线程操作集合的时候,我们就需要注意线程安全问题了。
线程不安全的集合:
1.ArrayList
2.LinkedList
3.HashMap
4.TreeMap
5.HashSet
6.TreeSet
7.StringBuilder

线程安全的集合:
1.ConcurrentHashMap
2.StringBuffer
在这里插入图片描述
我们可以看到StringBuffer的方法大多数都加了synchronized.
在这里插入图片描述
但是concurrentHashMap的方法没有加synchronized,但是同样是线程安全的,大家下去可以研究一下。
String我们没有加任何的锁,但它是不可修改的,仍然是线程安全的。
为什么不给每个集合都加上锁呢?
因为在加锁的同时,会带来额外的时间开销,有些情况下,不使用多线程,不会面临线程安全问题,所以我们的一些集合并没有加锁,因此,我们在多线程情况下使用这些集合时,可以手动加锁。

四、 死锁

可重入死锁

public synchronized void add() {
        synchronized (this) {
            this.num++;
        }
    }

一个线程,一把锁,连续加入两次,如果锁不是可重入锁,就会造成死锁问题。
但是Java的synchronized和ReentrantLock都是可重入锁,这里我们无法演示,大家可以参考我上面加可重入问题所讲的。

相互争夺锁

两个线程两把锁,t1,t2现针对锁A,锁B加锁,再去获取对方的锁,双方都不愿意让步,就会造成死锁问题。
在这里插入图片描述

这里我们举一个生活中类似的例子,某一天一码通给崩了,我们的手机健康吗打不开了,于是我们的程序员就需要赶到公司去修复这个问题,但是在公司楼下被保安拦住了,要求出示健康码,程序员说: 我上楼修复了bug才能出示健康码。保安: 你出示了健康码才能上楼修复bug. 这个情景和我们此处的死锁非常类似。

public static void main(String[] args) {
        Object A = new Object();
        Object B = new Object();
        Thread t1 = new Thread(() -> {
            synchronized (A) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                synchronized (B) {
                    System.out.println("t1获取到了锁A和锁B");
                }
            }
        });
        Thread t2 = new Thread(() -> {
            synchronized (B) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                synchronized (A) {
                    System.out.println("t2获取到了锁A和锁B");
                }
            }
        });
        t1.start();
        t2.start();
    }

在这里插入图片描述
我们可以发现程序什么都没有输出,证明t1,t2都没有获取到两把锁,相互堵塞状态。
我们打开jconsole看一下线程详细状态。
在这里插入图片描述
我们可以看到我们创建的t1,t2线程
在这里插入图片描述
我们可以看到t1处于BLOCKED阻塞状态,看到堆栈信息20行,证明我们t1线程获取不到锁B。
在这里插入图片描述
我们可以看到t2也处于BLOCKED阻塞状态,看到堆栈信息32行,证明我们t1线程获取不到锁A。
针对这样的死锁问题,我们需要借助jconsole这样的工具查看状态和堆栈信息去分析原因并进行修改。

哲学家就餐问题

在这里插入图片描述
我们有六个哲学家和六根筷子,每个哲学家要想吃饭,就必须拿起左手和右手的两根筷子。
每个哲学家只有两种情况:
1.发呆状态(线程阻塞状态)
2.吃饭状态(线程获取到锁并执行)
由于操作系统的随机调度,这六个哲学家,随时都可能吃面条,和发呆。但有这么一种情况。
在这里插入图片描述
我们六个哲学将同时处于吃饭状态,拿起了右手的筷子,当准备拿左手的筷子时,发现没有筷子可拿,都在等左边的哲学家放下筷子,可是没有哲学家放,于是都陷入了阻塞状态,这就是一种典型的死锁问题。

死锁的四个必要条件

  1. 互斥使用:线程1拿到了锁,线程2就得进行阻塞状态。
  2. 不可抢占:线程1拿到锁之后,必须是线程1主动释放,线程2不可能强行获取到
  3. 请求和保持:线程1拿到锁A后,再去获取锁B的时候,A这把锁仍然保持,不会释放
  4. 循环等待:我们第二种死锁的典型情况,线程1和线程2尝试获取锁A,B,线程1在获取锁B的时候等待线程2释放B,同时线程2在获取锁A的时候等待线程1释放A

由于synchronized的特性,前三点我们是无法改变的,想要打破死锁,我们只能从循环等待这里改变。
给锁编号,按照一个固定的顺序来加锁,我们针对银行家问题按从小到大加锁。
在这里插入图片描述
我们可以发现当该哲学家去拿左手小的1筷子的时候,发现已经被拿了,于是它进入阻塞状态。
在这里插入图片描述
另一个哲学家拿起5.6,然后放下,然后旁边的哲学家在拿起4.5放下,这样死锁问题就解决了。

我们再看一下t1,t2获取A,B的那个死锁问题,我们规定按照锁A,B的顺序进行获取。

public static void main(String[] args) {
        Object A = new Object();
        Object B = new Object();
        Thread t1 = new Thread(() -> {
            synchronized (A) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                synchronized (B) {
                    System.out.println("t1获取到了锁A和锁B");
                }
            }
        });
        Thread t2 = new Thread(() -> {
            synchronized (A) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                synchronized (B) {
                    System.out.println("t2获取到了锁A和锁B");
                }
            }
        });
        t1.start();
        t2.start();
    }

在这里插入图片描述
我们可以看到不会再出现死锁问题,t1,t2都获取到了A,B锁。

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

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

相关文章

亚马逊、ebay测评自养号新手应该要怎么做?

如今很多的跨境卖家开始在深入了解测评自养号这块,都想在运营上减低一些成本,多增加一些利润。对于整个测评工作来说,测评技术可谓是非常核心的一个环节,多学习,不要浮躁,这样才能让我们不断提高自己的测评…

Android 3D 魔方游戏的设计与开发

Android 3D 魔方游戏的设计与开发 5.1 Feature 定义 魔方是一个有趣的益智游戏,相信很多人都玩过。本次毕业设计,欲完成的主要的功能如下: (1) 开始游戏:开始一个新的游戏 (2) 返…

Java项目:食品检测管理系统(java+SSM+JavaScript+layui+Mysql)

源码获取:俺的博客首页 "资源" 里下载! 项目介绍 本项目后台食品检测管理系统; (1)用户管理:用户登录、验证。 (2)任务管理:添加任务、检品受理。 &#xff0…

数学基础从高一开始4、集合的基本运算2

数学基础从高一开始3、集合的基本运算2 目录 数学基础从高一开始3、集合的基本运算2 补集 例2: 总结: 补集 这里补集的符号我打不出来,这里就截图给大家看了啊。 下图是补集的语言表达,图形表达以及符号表达方式&#xff1a…

在无需分叉的情况下模拟任何 SIGHASH 标志

我们开发了一种新颖的方法来模拟任何 SIGHASH 标志,只需在智能合约中编写逻辑即可。它不需要更改协议,因此比每次构思新用例时通过分叉添加硬编码标志更实用和灵活。 SIGHASH 标志 SIGHASH 标志决定交易的哪一部分由签名者签名。具体来说,它…

Redis分布式锁那点事

锁超时问题 在redis分布式锁中,如果线程A加锁成功了,但是由于业务功能耗时时间很长,超过了设置的超时时间,这时候redis会自动释放线程A加的锁。通常我们加锁的目的是:为了防止访问临界资源时,出现数据异常…

【Python学习记录】Numpy广播机制(broadcast)

✨ 博客主页:小小马车夫的主页 ✨ 所属专栏:Python学习记录 文章目录一、什么是Numpy广播机制二、Numpy广播应用三、Numpy广播规则一、什么是Numpy广播机制 在Numpy、tensorflow、pytorch中数组有一种广播机制(broadcast), 就是针对两个不同形状的数组进…

【Linux】Linux调试器-gdb使用及git命令行

大家好我是沐曦希💕 文章目录一.预备知识1.背景2.Linux默认行为3.debug和release区别二.使用三.使用git命令行一.预备知识 1.背景 程序的发布方式有两种,debug模式和release模式Linux gcc/g出来的二进制程序,默认是release模式要使用gdb调试…

前端vue面试题(持续更新中)

Watch中的deep:true是如何实现的 当用户指定了 watch 中的deep属性为 true 时,如果当前监控的值是数组类型。会对对象中的每一项进行求值,此时会将当前 watcher存入到对应属性的依赖中,这样数组中对象发生变化时也会通知数据更新 源码相关 g…

Android 10.0 Launcher3双层(抽屉)高斯模糊(毛玻璃)背景功能的实现

1.概述 在进行定制开发的功能需求方面,Launcher3的需求也挺多的,单双层抽屉高斯模糊毛玻璃背景功能也是一个需求功能,最近按照功能需求来开发 双层抽屉高斯模糊毛玻璃效果背景的功能 效果图如图: 2. Launcher3双层(抽屉)高斯模糊(毛玻璃)背景功能的实现的核心代码 package…

基于Python logging 实现日志功能模块(即拿即用)

基于Python实现日志功能模块 在项目开发过程,日志文件是十分重要的,尤其对于程序员后期排查软件问题、发现问题bug及使用记录等更是非常重要。 本文使用部分软件版本如下: PyCharm 2019.3 Python 3.7.3 logging 0.5.1.2 logging logging 模块中包含为应用程序和库实现灵…

科研试剂2702973-69-9,endo BCN-PEG12-COOH,endo BCN-PEG12-acid

(本品应密封避光,储存于阴凉,干燥,通风处,取用一定要干燥,避免频繁的溶解和冻干) ●外观以及性质: endo BCN-PEG12-acid为浅黄色油状,带有 PEG 臂的试剂会增加化合物的亲…

论文投稿指南——中文核心期刊推荐(机械、仪表工业2)

【前言】 🚀 想发论文怎么办?手把手教你论文如何投稿!那么,首先要搞懂投稿目标——论文期刊 🎄 在期刊论文的分布中,存在一种普遍现象:即对于某一特定的学科或专业来说,少数期刊所含…

ElasticSearch集群部署系统参数配置调优

内存基本要求 参考书籍:[Elasticsearch: 权威指南]节选https://www.elastic.co/guide/cn/elasticsearch/guide/current/hardware.html 进程数和文件句柄数配置 使用命令查看: vi /etc/security/limits.conf如果没配置,在文件的后面加上配置 * soft n…

burst buffer技术初探

burst buffer是超算中一种作业加速技术,主要解决全球气候模拟预测建模、流体力学分析、磁性融合、天体物理学、生物分子模拟中浪涌型I/O的情况,burst buffer作为前端计算和后端存储之间的缓冲区,它弥合了计算节点的处理速度与存储系统的I/O带…

Nature:重磅,找到终结新冠的药了,关闭ACE2受体,防止所有变体

新冠病毒,通常通过呼吸道感染人类,并造成呼吸系统和人体各个器官的损伤。自2019年底首次爆发至今,新型冠状病毒仍在全球肆虐,对世界经济、社会造成极大的负面影响。随着新冠病毒的大规模流行,新的病毒突变株不断出现&a…

智能座舱开启「万物交互」新革命,隐形冠军们如何突围?

伴随智能座舱在市场端逐步深入消费者心智,从显示、语音到视觉交互,各细分赛道都在蓬勃发展。 高工智能汽车研究院监测数据显示,2022年1-10月,智能座舱前装搭载量同比增长58.06%%,高阶智能座舱同比增长137.61%&#xf…

老大难的 Java ClassLoader 再不理解就老了

ClassLoader 是 Java 届最为神秘的技术之一,无数人被它伤透了脑筋,摸不清门道究竟在哪里。网上的文章也是一篇又一篇,经过本人的亲自鉴定,绝大部分内容都是在误导别人。本文我带读者彻底吃透 ClassLoader,以后其它的相…

【网络编程】servlet和session

一、servlet 问题一:两个不同客户端请求同一个 servlet,是创建了两个一模一样的 servlet,然后用完之后全部销毁呢,还是只要一个 servlet,tomcat 开启时创建,关闭时销毁? 结论:当 Tomcat 接收…

后端存储实战课——海量数据篇

海量数据导致存储系统慢 拆,将一大坨数据拆分成 N 个小坨,学名「分片」。 归档历史数据 将大量的不常用的历史数据移到另外一张历史表中,大概流程: 批量删除大量数据 不能一次性直接删除,需要分批删除(…