Java 多线程 —— 死锁与锁的错误用法

引言

死锁状态的大致情况是:Thread_1在获得A对象的锁后,紧接着去请求B对象的锁 ,Thread_2在获得了B对象的锁后,紧接着又去请求A对象的锁,如下图:

 一、模拟一个死锁

public class DeadLockDemo {static class A {public synchronized void saying() {System.out.println(Thread.currentThread().getName() + " A start...........");try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}new B().saying();System.out.println(Thread.currentThread().getName() + " A end.............");}}static class B {public synchronized void saying() {System.out.println(Thread.currentThread().getName() + " B start...........");try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}new A().saying();System.out.println(Thread.currentThread().getName() + " B end.............");}}public static void main(String[] args) {new Thread(() -> new A().saying(), "t1").start();new Thread(() -> new B().saying(), "t2").start();}
}

可以看到在线程 t1 调用A对象的saying互斥方法的时候,t1拿到了A对象的锁,而如果想完成saying方法必须去请求B对象的锁才可以执行到B对象的saying互斥方法。线程 t2调用B对象的saying互斥方法的时候,t2拿到了B对象的锁,而如果想完成saying方法必须去请求A对象的锁才可以执行到A对象的saying互斥方法。 

这就导致了死锁的出现,程序会陷入无休止的“死循环”中。

如果没有2秒的睡眠时间,程序会很快因内存溢出而瘫痪:

否则程序会不停的循环下去,直到崩溃。

二、synchronized 锁的错误用法

使用synchronized 并不简单,以下这些用法一定要在实际开发中注意避免。

2.1 synchronized 锁定字符串对象

synchronized 可以给对象加锁,但这些对象不应该包括 String、Integer 这类共享对象。说String、Integer是共享对象,是因为在某些情况下,Java会共享一些数据来提高性能和节约内存。

例如,一个字符串 "Hello",如果以此对象为锁定目标,那么就可能在非常不恰当的位置造成线程阻塞或死锁:

public class T {String s1 = "Hello";String s2 = "Hello";void m1() {synchronized (s1) {}}void m2() {synchronized (s2) {}}
}

如上代码所示,s1 和 s2 虽然声明了两个变量,但实际上,"Hello" 字符串是共享的,因此锁也是一份,如果你不希望造成莫名其妙的线程阻塞,一定要记得synchronized 不要加在 String、Integer 这类对象上。

2.2 锁对象的引用被重新赋值

理解这个问题需要清楚synchronized加锁的目标对象是什么,究竟是栈内存中的引用?还是堆内存中的对象数据?

我们保证同步的目的是有序的执行堆中的数据,所以很明显,synchronized 锁定的应该是堆内存中实际的对象,而不是栈中的引用。

那么如果引用被重新赋值,那么整个并发程序可能造成更加难以排查的问题:

public class T_ChangeLock {private Object lock = new Object();public void doSync() {synchronized (lock) {while (true) {try {TimeUnit.SECONDS.sleep(1);// 打印当前线程System.out.println(Thread.currentThread().getName());} catch (InterruptedException e) {}}}}public static void main(String[] args) throws InterruptedException {T_ChangeLock t = new T_ChangeLock();new Thread(() -> t.doSync(), "t1").start();// 启动第一个线程TimeUnit.SECONDS.sleep(1);// 锁对象改变t.lock = new Object();// 想一想,t2 是否可以被成功阻塞?new Thread(() -> t.doSync(), "t2").start();}
}

doSync 是个同步方法,方法内死循环输出当前线程ID,t1首先抢到 doSync 的执行权(即抢到 lock 锁对象),不出意外的话,其他线程都将无法执行 doSync 方法,然而,在执行了 lock = new Object() 方法后,奇怪的事情发生了: 

所以,为了不让你的同步逻辑失效,请谨慎处理锁对象的引用。

 

 

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

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

相关文章

Java零基础并发编程入门

Java零基础并发编程入门并发编程主要包括: 线程,同步,future,锁,fork/join, volatile,信号量,cas(原子性,可见性,顺序一致性)&#xf…

Java内置锁——synchronized

一、给对象加把锁 synchronized关键字是Java唯一内置的互斥锁,通过关键字 synchronized 可以保证同一时刻只有一个线程获得某个同步代码块的执行权,但不会导致其他线程执行非同步方法时阻塞。 当获得锁的线程执行完同步代码块后,线程会将锁…

处理对象(toString()方法详解和==与equals方法的区别)

处理对象(toString()方法详解和与equals方法的区别)toString()是一个非常特殊的方法,它是一个自我描述的方法。当程序员直接打印该对象的时候,系统会输出该对象的“自我描述”的信息…

Java并发编程实战——volatile

引言 Java 语言提供了一种弱同步机制——volatile 变量。它的作用是确保将变量的更新操作通知到其他线程。 当把变量声明为volatile后,编译器和运行时都会注意到这个变量是共享的,因此不会将该变量上的操作与其他内存操作一起重排序。 另外&#xff0…

Java中类的加载顺序介绍(ClassLoader)

Java中类的加载顺序介绍(ClassLoader)1、ClassNotFoundExcetpion   我们在开发中,经常可以遇见java.lang.ClassNotFoundExcetpion这个异常,今天我就来总结一下这个问题。对于这个异常,它实质涉及到了java技术体系中的类加载。Java的类加载机…

UP装机部署步骤大纲

Linux装机 插上网线,然后,Ubuntu系统安装(略) 更改root密码 以装机时设置的用户登陆后,sudo修改root用户密码,然后退出,重新登录root。 $ sudo passwd root 下载并安装JDK $ java (根据提…

Java 多线程 —— wait 与 notify

引言 认识一下 Object 类中的两个和多线程有关的方法:wait 和 notify。 wait,当前线程进入 WAITING 状态,释放锁资源。 notify,唤醒等待中的线程,不释放锁资源。 一、使用 wait-notify 实现一个监控程序 实现一个…

重写equal()时为什么也得重写hashCode()之深度解读equal方法与hashCode方法渊源

重写equal()时为什么也得重写hashCode()之深度解读以及equal方法与hashCode方法渊源转载自:http://blog.csdn.net/javazejian/article/details/51348320 今天这篇文章我们打算来深度解读一下equal方法以及其关联方法hashCode(),我们准备从以下几点入手分…

Java8————Optional

引言 Optional 类是一个可以为null的容器对象。如果值存在则isPresent()方法会返回true,调用get()方法会返回该对象。 Optional 是个容器:它可以保存类型T的值,或者仅仅保存null。Optional提供很多有用的方法,这样我们就不用显式进…

问题反馈信息处理平台开发过程

问题反馈信息处理平台开发过程 “问题反馈信息处理平台”是一个将用户反馈上来的出错信息进行处理和收集的一个平台。 这个项目主要都是在实习的时候由我一个人进行开发,我导师在旁边进行指导完成的。 该项目的技术架构主要是: 前端主要基于Vue框架的…

Java8————Base64

Base64? Base64是一种用64个字符来表示任意二进制数据的方式。 对于二进制文件如图片、exe、音频、视频等,包含很多无法显示和打印的字符,如果希望能够通过记事本这样的文本处理软件处理二进制数据,就需要一个二进制转字符串的转…

大众点评后端项目解析

restful Api是一种设计风格: 启动前端项目:在前端项目根目录 npm install:加载依赖包 npm run mock:提供模拟数据的接口,前端脱离于后台;start /b npm run mock(于后台运行) npm…

Java中类及方法的加载顺序

代码展示 请运行下面代码,查看运行结果,并带着问题,尝试第二次debug程序。 class A {private static int numA;private int numA2;static {System.out.println("A的静态字段 : " numA);System.out.println("A的静态代码块…

新手入门教程-------Spring Boot中集成RabbitMQ

AMQP:是Advanced Message Queuing Protocol的简称,高级消息队列协议,是一个面向消息中间件的开放式标准应用层协议。 定义了以下特性: 消息方向消息队列消息路由(包括:点到点和发布-订阅模式)可…

Java 多线程 —— ReentrantLock 与 Condition

引言 ReentrantLock 是 JUC 下的一个功能强劲的锁工具,支持公平锁、非公平锁,以及多等待队列的 Condition 。 也常常被称为“手动锁”。本篇博客主要分析它的使用方法以及 Condition 实现的一个生产者消费者模式。 一、可替代 synchronized 的手动锁 …

Rabbitmq+Springboot设计秒杀应用

秒杀业务的核心是库存处理,用户购买成功后会进行减库存操作,并记录购买明细。当秒杀开始时,大量用户同时发起请求,这是一个并行操作,多条更新库存数量的SQL语句会同时竞争秒杀商品所处数据库表里的那行数据&#xff0c…

Java8————Stream API

引言 Java8 加入了java.util.stream包,这个包中的相关API将极大的增强容器对象对元素的操作能力。 它专注于对集合对象进行各种便利、高效的聚合操作,或大批量数据处理。 Stream API借助于同样新出现的Lambda表达式,极大的提高了编程效率和…

MySQL数据库知识点总结

数据库: 数据库索引的好处:索引是对数据库表中的一个或多个列的值进行排序的结构,这样检索或者查询某条记录的时候就不在是顺序查找,而是使用特定的查找方式进行查找,如通过二分查找或者是hash值来查找,提高…

Java8 函数式对齐约定————Eclipse自定义代码风格

引言 Java8 的函数式代码风格在Stream的使用上尤为突出。尽管我们可以通过连续调用函数完成一系列操作,但是其可读性并不能保证,还需要有与之相辅的Code Style。例如,请尝试阅读下面两段完全相同的代码: 未遵守约定格式&#xf…

Java核心篇之JVM--day3

Java核心篇之JVM--day3 Java JVM详解--通俗易懂教程 JVM:Java虚拟机的简称。 谈到JVM,通常会聊到三个问题: 1. 什么时候触发Java GC? 2. 对什么东西进行Java GC? 3. 如何进行Java GC? 首先解决第…