【Java开发】JUC基础 04:Synchronized、死锁、Lock锁

news2024/9/20 6:35:55

1 概念介绍

  • 并发同一个对象被多个线程同时操作

📌 线程同步

  • 现实生活中,我们会遇到“同一个资源,多个人都想使用”的问题,比如,食堂排队打饭,每个人都想吃饭,最天然的解决办法就是,排队,一个个来。

  • 处理多线程问题时,多个线程访问同一个对象,并且某些线程还想修改这个对象这时候我们就需要线程同步,线程同步其实就是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面线程使用完毕,下一个线程再使用。

📌 队列和锁

  • 队列:队列等同于对象的等待池,各线程于队列中进行等待;

  • :保证队列的安全,保证同一时间只有一个线程操作该对象。

📌 Synchronized 方法/同步方法

Sybchronized 机制控制对对象的访问,每个对象对应一把锁,每个 synchronized 方法都必须获取调用方法的对象的锁才能执行,否则线程会阻塞,方法一旦执行,就独占该锁,知道方法返回才释放锁,后面被阻塞的线程才能获得锁,继续执行;

存在以下问题:

  1. 一个线程持有锁会导致其他所有需要此锁的线程挂起;

  1. 在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时,引起性能问题;

  1. 如果一个优先级高的线程等待一个优先级低的线程释放锁会导致优先级倒置,引起性能问题。

2 线程不安全及解决

2.1 同步方法

📌 要点

由于我们可以通过private关键字来保证数据对象只能被方法访问,所以我们只需要针对方法提出一套机制,这套机制就是synchronized关键字,它包括两种用法:synchronized方法 和synchronized块.

  • 同步方法: public synchronized void method(int args)

  • 同步代码块:synchronized(obj){}

Obj 称之为同步监视器,obj 可以是任何对象,但是推荐使用共享资源作为同步监视器,同步方法中无需指定同步监视器,因为同步方法的同步监视器就是 this,这个对象本身;

同步监视器的执行过程:
第一个线程访问,锁定同步监视器,执行其中的代码
第二个线程访问,发现同步监视器被锁定,无法访问
第一个线程访问完毕,解锁同步监视器
第二个线程访问,发现同步监视器没有锁,然后锁定并访问;

2.1 多人购票

📌 不安全代码

public class UnsafeBuyTicket{
    public static void main(String[] args) {
        BuyTicket buyTicket = new BuyTicket();

        new Thread(buyTicket, "A").start();
        new Thread(buyTicket, "B").start();
        new Thread(buyTicket, "C").start();
    }
}

class BuyTicket implements Runnable{

    //票数
    private int ticketNums= 10;
    //外部停止方式
    boolean flag = true;

    @Override
    public void run() {
        while (flag){
            buy();
        }
    }

    private void buy(){
        //判断是否有票
        if (ticketNums <=0){
            flag = false;
            return;
        }
        System.out.println(Thread.currentThread().getName()+"买到了第"+ticketNums--+"张票;");
    }
}

控制台输出:

📢 可以看到输出混乱,C和A拿到了同一张票,甚至有时会出现负数,这就是线程不安全。

这是因为同一时刻,两个线程进行统一操作,内存控制不当会导致数据不一致

📌 同步方法

给buy方法加上了锁~

控制台输出:

📢 可以看到很有顺序。

2.2 银行取钱

📌 不安全代码

public class UnsafeBank {
    public static void main(String[] args) {
        Account account = new Account(100,"建设银行共同");
        new Thread(new Drawing(account, 50),"A").start();
        new Thread(new Drawing(account, 100),"B").start();

    }
}

//1.创建账户信息
class Account {
    //余额
    int money;
    //卡名
    String name;

    public Account(int money,String name){
        this.money = money;
        this.name = name;
    }
}

class Drawing implements Runnable{

    Account account;
    //取出的钱
    int drawingMoney;
    //现有的钱
    int nowMoney;

    public Drawing(Account account,  int drawingMoney){
        this.account = account;
        this.drawingMoney = drawingMoney;
    }

    @Override
    public void run() {
        //判断有没有钱
        if (account.money - drawingMoney < 0){
            System.out.println(Thread.currentThread().getName()+"操作:"+account.name+"账户没有那么多钱!");
            return;
        }
        //延时,保证两个线程都能抵达这
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        
        //卡内余额 = 余额 - 取出的钱
        account.money = account.money - drawingMoney;
        //现有的钱
        nowMoney = nowMoney + drawingMoney;

        System.out.println(Thread.currentThread().getName()+"操作:"+account.name+"账户余额为"+account.money);
        System.out.println(Thread.currentThread().getName()+"手里的钱为"+nowMoney);
    }
}

控制台输出:

📢 可以看到两个线程都取了钱,超出了限制条件(不够取的话不能取),线程不安全!

📌 同步代码块

因为同步方法默认是锁this对象,但是这里需要锁这个账户,因此同步代码块,记住锁的对象是变化的量即可。

控制台输出:

📢 上锁成功

2.3 ArrayList

📌 不安全代码

public class UnsafeArrayList {
    public static void main(String[] args) {
        ArrayList<String> arrayList = new ArrayList<>();
        for (int i = 0; i < 10000; i++) {
            new Thread(()-> arrayList.add(Thread.currentThread().getName())).start();
        }
        System.out.println(arrayList.size());
    }
}

控制台输出:

📢 与10000对不上,说明线程名称加到集合中去的时候有冲突,也是线程不安全。

📌 同步代码块

在lambda表达式中给ArrayList对象添加锁~

控制台输出:

📢 上锁成功

2.4 线程安全的集合

这是Juc里边的集合,自带线程安全功能,底层也是用了锁。

import java.util.concurrent.CopyOnWriteArrayList;

public class JucList {
    public static void main(String[] args) {
        CopyOnWriteArrayList<String> list= new CopyOnWriteArrayList<>();
        for (int i = 0; i < 10000; i++) {
            new Thread(()->list.add(Thread.currentThread().getName())).start();
        }

        //增加延时,方式main线程先结束,不然输出不准确
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(list.size());
    }
}

3 死锁和Lock锁

📌 要点

多个线程各自占有一些共享资源,并且相互等待其他线程占用的资源才能运行,而导致两个或两个以上的线程都在等待对方释放资源,都停止执行的情形;某一个同步块同时拥有两个对象以上的锁,就有可能发生死锁

📌 产生死锁的必要条件

  1. 互斥条件:一个线程只能被一个人使用

  1. 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放

  1. 不剥夺条件: 进程已获得的资源,在未使用完之前,不能剥夺;

  1. 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源的关系

3.1 死锁

简单来说,就是A线程拿到一个口号,B线程拿到一个镜子,然后他们都想再拿到对方的东西,但是无法成功,因为口号和镜子都被加锁了,只有一个线程能用。

//死锁:多个线程互相抱着对方需要的资源,然后形成僵持
public class DeadLock {
    public static void main(String[] args) {
        Thread t1 = new Thread(new MakeUp(0,"A"));
        Thread t2 = new Thread(new MakeUp(1,"B"));
        t1.start();
        t2.start();
    }
}

//口红
class Lipstick{
}

//镜子
class Mirror{
}

class MakeUp implements Runnable{

    //需要的资源只有一份
    static Lipstick lipstick = new Lipstick();
    static Mirror mirror = new Mirror();

    int choice;//选择
    String name;//使用人

    public MakeUp(int choice, String name) {
        this.choice = choice;
        this.name = name;
    }

    @Override
    public void run() {
        try {
            makeup();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    //化妆,互相持有对方的锁,拿到对方的资源
    private void makeup() throws InterruptedException {
        if(choice==0){
            synchronized (lipstick){
                System.out.println(this.name+"获取口红的锁");
                Thread.sleep(1000);
                synchronized (mirror){//一秒钟后获得镜子
                    System.out.println(this.name+"获取镜子的锁");
                }
            }
        }else{
            synchronized (mirror){
                System.out.println(this.name+"获取镜子的锁");
                Thread.sleep(1000);
                synchronized (lipstick){//一秒钟后获得口红
                    System.out.println(this.name+"获取口红的锁");
                }
            }
        }
    }
}

控制台输出:

📢 程序无法结束,这就是死锁。

📌 解决死锁

把同步代码块(上锁的)分开,这样锁就能得到释放。

3.2 Lock锁

📌 要点

  • Java 提供了更强大的线程同步机制—通过显式定义同步锁对象来实现同步;同步锁使用 Lock 对象充当;

  • Lock 接口时控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对 Lock 对象加锁,线程开始访问共享资源之前应先获得 Lokc 对象;

  • ReentrantLock 类(可重用锁)实现了 Lock, 他拥有 synchronized 相同的并发性和内存语义,在实现线程安全的控制中,比较常用的时 ReentrantLock,可以显示枷锁释放锁;

📌 未加Lock锁时

public class TestLock {
    public static void main(String[] args) {
        BuyTicket buyTicket = new BuyTicket();

        new Thread(buyTicket).start();
        new Thread(buyTicket).start();
        new Thread(buyTicket).start();
    }
}

class BuyTicket implements Runnable{
    int ticketNums=10;
    @Override
    public void run() {
        while (true){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if(ticketNums>0){
                System.out.println(Thread.currentThread().getName()+"买到了票"+ticketNums--);
            }else {
                break;
            }
        }
    }
}

控制台输出:

📌 加Lock锁后

需要先定义ReentrantLock,然后再try{}finally{}中分别加锁和解锁。

public class TestLock {
    public static void main(String[] args) {
        BuyTicket buyTicket = new BuyTicket();

        new Thread(buyTicket).start();
        new Thread(buyTicket).start();
        new Thread(buyTicket).start();
    }
}

class BuyTicket implements Runnable{
    int ticketNums=10;

    //定义lock锁
    private final ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            try {
                lock.lock();//加锁
                if(ticketNums>0){
                    System.out.println(Thread.currentThread().getName()+"买到了票"+ticketNums--);
                }else {
                    break;
                }
            } finally {
                lock.unlock();//解锁
            }

        }
    }
}

控制台输出:

3.3 synchronized和Lock对比

📌 要点

  • Lock是显式锁(手动开启和关闭锁,别忘记关闭锁)synchronized是隐式锁,出了作用域自动释放

  • Lock只有代码块锁,synchronized有代码块锁和方法锁

  • 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)

  • 优先使用顺序:Lock>同步代码块(已经进入了方法体,分配了相应资源)>同步方法(在方法体之外)

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

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

相关文章

强化学习RL 04: Actor-Critic Algorithm

actor: 是policy network&#xff0c;通过生成动作概率分布&#xff0c;用来控制agent运动&#xff0c;类似“运动员”。critic: 是value network&#xff0c;用来给动作进行打分&#xff0c;类似“裁判”。构造这两个网络&#xff0c;并通过environment奖励来学习这两个网络。…

AQS抽象队列同步器

aqs 抽象队列同步器&#xff0c;内部存储了一个valitail修饰的status 和内部类node &#xff0c;来实现对共享变量并发同步队列机制,以reentrantLock为例&#xff0c;lock底层实际上调用的是sync的lock&#xff0c;会调用cas对status的状态进行修改&#xff0c;来确定是否获得锁…

学习 Python 之 Pygame 开发魂斗罗(七)

学习 Python 之 Pygame 开发魂斗罗&#xff08;七&#xff09;继续编写魂斗罗1. 载入水中图片并添加在水中的标志2. 修改玩家类函数3. 增加河的碰撞体4. 实现玩家在河中的样子继续编写魂斗罗 在上次的博客学习 Python 之 Pygame 开发魂斗罗&#xff08;六&#xff09;中&#…

csgo搬砖项目详细拆解,附选品+详细操作教程

项目实操 一&#xff1a;项目原理 csgo这款游戏不知道大家玩过没有&#xff0c;如果不了解的话&#xff0c;那你肯定知道穿越火线这款游戏吧&#xff0c;都是一个类型的射击游戏。 说到csgo&#xff0c;就得所以一下今天项目的平台steam&#xff0c;它是一个游戏平台&#x…

Redis面试问题总结

1. 什么是Redis&#xff1f;Redis 是一个使用 C 语言写成的&#xff0c;开源的高性能key-value非关系缓存数据库。它支持存储的value类型相对更多&#xff0c;包括string(字符串)、list(链表)、set(集合)、zset(sorted set --有序集合)和hash&#xff08;哈希类型&#xff09;。…

【Redis】Redis主从同步中数据同步原理

【Redis】Redis主从同步中数据同步原理 文章目录【Redis】Redis主从同步中数据同步原理1. 全量同步1.1 判断是否第一次数据同步2. 增量同步3. 优化Redis主从集群4. 总结1. 全量同步 主从第一次同步是全量同步。 数据同步包括以下三个阶段&#xff1a; 在从节点执行slaveof命令…

技术分担产品之忧(上):挑选有业务专家潜力的人

你好&#xff0c;我是王植萌&#xff0c;去哪儿网的高级技术总监、TC主席。从2014年起&#xff0c;担任一个部门的技术负责人&#xff0c;有8年技术总监经验、5年TC主席的经验。这节课我会从去哪儿网产研融合的经验出发&#xff0c;和你聊一聊怎么让技术分担产品之忧。 技术分…

SSL证书与我们普通人之间有什么关系

对于很多普通人来说&#xff0c;SSL证书似乎会感到很陌生&#xff0c;总觉得离自己很遥远&#xff0c;从而并没有引起察觉。要是这么想的话那么就真的大错特错了&#xff0c;其实SSL证书与我们普通人之间还是很密密相关的&#xff0c;是我们应该都需要关注的&#xff0c;下面就…

cesium学习记录02-vue项目中cesium的配置与使用

1&#xff0c;下载cesium包 &#xff08;当然&#xff0c;使用npm install cesium安装也是可以的&#xff0c;不过在这里选择下载包放到本地&#xff09; 官方下载地址 笔者的cesium版本为1.101 2&#xff0c;将下载的Cesium文件夹放到项目里某个位置 这里&#xff0c;笔者将…

又一个国内类ChatGPT模型?【秘塔科技上线自研LLM大模型「对话写作猫」】

又一个国内类ChatGPT模型&#xff1f;【秘塔科技上线自研LLM大模型「对话写作猫」】 &#xff08;马上被打脸 ~ ~&#xff09; 一直期待中国有没有类ChatGPT产品可以出现。 昨天&#xff0c;2023年2月27日&#xff0c;秘塔科技上线了自研LLM大模型「对话写作猫」&#xff0c;…

全屋Wi-Fi领域「兵戎相见」,鸿雁这一局赢面大不大?

作者 | 牧之 编辑 | 小沐 出品 | 智哪儿 zhinaer.cn相比全屋智能&#xff0c;另一个刚需属性更强&#xff0c;消费规模更大的细分市场&#xff0c;便是全屋Wi-Fi。在这个板块&#xff0c;当鸿雁入局的时候&#xff0c;笔者还是有些许的「诧异」。毕竟&#xff0c;鸿雁给大众的印…

【Vue3】vue3 + ts 封装城市选择组件

城市选择-基本功能 能够封装城市选择组件&#xff0c;并且完成基础的显示隐藏的交互功能 &#xff08;1&#xff09;封装通用组件src/components/city/index.vue <script lang"ts" setup name"City"></script> <template><div class…

【PyTorch】教程:torch.nn.Conv2d

Conv2d CLASS torch.nn.Conv2d(in_channels, out_channels, kernel_size, stride1, padding0, dilation1, groups1, biasTrue, padding_modezeros, deviceNone, dtypeNone) 2D 卷积 out(Ni,Coutj)bias(Coutj)∑k0Cin−1Weight(Coutj,k)∗input(Ni,k)out(N_i, C_{out_j})bias(C_…

k8s环境jenkins发布vue项目指定nodejs版本

k8s环境jenkins发布vue项目指定nodejs版本1、背景2、分析3、解决方法3.1、 找到配置镜像位置3.2、 制作新镜像3.3、 推送镜像到私有仓库3.4、 修改配置文件1、背景 发布一个前端项目&#xff0c;它需要nodejs 16.9.0版本支持&#xff0c;而kubesphere 3.2.0集成的jenkins 的镜…

Hbase2.4.11简单了解_搭建Hbase集群_配置Hbase高可用---大数据之Hbase工作笔记0034

然后我们看一下hbase的集群架构,可以看到跟其他Hadoop系列的架构一样 都是有个master对吧,然后 还有3个region server,然后所有的机器,都连接到zookeeper 然后这里还要注意有个:backup-master103 ,这个是个备用的master 看看master 和 regionserver的作用. master部署到namen…

深度学习之神经网络的优化器篇

神经网络的优化器 文章目录神经网络的优化器GD 梯度下降算法重球法SGD随机梯度下降Momentum动量梯度NAG(Nesterov accelerated gradient)AdaGrad(Adaptive gradient)RMSProp(Root mean square prop)Adam(Adaptive Moment Estimation)AdamWAdan(Adaptive Nesterov Momentum)本片…

cesium学习记录03-QGis数据生产=>Postgis存储=>Geoserver发布=>Cesium调用

说明&#xff1a; 参照文章 1&#xff0c;安装 QGIS 下载安装 &#xff08;前四步就可以了&#xff09; 2&#xff0c;下载安装postgresql 3&#xff0c;下载安装PostGis 4&#xff0c;QGIS连接PostGis 5&#xff0c;QGIS 上传到Postgis 1&#xff0c;QGIS图的图 &…

坚鹏:学习贯彻二十大精神 解码共同富裕之道(面向银行)

学习贯彻二十大精神 解码共同富裕之道课程背景&#xff1a; 很多银行从业人员存在以下问题&#xff1a; 不知道如何准确解读二十大精神&#xff1f; 不清楚共同富裕相关政策要求&#xff1f; 不知道如何有效推动共同富裕&#xff1f; 课程特色&#xff1a; 有实战案例 有…

【C++】STL 模拟实现之 list

文章目录一、list 的常用接口及其使用1、list 一般接口2、list 特殊接口3、list 排序的性能分析二、list 迭代器的实现1、迭代器的分类2、list 迭代器失效问题3、list 迭代器源码分析4、list 迭代器模拟实现4.1 普通迭代器4.2 const 迭代器4.3 完整版迭代器三、list 的模拟实现…

05 封装

在对 context 的封装中&#xff0c;我们只是将 request、response 结构直接放入 context 结构体中&#xff0c;对应的方法并没有很好的封装。 函数封装并不是一件很简单、很随意的事情。相反&#xff0c;如何封装出易用、可读性高的函数是非常需要精心考量的&#xff0c;框架中…