synchronized和死锁介绍

news2024/11/27 21:38:31

    • synchronized特性
    • synchronized使用
      • 修饰普通方法(对象锁)
      • 修饰静态方法(类锁)
      • 修饰代码块(明确指定锁的对象)
      • 非锁竞争情况
    • 死锁
      • 死锁是什么?
      • 死锁的必要条件
      • 循环等待场景
      • 程序死锁怎么排除
      • 死锁问题怎么解决
    • 标准库的线程安全类
    • Java多线程是如何实现数据共享

前面介绍到线程安全问题:线程安全问题;线程安全问题怎么解决呢?synchronized是一大重点

synchronized特性

synchronized是个对象加锁;进入 synchronized 修饰的代码块, 相当于加锁;退出 synchronized 修饰的代码块, 相当于解锁。这个锁是存在对象头里的;可以理解, 每个对象在内存中存储的时候, 都存有一块内存表示当前的 “锁定” 状态;加锁了则表示有人。
1:互斥:
理解为每一把锁内部维护一个等待对象;锁被某个线程占用;其它线程尝试进行加锁就会阻塞等待。等改线程释放锁后;才由操作系统唤醒被阻塞的线程来获取这个锁。
注意:假设有 A B C 三个线程, 线程 A 先获取到锁, 然后 B 尝试获取锁, 然后 C 再尝试获取锁,此时 B 和 C 都在阻塞队列中排队等待. 但是B和C谁先获取到这把锁是不知道的;看操作系统怎么调度。

2:刷新内存
synchronized 也能保证内存可见性的问题;当一个线程进入一个Synchronized方法或块时,它会获取一个锁,并且在释放锁之前,会将其所有的本地内存的改动刷新到主内存中。同时,当另一个线程进入一个Synchronized方法或块时,它会从主内存中读取变量的最新值到自己的本地内存。

3:可重入
同一个线程两次加同一把锁;不会死锁。比如:你在上厕所把门锁了;突然有人时空回溯把你传回5分钟前在厕所门口的情况;然后你发现门是锁的。synchronized就是自己带了钥匙。

static class Counter {
    public int count = 0;
    synchronized void increase() {
        count++;
   }
    synchronized void increase2() {
        increase();
   }
}

在可重入锁的内部, 包含了 “线程持有者” 和 “计数器” 两个信息;当发现加锁时占用锁的是自己可以继续获取
到锁, 并让计数器自增。解锁的时候计数器递减为 0 的时候, 才真正释放锁.;才能被别的线程获取到。

synchronized使用

修饰普通方法(对象锁)

public class SynchronizedDemo {
    public synchronized void methond() {
   }

修饰静态方法(类锁)

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

锁定的是类的Class对象,而不是类的某个实例。
如果现在一个类有两个方法;add1和add2都是静态方法;其中add1这个方法加了synchronized。如果我两次线程一个调用add1一个调用add2;会不会锁竞争?
当然不会;因为一个线程在加锁;一个线程在不加锁这个类对象。

修饰代码块(明确指定锁的对象)


public class SynchronizedDemo {

    public void method() {
    Object obj=new Object();
        synchronized (obj) {
            
       }
   }
}

总结:两个线程针对同一个对象加锁才会产生锁竞争;有锁竞争线程才安全;一定是锁的对象相同。

修饰普通方法:
加锁的是实例对象,谁调用谁就是加锁的对象,线程里也是通过对象调用这个方法的。
修饰静态方法:
Synchronzied 修饰静态方法==》其实是类锁,因为是静态方法,它把整个类锁起来了; 两个线程都用这个类名.方法名;那就会锁竞争。

非锁竞争情况

情况1:一个加锁一个没加锁:;虽然是同一个对象;但是线程2没加锁就起不到一个阻塞的效果

class counter3{
  static   int count=0;
synchronized   public  static void add1(){
        count++;
    }
   public  static void add2(){
        count++;
    }


} //执行结果是100000

public class test3 {//一个加锁,一个不加锁情况

    public static void main(String[] args) throws InterruptedException {
        counter3 c3=new counter3();//修饰静态方法时是类锁,即便用两个不同的对象调用,最终调用的还是类名.方法。
        counter3 c33=new counter3();
        Thread t1=new Thread(()->
        {
            for (int i = 0; i <50000 ; i++) {
                c3.add1();//调用的时候就会进行加锁
            }

        }
                );
        Thread t2=new Thread(()-> {
            for (int i = 0; i <50000 ; i++) {
                c33.add2();
            }

        }
        );
		t1.start();
		t2.start();
		t1.join();//等待线程执行完再去打印count的值
		t2.join();
		        System.out.println(counter3.count);


    }
}


总结:
一个有锁;一个无锁;没有起到原子性线程安全的意义。相当于都没加锁。一个add1有修饰,一个add2没有修饰。两个线程用c对象分别调用这两个方法,都是执行count++。所以它们还是同步执行的;会原子性问题。

情况2:同一个对象;多线程下操作同一个变量一个有锁;一个无锁。

class counter{
    int count=0;
synchronized   public void add1(){
        count++;
    }

  public void add2() {
    count++;

    }
}
public class test1 {//线程安全问题测试。

    public static void main(String[] args) throws InterruptedException {
        counter c=new counter();
        Thread t1=new Thread(()->
        {
            for (int i = 0; i <50000 ; i++) {
                c.add1();
            }

        }
                );
        Thread t2=new Thread(()->
        {
            for (int i = 0; i <50000 ; i++) {
                c.add2();
            }

        }
        );
		t1.start();
		t2.start();
		t1.join();//等待线程执行完再去打印count的值
		t2.join();
	    System.out.println(c.count);


    }
}


就像这个代码,相当于都没加锁的效果,结果依然是不确定的。在这里插入图片描述

三条结论:
1:两个线程针对同一个对象加锁,锁竞争能解决我们上述问题
2:两个线程针对不同对象加锁,不会锁竞争,解决不了上述问题
3:两个线程一个加锁一个不加锁。不会锁竞争,解决不了上述问题(同一个对象;两个方法add1、add2都操作static的count++;而一个有加锁、一个没加锁;那就出问题)
加锁本质上是cpu提供加锁这样的指令,操作系统实现锁,操作系统锁的api提供给JVM,JVM就提供给synchronized

死锁

为了解决线程安全问题引入锁的概念;然后引入锁的概念就可能会出现死锁的问题

死锁是什么?

死锁指的是两个或多个运行的线程或进程因争夺资源而造成的一种僵局。比如:所有线程或进程都在等待某个资源,但这个资源又被另一个线程或进程占有,因此无法继续执行。

死锁的必要条件

1:互斥使用;当资源(锁)被一个线程占有时,别的线程不能使用。(如果别人都能用了;那肯定就不会死锁)
2:不可抢占;当别人把这个资源(锁)占有;你只能等它主动释放;不能把它的给抢了。
3:持有和等待:当资源请求者在请求其他的资源的同时保持对原有资源的占有。比如:线程1请求这个被线程2占用的锁;当我线程2占用这个锁;我去请求线程3的资源;线程1得一直等着;线程2不会释放这个锁的。
4:循环等待;存在一个等待循环,每个线程都在等待下一个线程所持有的资源。比如:P1占有P2的资源,P2占有P3的资源,P3占有P1的资源。形成了一个等待环路。

循环等待场景

1:哲学家就餐问题:先不考虑卫不卫生的问题。5个哲学家5根筷子,哲学家两种状态:吃饭和思考。如果每个哲学家同时都拿着左手的筷子,并在等右边的筷子,就会有死锁的风险。都等不到对方释放左边筷子的时候。

2:两个线程两把锁,t1和t2加锁,互相获取对方的锁。都等不到对方释放锁的时候。

public class ThreadDemo14 {
    public static void main(String[] args) {
        Object x = new Object();
        Object y = new Object();

        Thread t1 = new Thread(() -> {
            synchronized (x) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                synchronized (y) {
                    System.out.println("abc");
                }
            }
        });
        Thread t2 = new Thread(() -> {
            synchronized (y) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                synchronized (x) {
                    System.out.println("123");
                }
            }
        });
        t1.start();
        t2.start();
    }
}

3:一个线程,一把锁;连续加锁两次,如果锁是不可重入锁就会死锁。
为什么呢?
不可重入锁:锁不跟踪它是由哪个线程持有的;它只是简单地考虑是否被锁定。当你这个线程已经持有锁并且尝试再次加锁时;你只能慢慢的阻塞等等别人把这个锁释放;但是这个锁是你自己加的;你得自己释放;然后你把自己阻塞了;就不会有释放的那一天。比如自旋锁是不可重入的。

程序死锁怎么排除

比如:两个线程两把锁,t1和t2加锁,互相获取对方的锁
在这里插入图片描述

需要加个sleep才会出现这种情况。确保两个线程先把第一个锁获取到。因为这里如果不休眠一下,调度速度很快,线程1给第一个x加锁,然后马上就给y加锁。线程2就先进不去阻塞等待到线程1的解锁再继续加。如果不加sleep就产生不了相互访问锁的情况。

死锁是很隐蔽的一个问题;在程序不容易测试出来;所以我们可以在jconsole查看一下线程在搞什么飞机。
在这里插入图片描述

在这里插入图片描述
可以看到两个对方都显示在阻塞状态,并且告诉你阻塞在14行和26行。等待数,表示等待一个线程的解锁。
怎么解决这个问题呢?

死锁问题怎么解决

给锁加一个编号按顺序加锁;上面的问题是;线程1先给x加锁后给y加锁;线程2先给y加锁再给x加锁。如果两个线程同时先给x加锁;再给y加锁;这样子另一个线程就会等待先给x加锁的释放后才能加上锁。x锁释放了;y就自然也释放了。阻塞在第一步。

标准库的线程安全类

多个线程操作同一个集合类就需要考虑到线程安全问题。
在这里插入图片描述
在这里插入图片描述

一般都是我们需要加锁的自己加,因为加锁会有额外的时间开销。String虽然没加锁,但是它是不可变性,也是线程安全的。

Java多线程是如何实现数据共享

JVM 把内存分成了这几个区域:方法区, 堆区, 栈区, 程序计数器.
其中堆区这个内存区域是多个线程之间共享一份的;只要把某个数据放到堆内存中, 就可以让多个线程都能访问到。

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

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

相关文章

led驱动电源模块能进行自动化测试吗,具体测试方法,流程是?

LED电源模块测试的痛点主要包括以下几点&#xff1a; 测试效率低&#xff1a;传统的LED电源模块测试方法通常采用人工操作&#xff0c;测试效率低下&#xff0c;且易出错。 测试项目不全面&#xff1a;传统的测试方法可能无法覆盖所有的性能指标&#xff0c;导致一些潜在的问…

非洲“支付宝”PalmPay搭载OceanBase:成本降低80%

10 月 30 日&#xff0c;非洲支付公司PalmPay 的核心系统搭载国产自研数据库OceanBase&#xff0c;正式投入使用。PalmPay 也是 OceanBase 首个非洲商业用户。 作为一家非洲领先的金融科技公司&#xff0c;PalmPay 于 2019 年在尼日利亚推出电子钱包应用&#xff0c;其功能类似…

Verilog刷题[hdlbits] :Module cseladd

题目&#xff1a;Module cseladd One drawback of the ripple carry adder (See previous exercise) is that the delay for an adder to compute the carry out (from the carry-in, in the worst case) is fairly slow, and the second-stage adder cannot begin computing …

推荐PHP匿名在线聊天室系统源码

PHP匿名在线聊天室系统源码 自适应PCWAP端 可发语音、图片&#xff0c;修改数据库config\settings.php可拿去搭建专门跟客户聊天的网站。 演示地址&#xff1a;runruncode.com/php/19610.html

AD教程(五)光耦及二极管元件模型的创建

AD教程&#xff08;五&#xff09;光耦及二极管元件模型的创建 二极管元件的创建 放置管脚&#xff0c;设置管脚号和管脚名称&#xff08;一般隐藏&#xff09;绘制三角形 右键放置直线&#xff0c;选择放置多边形&#xff0c;操作逻辑&#xff0c;每次操作都会增加一边&…

【面试精选】00后卷王带你三天刷完软件测试面试八股文

前言 本人普通本科计算机专业&#xff0c;做测试也有3年的时间了&#xff0c;讲下我的经历&#xff0c;我刚毕业就进了一个小自研薪资还不错&#xff0c;有10.5k&#xff08;个人觉得我很优秀&#xff09;&#xff0c;在里面呆了两年&#xff0c;积累了一些的经验和技能&#…

火爆全网,性能测试-Tomcat连接数分析/jprofiler定位接口相应时长(总结)

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 1、Tomcat链接数分…

QT之多个界面相互切换 (stackedWidget控件)

TOC Chapter1 QT之多个界面相互切换 (stackedWidget控件) 原文链接 stackedWidget控件&#xff1a; 可以自由在多个widget切换&#xff0c;可以通过点击顶部菜单&#xff0c;显示不同界面&#xff0c;在实际开发中应用很广泛。 QStackedWidget是一个堆栈窗口控件&#xff0c…

tp6使用Spreadsheet报错:Class ‘PhpOffice\PhpSpreadsheet\Spreadsheet‘ not found

问题提示如下&#xff1a; 可能vendor下的 phpoffice是从别的项目拷贝过来的&#xff0c;所以咋都不行 解决办法是删掉vendor下的phpoffice&#xff0c;用composer重新下载 具体操作&#xff1a;1、在项目根目录下cmd执行下面这条命令 composer require phpoffice/phpspread…

【Linux】 shutdown 命令使用

shutdown 命令可以用来进行关机程序&#xff0c;并且在关机以前传送讯息给所有使用者正在执行的程序&#xff0c;shutdown 也可以用来重开机。使用权限&#xff1a;系统管理者。 语法 shutdown [选项] 时间 [警告信息] 命令选项及作用 执行令 man shutdown 执行命令结果 参…

矿山数字化react-native技术点记录

文章目录 1&#xff0c;宽度自适应表格实现2&#xff0c;新建作业手势动画3&#xff0c;tabBar中间midTabBar动态展示4&#xff0c;堆料图实现5&#xff0c;语音识别实现6&#xff0c;待办事项上下滑动切换页面7&#xff0c;装料卸料手势冲突8&#xff0c;首页初始化性能9&…

有活动啦 | 同立海源年终大促,满额赠礼!

秋天是倒放的春天 时间不停流转 四季马不停蹄 转眼间 距离2024年还有59天啦&#xff01; 在这个最美的季节 同立海源&#xff0c;年终大促&#xff0c;携礼而来&#xff01; 关于同立海源 北京同立海源生物科技有限公司&#xff0c;专注细胞和基因治疗&#xff08;CGT&am…

都是80m²小户型,凭啥她家那么好看!福州中宅装饰,福州装修

杨桥新苑 本案来自杨桥新苑的住宅&#xff0c; 质朴弥漫在80㎡的小家&#xff0c; 自然淡雅的木纹&#xff0c;精炼的玄关隔断&#xff0c; 简约的设计里传达着中式的静谧风雅&#xff0c; 简练的空间加入中国元素&#xff0c; 让人从进门开始就沾染一丝艺术气息。 风格&a…

库尔勒春雨社会工作服务中心开展“三金西瓜霜‘爱牙公益行’善网公益资源对接

为进一步增强辖区居民口腔健康观念和口腔保健意识&#xff0c;加大口腔防治知识宣传教育&#xff0c;促进形成良好的口腔健康行为。10月29日&#xff0c;库尔勒市春雨社会工作服务中心联合库尔勒市天山街道未成年人保护站开展“三金西瓜霜‘爱牙公益行’─善网公益资源对接”公…

uniapp原生插件之安卓客户端之间wifi文件传输

插件介绍 安卓客户端之间文件传输&#xff0c;支持大文件传输&#xff0c;Wifi/热点文件传输&#xff0c;轻松传送1G文件 插件地址 安卓客户端之间wifi文件传输 - DCloud 插件市场 超级福利 uniapp 插件购买超级福利 使用文档 详细使用文档 示例文件 百度网盘链接&a…

精品Python的旅游数据可视化大屏平台-爬虫-景点门票

《[含文档PPT源码等]精品基于Python的旅游数据可视化平台-爬虫》该项目含有源码、文档、PPT、配套开发软件、软件安装教程、项目发布教程、包运行成功&#xff01; 软件开发环境及开发工具&#xff1a; 开发语言&#xff1a;python 使用框架&#xff1a;Flask 前端技术&…

突破性技术!开源多模态模型—MiniGPT-5

多模态生成一直是OpenAI、微软、百度等科技巨头的重要研究领域&#xff0c;但如何实现连贯的文本和相关图像是一个棘手的难题。 为了突破技术瓶颈&#xff0c;加州大学圣克鲁斯分校研发了MiniGPT-5模型&#xff0c;并提出了全新技术概念“Generative Vokens "&#xff0c…

Java Web 学习笔记(一) —— MySQL(2)

目录 1 约束1.1 约束概述1.2 非空约束1.3 唯一约束1.4 主键约束1.5 默认约束1.6 外键约束 2 数据库设计2.1 数据库设计概述2.2 表关系 3 多表查询3.1 多表查询概述3.2 内连接查询3.3 外连接查询3.4 子查询 4 事务4.1 事务概述4.2 四大特征 1 约束 1.1 约束概述 约束是作用于表…

SPSS游程检验

前言&#xff1a; 本专栏参考教材为《SPSS22.0从入门到精通》&#xff0c;由于软件版本原因&#xff0c;部分内容有所改变&#xff0c;为适应软件版本的变化&#xff0c;特此创作此专栏便于大家学习。本专栏使用软件为&#xff1a;SPSS25.0 本专栏所有的数据文件请点击此链接下…

2023年汉字小达人市级比赛的几个新问题和备赛建议

昨天&#xff08;2023年11月2日&#xff09;&#xff0c;中文自修杂志社在官网公布了2023年第十届小学生汉字小达人区级比赛的结果&#xff0c;公布了区级比赛的获奖名单。 六分成长昨天第一时间把这个消息告诉了关注这项比赛的父母和孩子们&#xff0c;也就2023年汉字小达人的…