锋盈数科-知识库 Logo
首页
软件开发
计算机基础
Hello Halo
新手必读
关于本知识库
登录 →
锋盈数科-知识库 Logo
首页 软件开发 计算机基础 Hello Halo 新手必读 关于本知识库
登录
  1. 首页
  2. 软件开发
  3. [synchronized ]关键字详解

[synchronized ]关键字详解

0
  • 软件开发
  • 发布于 2024-09-20
  • 0 次阅读
黄健
黄健

作者:敲代码の流川枫

博客主页:流川枫的博客

专栏:和我一起学java

语录:Stay hungry stay foolish

给大家推荐一款好用的神器
Apifox = Postman + Swagger + Mock + JMeter。集接口文档工具、接口Mock工具、接口自动化测试工具、接口调试工具于一体,提升 10 倍研发效率戳我来体验\~

目录

1.synchronized 特性

1.1互斥性

1.2内存刷新

1.3可重入

2.Java 标准库中的线程安全类

3.死锁问题

3.1 一个线程,一把锁

3.2 两个线程,两把锁

3.3 多个线程,多把锁

4.死锁的条件



1.synchronized 特性

1.1互斥性

synchronized 关键字会起到互斥效果,当某个线程执行到某个对象的synchronized中时,如果其他线程也执行到了同一个对象的synchronized了,就会阻塞等待

进入 synchronized 修饰的代码块, 相当于 加锁

退出 synchronized 修饰的代码块, 相当于 解锁

有的编程语言加锁和解锁往往是两个分开的操作,例如,加锁lock(),解锁unlock(),这样的方法缺点就是,容易忘记 写unlock(),那后果就比较严重了,别的线程就一直阻塞等待这个对象了.或者没有忘记写unlock(),但是加锁的代码中有条件语句,return等,直接就出方法了,无法执行到unlock().synchronized修饰代码块的方式就很好地解决了这个问题

如何理解阻塞等待呢?

针对每一把锁,操作系统内部都维护了一个等待队列.当这个锁被某个线程占有的时候,其他线程尝试进行加锁,就不会成功,陷入等待这个锁被释放后继续加锁的状态,这个状态就是阻塞等待状态,一直到之前的线程解锁了之后,操作系统唤醒另一个线程来获取到这个锁

1.2内存刷新

synchronized 的工作过程:

  1. 获得互斥锁

  2. 从主内存拷贝变量的最新副本到工作的内存

  3. 执行代码

  4. 将更改后的共享变量的值刷新到主内存

  5. 释放互斥锁

1.3可重入

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

锁的对象是this,线程调用add,进入方法时就会加锁,能够加上,然后又到了代码块,开始尝试加锁

问题来了,锁对象已经被加锁了,被一个线程占用了,第二次加锁是否要阻塞等待呢,并且这两个线程还是同一个线程

如果上述场景允许加第二把锁,就是可重入的,反之是不可重入的,不可重入那么就会陷入死锁状态,因为线程一直阻塞等待获取锁

java中的synchronized是可重入的

2.Java 标准库中的线程安全类

Java 标准库中很多都是线程不安全的,这些类可能会涉及到多线程修改共享数据,有没有加锁措施

有一些是线程安全的. 使用了一些锁机制来控制

还有像String类型的,不涉及到修改,就是线程安全的

既然加了锁就会安全,为什么不都加上锁,让线程安全呢,是因为加锁操作也是有额外的时间开销的,有的地方用不到锁,反而浪费时间

3.死锁问题

死锁问题一旦出现,线程就会陷入僵持等待,程序就无法执行,并且死锁非常隐蔽,难以测试

死锁问题有很多种情况,上文中死锁只是其中一种


3.1 一个线程,一把锁

就是上文的情况,一个线程,一把锁,连续加锁两次,如果是不可重入的,就会死锁

3.2 两个线程,两把锁

场景:

t1和t2两个线程先各自针对锁A和锁B加锁,再尝试获取对方的锁

这个场景就像有个人的车钥匙锁在房子里了,房子钥匙锁在车里了

来看这种情况的代码

public class ThreadDemo15 {
    public static void main(String[] args) {
        Object locker1 = new Object();
        Object locker2 = new Object();

        Thread t1 = new Thread(()->{
            synchronized (locker1){

                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (locker2){
                    System.out.println("t1获取到了两把锁");
                }
            }
        });

        Thread t2 = new Thread(()->{
           synchronized (locker2){
               try {
                   Thread.sleep(1000);
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
               synchronized (locker1){
                   System.out.println("t2获取到了两把锁");
               }
           }
        });

        t1.start();
        t2.start();
    }
}

结果什么都没有打印,就代表着,两个线程都没有执行到获取两把锁这里的代码,也就是一直在僵持不下,互相等待对方释放自己需要的锁,出现了相互阻塞的现象!!!

使用jconsole查看一下线程的情况

两个线程都出现了BLOCKED状态, t1是阻塞到17行代码,t2阻塞到30行这里,因为相互都在等待释放锁,两个线程会一直处于这个阻塞状态

因此我们也可以使用jconsole工具来定位死锁,查看线程的状态和调用栈,就可以分析出哪里死锁了 !!

解决方法:下文理解锁的条件后就能解决这种死锁情况

3.3 多个线程,多把锁

经典案例就是"哲学家就餐问题”

哲学家就餐问题可以这样表述,假设有五位哲学家围坐在一张圆形餐桌旁,做以下两件事情之一:吃饭,或者思考。吃东西的时候,他们就停止思考,思考的时候也停止吃东西。餐桌中间有一大碗意大利面,每两个哲学家之间有一只餐叉。因为用一只餐叉很难吃到意大利面,所以假设哲学家必须用两只餐叉吃东西。他们只能使用自己左右手边的那两只餐叉。哲学家就餐问题有时也用米饭和筷子而不是意大利面和餐叉来描述,因为很明显,吃米饭必须用两根筷子

哲学家从来不交谈,这就很危险,可能产生死锁,每个哲学家都拿着左手的餐叉,永远都在等右边的餐叉(或者相反)。即使没有死锁,也有可能发生资源耗尽,例如,假设规定当哲学家等待另一只餐叉超过五分钟后就放下自己手里的那一只餐叉,并且再等五分钟后进行下一次尝试.这个策略消除了死锁(系统总会进入到下一个状态),但仍然有可能发生”活锁“。如果五位哲学家在完全相同的时刻进入餐厅,并同时拿起左边的餐叉,那么这些哲学家就会等待五分钟,同时放下手中的餐叉,再等五分钟,又同时拿起这些餐叉

如果出现了极端的情况,就陷入死锁

同一时刻,所有的哲学家都拿起来左手的筷子,此时所有的哲学家都拿不起右手的筷子,都要等待右边的哲学家放下筷子

4.死锁的条件

1.互斥使用:线程1拿到了锁,线程2如果想要获取锁就必须阻塞等待

2.不可抢占:线程1获取到锁之后,其他线程不能强行获取到

3.请求和保持:线程1拿到锁A之后,再尝试获取锁B时,A这把锁还是保持的,不会因为这个释放了A

4.循环等待:线程1尝试获取锁A和B,线程2在尝试获取锁B和锁A.

线程1尝试获取锁B时等待线程2释放B,线程2在尝试获取锁B时等待线程1释放锁A

四个条件同时具备才会死锁!!前三个都是锁的基本特性,循环等待是最关键的!

循环等待是四个条件中唯一一个和代码结构相关的,也是可以被程序员控制的,为了避免循环等待,突破口就是循环等待!!可以给锁编号,然后指定一个固定的顺序来加锁,任意线程加多把锁的时候,都让线程遵守顺序,循环等待自然破除!

给筷子编号,规定每次拿较小号的筷子,假设4个哲学家都拿起筷子,那么和5相邻的两个哲学家肯定要阻塞一个,必然还剩一只5号筷子,拿到较小号筷子的哲学家就可以拿5号筷子就餐,另一位哲学家阻塞等待!破除了循环等待避免死锁

再来看上文提到的,如何让t1t2都获取到A锁和B锁?也是对锁进行编号,让他们都先获取A锁,再获取B锁!

Thread t1 = new Thread(()->{
            synchronized (locker1){

                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (locker2){
                    System.out.println("t1获取到了两把锁");
                }
            }
        });

        Thread t2 = new Thread(()->{
           synchronized (locker1){
               try {
                   Thread.sleep(1000);
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
               synchronized (locker2){
                   System.out.println("t2获取到了两把锁");
               }
           }
        });

改动t2线程获取锁的顺序,也先获取A锁,后获取B锁

此时两个线程都获取到了两把锁,解决了死锁问题!


原文链接: https://blog.csdn.net/chenchenchencl/article/details/128230691

标签: #软件开发 1171
相关文章

万字:支付“核心系统”详解 2024-11-02 15:33

专栏作者:隐墨星辰 \| 主编:陈天宇宙 这篇文章也尝试化繁为简,探寻支付系统的本质,讲清楚在线支付系统最核心的一些概念和设计理念。 虽然支付行业已经过了风头最劲的时光,但跨境支付仍然在蓬勃发展,每年依然有很多新人进入这个行业,这篇文章尝试为这些刚入行的新人提供一点帮助。 文章只介绍一些支付行业十几

资深支付架构师视角:实战从问题定义到代码落地的完整套路 2024-11-02 15:33

前言 今天从一个实际案例入手,介绍站在架构师的角度,如何识别并定义问题,提炼需求,技术方案选型,再到详细设计,最后利用AI的能力协助写出核心的代码,验证与调优。 解决问题存在一定的模式,也可以称之为框架,总结出自己的思考和解题框架,以后再碰到同类型的问题就可以如庖丁解牛一样容易。 很多年前,我写代码

Spring 实现 3 种异步接口 2024-10-18 09:07

大家好,我是苏三~ 如何处理比较耗时的接口? 这题我熟,直接上异步接口,使用 Callable、WebAsyncTask 和 DeferredResult、CompletableFuture等均可实现。 但这些方法有局限性,处理结果仅返回单个值。在某些场景下,如果需要接口异步处理的同时,还持续不断地

重学SpringBoot3-集成Redis(五)之布隆过滤器 2024-10-08 11:24

更多SpringBoot3内容请关注我的专栏:《SpringBoot3》 期待您的点赞👍收藏⭐评论✍ 重学SpringBoot3-集成Redis(五)之布隆过滤器 1. 什么是布隆过滤器? * 基本概念 适用场景 2. 使用 Redis 实现布隆过滤器 * 项目依赖 Redis 配置

设计模式第16讲——迭代器模式(Iterator) 2024-10-08 11:24

一、什么是迭代器模式 迭代器模式是一种行为型设计模式,它提供了一种统一的方式来访问集合对象中的元素,而不是暴露集合内部的表示方式。简单地说,就是将遍历集合的责任封装到一个单独的对象中,我们可以按照特定的方式访问集合中的元素。 二、角色组成 抽象迭代器(Iterator):定义了遍历聚合对象所需的方法

vue2路由和vue3路由区别及原理 2024-10-08 11:24

一、Vue2 与 Vue3 路由的区别 1. 创建路由实例方式的不同 Vue 2 中,通过 Vue.use() 注册路由插件,并通过 new VueRouter() 来创建路由实例。 import Vue from 'vue';import VueRouter from 'vue-router';i

目录

IT 外包服务商

  • 意见投递
  • zyf6619

软件开发应用

主菜单

  • 首页
  • 软件开发
  • 计算机基础
  • Hello Halo
  • 新手必读
  • 关于本知识库
Copyright © 2024 your company All Rights Reserved. Powered by Halo.