我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Java synchronized与死锁深入探究

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

Java synchronized与死锁深入探究

1.synchronized的特性

1). 互斥性

当某个线程执行到 synchronized 所修饰的对象时 , 该线程对象会加锁(lock) , 其他线程如果执行到同一个对象的 synchronized 就会产生阻塞等待.

  • 进入 synchronized 修饰的代码块 , 相当于加锁.
  • 退出 synchronized 修饰着代码块 , 相当于解锁.

synchronized 使用的锁存储在Java对象里 , 可以理解为每个对象在内存中存储时 , 都有一块内存表示当前的锁定状态.类似于公厕的"有人" , "无人".

如果是"无人"状态 , 此时就可以使用 , 使用时需设置为"有人"状态.

如果是"有人"状态 , 此时就需要排队等待.

如果理解阻塞等待?

针对每一把锁 , 操作系统都会维护一个等待队列 , 当一个线程获取到这个锁之后 , 其他线性再尝试获取这个锁 , 就会获取不到锁 , 陷入阻塞等待. 一直等到之前这个线程释放锁后 , 操作系统才会唤醒其他线程来再次竞争这个锁.

2)可重入

synchronized 对同一个线程来说是可重入的 , 不会出现把自己锁死的情况.

如何理解把自己锁死?

观察下面这段代码可以发现 , 当某个线程调用add方法时 , 就会对 this 对象先加锁 , 接着进入代码块又会对 this 对象再次尝试加锁. 站在 this 对象的角度 , 它认为自己已经被另外的线程占用了 , 那么第二次加锁是否需要阻塞等待呢? 如果运行上述情况 , 那么这个锁就是可重入的 , 否则就是不可重入的.不可重入锁会导致出现死锁 , 而Java中的 synchronized 是可重入锁 , 因此没有上述问题.

synchronized public void add(){
        synchronized (this) {
            count++;
        }
    }

在可重入锁内部 , 包含了"线程持有者"和"计数器"两个信息.

  • 如果每个线程加锁时 , 发现锁以及被占用了 , 但加锁的人是它自己 , 那么仍然可以获取到锁 , 让计数器自增.
  • 解锁的时候当计数器递减到0时 , 才真正释放锁.

2.synchronized使用示例:

1). 修饰普通方法

锁的是 Counter 对象.

class Counter{
    public int count;
    synchronized public void add(){
            count++;
        }
}

2). 修饰静态方法

锁的是 Counter 类

class Counter{
    public int count;
    synchronized public static void add(){
            count++;
        }
}

3).修饰代码块.明确指定锁哪个对象

锁当前对象:

class Counter{
    public int count;
    public void add(){
        synchronized (this) {
            count++;
        }
    }
}

锁类对象:

class Counter{
    public int count;
    public void add(){
        synchronized (Counter.class) {
            count++;
        }
    }
}

类锁和对象锁有什么区别?

顾名思义 , 对象锁用来锁住当前对象 , 类锁用来锁住当前类.如果一个类有多个实例对象 , 那么如果对其中一个对象加锁 , 别的线程只会在访问这个对象时阻塞等待 , 访问其他对象时没有影响.但如果是类锁 , 那么当一个线程对这个类加锁后 , 其他线程访问该类的所有对象都要阻塞等待.

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

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

  • ArrayList
  • LinkedList
  • HashMap
  • HashSet
  • TreeSet
  • StringBuilder

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

  • Vector
  • HashTable
  • CurrentHashMap
  • StringBuffer
@Override
    @IntrinsicCandidate
    public synchronized StringBuffer append(String str) {
        toStringCache = null;
        super.append(str);
        return this;
    }

这些线程之所以不加锁是因为 , 加锁会损失部分性能.

4.死锁是什么

死锁是这样一种情况 , 多个线程同时被阻塞 , 其中一个或全部都在等待某个资源被释放.由于线程被无限期的阻塞 , 因此程序不可能正常终止.

死锁的三个典型情况

1). 一个线程一把锁 , 连续加两次 , 如果锁是不可重入锁 , 就会死锁. Java中的synchronized和ReentranLock 都是可重入锁 , 因此不会出现上述问题.

2). 两个线程两把锁 , t1 和 t2 线程各种先针对锁A和锁B加锁 , 再尝试获取对方的锁.

例如 , 张三和女神去吃饺子 , 需要蘸醋和酱油 , 张三拿到醋 , 女神拿到酱油 , 张三对女神说:"你先把酱油给我 , 我用完就把醋给你" , 女神对张三说:"你先把醋给我 , 我用完就把酱油给你". 这时两人争执不下 , 就构成了死锁 , 醋和酱油就是两把锁 , 张三和女生就是两个线程.

public static void main(String[] args) {
        Object jiangyou = new Object();
        Object cu  = new Object();
        Thread zhangsan = new Thread(()->{
            synchronized (jiangyou){
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                synchronized (cu ){
                    System.out.println("张三把酱油和醋都拿到了");
                }
            }
        });
        Thread nvsheng = new Thread(()->{
            synchronized (cu ){
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                synchronized (jiangyou){
                    System.out.println("女神把酱油和醋都拿到了");
                }
            }
        });
        zhangsan.start();
        nvsheng.start();
    }

执行代码后 , 发现没有打印任何日志 , 说明没有线程拿到两把锁.

通过jconsole查看线程的情况:

3)多个线程多把锁

例如常见经典案例--"哲学家就餐问题"

假设有五个哲学家围着桌子吃饭 , 每个人中间放一个筷子 , 哲学家有两种状态 , 1.思考人生(相当于线程的阻塞状态) , 2.拿起筷子吃面条(相当于线程获取到锁执行计算) , 由于操作系统的随机调度 , 这五个哲学家随时都可能想吃面条 , 也随时都可能思考人生 , 但是想要吃面条就得同时拿起左右两个筷子.

假设同一时刻 , 所有哲学家同时拿起左手的筷子 , 所有的哲学家都拿不起右手的筷子 , 就会产生死锁.

死锁是一个严重的"BUG" , 导致一个程序的线程"卡死"无法正常工作.

5.如果避免死锁

死锁的四个必要条件:

1.互斥使用: 当资源被一个线程占有时 , 别的线程不能使用

2.不可抢占: 资源请求者不能从资源获取者手中夺取资源 , 只能等资源占有者主动释放.

3.请求和保持: 当资源请求者请求获取别的资源时 , 保存对原有资源的占有.

4.循环等待: 即存在一个等待队列 , P1占有P2的资源 , P2占有P3的资源 , P3占有P1的资 源, 这样就形成一个等待回路.

当上述四个条件都成立就会形成死锁 , 当然破坏其中一个条件也可以打破死锁 , 对于synchronized 来说 , 前三个条件是锁的基本特性 , 因此想要打破死锁只能从"循环等待"入手.

如何破除死锁?

如果我们给锁编号 , 然后指定一个固定的顺序来加锁(必然从小到大) , 任意线程加多把锁的时候都遵循上述顺序, 此时循环等待自然破除.

因此解决哲学家就餐问题就可以给每个筷子编号 , 每个人都遵守"先拿小的再拿大的顺序".此时1号哲学家和2号哲学家为了竞争筷子其中一个人就会阻塞等待 , 这时5号哲学家就有了可乘之机 , 5号哲学家拿起4号和5号筷子吃完面条 , 四号哲学家重复上述操作也吃完面条 , 这样就完美的打破了循环等待的问题.

同样 , 最初的张三和女神吃饺子问题也是同样的解决方式 , 规定两人都按"先拿醋再拿饺子"的顺序执行 , 就可以完美解决死锁问题.

public static void main(String[] args) {
        Object jiangyou = new Object();
        Object cu  = new Object();
        Thread zhangsan = new Thread(()->{
            synchronized (cu){
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                synchronized (jiangyou ){
                    System.out.println("张三把酱油和醋都拿到了");
                }
            }
        });
        Thread nvsheng = new Thread(()->{
            synchronized (cu ){
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                synchronized (jiangyou){
                    System.out.println("女神把酱油和醋都拿到了");
                }
            }
        });
        zhangsan.start();
        nvsheng.start();
    }

到此这篇关于Java synchronized与死锁深入探究的文章就介绍到这了,更多相关Java synchronized 内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

Java synchronized与死锁深入探究

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

Java synchronized与死锁深入探究

这篇文章主要介绍了Java synchronized与死锁,Java中提供了synchronized关键字,将可能引发安全问题的代码包裹在synchronized代码块中,表示这些代码需要进行线程同步
2023-01-30

Java 多线程同步 锁机制与synchronized深入解析

从尺寸上讲,同步代码块比同步方法小。你可以把同步代码块看成是没上锁房间里的一块用带锁的屏风隔开的空间
2022-11-15

深入理解java内置锁(synchronized)和显式锁(ReentrantLock)

synchronized 和 Reentrantlock多线程编程中,当代码需要同步时我们会用到锁。Java为我们提供了内置锁(synchronized)和显式锁(ReentrantLock)两种同步方式。显式锁是JDK1.5引入的,这两种
2023-05-30

深入探究Java @MapperScan实现原理

之前是直接在Mapper类上面添加注解@Mapper,这种方式要求每一个mapper类都需要添加此注解,麻烦。通过使用@MapperScan可以指定要扫描的Mapper类的包的路径,这篇文章深入探究Java @MapperScan的实现原理
2023-01-04

深入详解Java中synchronized锁升级的套路

synchronized锁是啥?锁其实就是一个对象,随便哪一个都可以,Java中所有的对象都是锁,换句话说,Java中所有对象都可以成为锁。本文我们主要来聊聊synchronized锁升级的套路,感兴趣的可以收藏一下
2023-05-15

Java 内存模型与死锁:深入理解并发编程中的死锁问题

本文深入探讨 Java 内存模型与死锁问题之间的关联,并以示例代码阐释死锁的成因和解决方法,旨在帮助读者深入理解并发编程中的死锁问题。
Java 内存模型与死锁:深入理解并发编程中的死锁问题
2024-02-04

JavaScript深拷贝与浅拷贝原理深入探究

深拷贝和浅拷贝是面试中经常出现的,主要考察对基本类型和引用类型的理解深度,这篇文章主要给大家介绍了关于js深拷贝和浅拷贝的相关资料,需要的朋友可以参考下
2022-11-13

深入探究Java原型模式的魅力

Java原型模式是一种创建型设计模式,它通过复制现有对象的实例来创建新的对象实例,在本篇博客中,我们将详细介绍Java原型模式的原理、实现方式、优缺点以及适用场景等方面,需要的朋友可以参考下
2023-05-20

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录