JavaEE初阶-线程2

文章目录

  • 一、多线程安全问题
    • 1.1 线程安全问题的原因
    • 1.2 如何解决线程安全问题
  • 二、加锁
    • 2.1 synchronized
    • 2.2 synchronized的几种使用方式
    • 2.3 synchronized的可重入性
  • 三、死锁
    • 3.1 死锁的必要条件


一、多线程安全问题

代码示例如下:

public class Demo20 {
    static int count = 0;

    public static void main(String[] args) throws InterruptedException {

        Thread t1 = new Thread(() -> {

            for (int i = 0; i < 50000; i++) {
                count++;

            }

        });

        Thread t2= new Thread(() -> {

            for (int i = 0; i < 50000; i++) {
                count++;

            }

        });

        //串行执行
//        t1.start();
//        t1.join();
//        t2.start();
//        t2.join();

        t1.start();

        t2.start();

        t1.join();
        t2.join();
        System.out.println("count:"+count);

    }
}

这段代码每次执行的结果都不一样,都没有达到预期的结果即100000。主要的原因在于:
(1)count++这个操作在cpu指令的角度上看其实是三个指令

  • load:把内存中的数据加载到寄存器。
  • add:把寄存器中的值+1。
  • 把寄存器中的值写回内存。

(2)两个线程并发进行count++,多线程的执行是随即调度,抢占式的执行模式。

综合以上两点,实际并发执行时,两个线程的指令执行的相对顺序就存在多种可能,不同执行顺序下得到的结果就会有差异,例如:
在这里插入图片描述

光这一种情况,得到的结果就不可能时100000。

1.1 线程安全问题的原因

(1)线程在系统中是随机调度的,抢占式执行。
这个无法改变,是内核设计者的考虑。
(2)多个线程修改同一个变量。
一个线程修改一个变量=>没事
多个线程读取一个变量=>没事
多个线程修改不同的变量=>没事
(3)线程针对变量的修改操作,不是"原子"的。
(4)内存可见性。
(5)指令重排。

1.2 如何解决线程安全问题

从原因入手:
(1)原因1:无法干预。
(2)原因2:是一个切入点,但是在java中这种做法不是很普适,只针对一些特定场景是可以做到的。
(3)原因3:这是解决线程安全问题最普适的方案。可以通过一些操作,将非原子的操作打包成一个原子的操作。(加锁)

二、加锁

加锁就是针对原因3解决线程安全问题的操作。
锁的几个特点:
(1)锁涉及的几个操作:加锁和解锁。
(2)锁的主要特性:互斥。
一个线程获取到一个锁之后,如果其它线程也想要获取该锁,会进行阻塞等待。
(3)代码中可以创建多个锁。
只有多个线程竞争同一把锁才会互斥,针对不同的锁则不会。

2.1 synchronized

synchronized后面带上()里面写的就是"锁对象"。
注意:锁对象的用途有且仅有一个,就是用来区分两个线程是否对同一个对象加锁。如果是就会互斥,引起阻塞等待。如果不是,就不会出现锁竞争,也不会阻塞等待
synchronized后面还跟着{},进了代码块就是对()内的锁对象进行了加锁,出了代码块就是对()的锁对象进行了解锁。
用加锁解决线程安全问题代码示例如下:

public class Demo23 {
    public static int count=0;

    public static void main(String[] args) throws InterruptedException {
        Object object=new Object();

        Thread t1=new Thread(()->{
            for (int i = 0; i < 50000; i++) {
                synchronized (object) {
                    count++;
                }
            }
        });

        Thread t2=new Thread(()->{
            for (int i = 0; i < 50000; i++) {
                synchronized (object) {
                    count++;
                }
            }
        });

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

        t1.join();
        t2.join();


        System.out.println(count);
    }
}

在这里插入图片描述
每次执行count++会因为锁竞争,从而强制变为串行执行,但是执行for循环的条件以及i++都是并发执行的。和join等待操作相比,join操作时是等一个线程执行完了 再让join返回,第二个线程才能继续执行。
上述代码t1释放锁之后,下一次拿到锁的是t1还是t2仍然是概率性问题。

2.2 synchronized的几种使用方式

在示例方法内加锁及给类方法加锁:

class Counter {
    public int count = 0;

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

    synchronized public static void func() {
//        synchronized (Counter.class) {
//
//        }
    }

    public int get() {
        return count;
    }
}


public class Demo24 {


    public static void main(String[] args) throws InterruptedException {
        Counter counter = new Counter();
        Counter counter1 = new Counter();
        Thread t1 = new Thread(() -> {
//            synchronized (counter) {
//                counter.add();
//            }
            for (int i = 0; i < 5000; i++) {
                //counter.func();
                counter.add();
            }

        });

        Thread t2 = new Thread(() -> {
//           synchronized (counter) {
//               counter.add();
//           }

            //counter.add();
            for (int i = 0; i < 5000; i++) {
                //counter1.func();
                counter.add();
            }
        });


        t1.start();
        t2.start();
        t1.join();
        t2.join();

        System.out.println(counter.get());
    }

}

2.3 synchronized的可重入性

class Counter1 {
    private int count = 0;

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

    public int get() {
        return count;
    }

}

public class Demo25 {
    public static void main(String[] args) throws InterruptedException {
        Counter1 counter=new Counter1();
        Thread t1=new Thread(()->{
            for (int i = 0; i < 50000; i++) {
               //相当于
//                synchronized (counter) {
//                    synchronized (counter) {
//                        count++;
//                    }
//                }
                //按理说这样写应该会阻塞,因为外括号已经在counter上加了锁,内括号再加就会堵塞
                //内等待外执行完但是外又在等内执行完,于是进入死锁
                //但是java中不会这样,因为如果在java中内外是一个锁对象则直接进入
                //理论上上这是线程死锁的情况一
                synchronized (counter) {
                    counter.add();
                }

            }

        });

        Thread t2=new Thread(()->{

            for (int i = 0; i < 50000; i++) {
                counter.add();
            }
        });


        t1.start();
        t2.start();
        t1.join();
        t2.join();

        System.out.println(counter.get());
    }

}

上述代码中的t1按理应该进入死锁,但是因为java中的synchronized具有可重入性所以避免了死锁,但是在其它语言中都是会死锁的。

三、死锁

死锁的三种典型场景:

  • 锁的不可重入性,一个线程获取了一个锁,如果再对这个锁加锁就会产生死锁。不过引入可重入锁就可以解决,并且java中的用来加锁的synchronized具有可重入性所以无需考虑这种情况。
  • 两个线程两把锁。例如线程1加锁a,线程2加锁b,线程a又请求锁b,线程2又请求锁a此时就会死锁。
  • 多个线程多把锁。例子就是经典的哲学家就餐问题。

3.1 死锁的必要条件

(1)锁具有互斥性,一个线程获取锁a,如果另一个线程也想获取锁a就得阻塞等待。
(2)锁具有不可剥夺性:一个线程获取锁a,除非它主动释放,其它线程无法夺取线程上的锁a。
(3)请求和保持:一个线程获取锁a,在不释放该锁的前提下,去获取其它的锁。
(4)循环等待:多个线程获取多个锁的过程中出现循环等待。
这四个必要条件缺一不可,任何死锁的情况都必须具备以上四点否则无法构成死锁。
如果想避免死锁,可以打破上述的四个必要条件。条件一二如果是自己实现的锁那么可以打破,但是在java的synchronized中这两条是无法打破的,但是可以从后面两条来进行打破。
打破条件三是要从代码结构上进行改变,因为条件三会造成锁的嵌套,只要让自己建立的锁不嵌套即可,当然有的时候会不得不去嵌套锁。
打破条件四只需要规定好加锁的顺序即可。
代码示例如下:

public class Demo26 {

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

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

        });

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

                synchronized (locker2) {
                    System.out.println("t2 获取了两把锁");
                }
            }
        });
        //以上代码会发生线程死锁的情况二
        //可以通过修改代码结构来避免死锁
        //避免循环等待:约定加锁的顺序
        //Thread t1 = new Thread(() -> {
        //            synchronized (locker1) {
        //                try {
        //                    Thread.sleep(1000);
        //                } catch (InterruptedException e) {
        //                    throw new RuntimeException(e);
        //                }
        //                synchronized (locker2) {
        //                    System.out.println("t1 获取了两把锁");
        //                }
        //            }
        //
        //        });
        //
        //        Thread t2 = new Thread(() -> {
        //            synchronized (locker1) {
        //
        //                synchronized (locker2) {
        //                    System.out.println("t2 获取了两把锁");
        //                }
        //            }
        //        });

        //避免请求和保持:
        //Thread t1 = new Thread(() -> {
        //            synchronized (locker1) {
        //                try {
        //                    Thread.sleep(1000);
        //                } catch (InterruptedException e) {
        //                    throw new RuntimeException(e);
        //                }
        //
        //            }
    //               synchronized (locker2) {
//                         System.out.println("t1 获取了两把锁");
        //            }
        //        });
        //
        //        Thread t2 = new Thread(() -> {
        //            synchronized (locker2) {
        //
        //
        //            }
        //        synchronized (locker1) {
        //                 System.out.println("t2 获取了两把锁");
        //         }
        //        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();


    }
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

直流电源电路(上)

直流电源电路&#xff08;上&#xff09; 综述&#xff1a;本篇文章讲述了直流电源电路的各种类型以及他们之间的优缺点对比。 一、总体关系框图 二、LDO 1&#xff09;LDO基础知识 2&#xff09;LDO电路框图 LDO电路由调整管、误差放大器、基准电压和采样电路组成。 3&…

docker容器之etcd

一、etcd介绍 1、etcd是什么 etcd是CoreOS团队于2013年6月发起的开源项目&#xff0c;它的目标是构建一个高可用的分布式键值(key-value)数据库。 2、etcd特点 简单的接口&#xff0c;通过标准的HTTP API进行调用&#xff0c;也可以使用官方提供的 etcdctl 操作存储的数据。…

【战略前沿】与中国达成生产协议后,飞行汽车即将起飞

【原文】Flying cars edge towards takeoff after Chinese production deal 【作者】Thomas Macaulay 斯洛伐克公司KleinVision签署了一项协议&#xff0c;将大规模生产AirCar。 一辆获得航空认证的飞行汽车向商业化又迈出了一大步。 空中汽车的创造者KleinVision今天宣布出售…

Anaconda/Python快速安装jieba 【win/mac】

一、直接上命令 pip install -i https://pypi.tuna.tsinghua.edu.cn/simple jieba 我实在PyCharm里面的终端输进去。 之后就很快速的看到成功的下图。 二、官网 官网下载的速度太慢了——这是官网地址https://pypi.org/project/jieba/#files 点进去之后点击下载&#xff0c…

黑马鸿蒙笔记 3

目录 11.ArkUI组件-Column和Row 12.ArkUI组件-循环控制 13.ArkUI组件-List 14.ArkUI组件-自定义组件 15.ArkUI组件-状态管理State装饰器 16.ArkUI组件-状态管理-任务统计案例 17.ArkUI组件-状态管理-PropLinkProvideConsume 11.ArkUI组件-Column和Row Colum和Row的交叉…

Docker容器与Serverless的融合:探索《2023腾讯云容器和函数计算技术实践精选集》中的云原生创新案例

Docker容器与Serverless的融合&#xff1a;探索《2023腾讯云容器和函数计算技术实践精选集》中的云原生创新案例 文章目录 Docker容器与Serverless的融合&#xff1a;探索《2023腾讯云容器和函数计算技术实践精选集》中的云原生创新案例一、引言二、《2023腾讯云容器和函数计算…

Tailscale:随时随地远程和使用服务器

文章目录 Tailscale是什么&#xff1f;Tailscale能做什么&#xff1f;1、传输文件2、远程开发3、代理 Tailscale怎么用&#xff1f;Windows下安装OpenSSH在线安装离线安装连接SSH服务器 Reference相关阅读 彩蛋&#xff1a;Pycharm远程连接服务器并运行代码 Tailscale是什么&am…

3d怎么两个模型连接圆润?---模大狮模型网

在3D建模中&#xff0c;如何实现两个3d模型的圆润连接是一个常见而又关键的问题。无论是为了美观的外观设计还是为了模型的功能性&#xff0c;圆润连接都能够增加模型的整体质感和流畅度。模大狮将介绍一些常见的方法和技巧&#xff0c;帮助您实现两个模型之间的圆润连接。 一、…

maven构建项目报错:Failure to find com.microsoft.sqlserver:sqljdbc4:jar:4.0 in

背景 今天在项目里面查询sqlserver的数据库的时候&#xff0c;本地maven中引入依赖&#xff1a; <dependency><groupId>com.microsoft.sqlserver</groupId><artifactId>sqljdbc4</artifactId><version>4.0</version></dependenc…

若依框架学习——新建模块(图文)

文章目录 前言一、启动项目二、添加模块1、添加菜单2、创建表3、生成代码4、添加后端代码5、添加前端代码 前言 官网&#xff1a;添加链接描述 一、启动项目 项目地址&#xff1a;https://gitee.com/y_project/RuoYi-Vue 1、后端启动 使用idea工具打开项目&#xff0c;使用sq…

Red Hat配置本地yum源

Red Hat配置本地yum源 创建本地源文件夹 mkdir -p /mnt/cdrom挂载镜像文件至指定的目录 mount /dev/cdrom /mnt/cdrom备份本地源 cp -rf /etc/yum.repos.d /etc/yum.repos.d_$(date %Y%m%d_%H%M%S)删除默认原本地源 rm -rf /etc/yum.repos.d/*配置本地源&#xff0c;创建…

云原生技术赋能AI绘图:Stable Diffusion在腾讯云的部署与应用新篇章

摘要 随着信息技术的飞速发展和数字化转型的深入推进&#xff0c;云原生架构已成为企业数字化转型的重要基石。Docker容器、Serverless和微服务等技术作为云原生的核心组成部分&#xff0c;正在不断推动着企业应用架构的革新与升级。本文旨在总结近期在云原生实践、容器技术、…

后端返还二进制excl表格数据时候,如何实现在前端下载表格功能及出现表格打开失败的异常处理。

背景&#xff1a; 后端返还一个二进制流的excl表格数据&#xff0c;前端需要对其解析&#xff0c;然后可提供给客户进行下载。 思路&#xff1a;把二进制流数据转换给blob对象&#xff0c;然后利用a标签进行前端下载。 代码&#xff1a; 后端返还 类似如下的数据 前端代码…

java----继承

1、继承的定义 继承就是子类继承父类的特征和行为&#xff0c;使得子类对象具有父类的属性和方法&#xff08;不劳而获&#xff09; 使用 extends关键字 2、方法重写&#xff08;方法覆盖&#xff09; 子类可以重写父类中的方法&#xff0c;要求方法签名必须一样 3、方法重载…

高效批量剪辑视频,一键设置区间随机抽取画面,批量剪辑视频不再是梦!

在数字世界的浩瀚海洋中&#xff0c;视频内容日益丰富&#xff0c;如何从冗长的视频中快速抓取关键瞬间&#xff0c;将精彩定格为永恒&#xff1f;今天&#xff0c;我们为你带来一款强大的视频剪辑工具&#xff0c;它拥有批量从视频中指定区间随机抽帧并导出保存的功能&#xf…

蓝桥杯刷题day13——乘飞机【算法赛】

一、问题描述 等待登机的你看着眼前有老有小长长的队伍十分无聊&#xff0c;你突然想要知道&#xff0c;是否存在两个年龄相仿的乘客。每个乘客的年龄用一个 0 到 36500 的整数表示&#xff0c;两个乘客的年龄相差 365 以内就认为是相仿的。 具体来说&#xff0c;你有一个长度…

聚观早报 | 微软和OpenAI联合;日本将与欧盟合作

聚观早报每日整理最值得关注的行业重点事件&#xff0c;帮助大家及时了解最新行业动态&#xff0c;每日读报&#xff0c;就读聚观365资讯简报。 整理丨Cutie 4月01日消息 微软和OpenAI联合 日本将与欧盟合作 苹果为员工提供优惠 萤石2024春季新品发布会 特斯拉Model Y车…

天池医疗AI大赛[第一季] Rank8解决方案[附TensorFlow/PyTorch/Caffe实现方案]

团队成员&#xff1a;北京邮电大学 模式识别实验室硕士研究生 今年5月&#xff0c;参加了天池医疗AI大赛&#xff0c;这次比赛是第一次参加此类的比赛&#xff0c;经过接近半年的比赛&#xff0c;终于10月落下帷幕&#xff0c;作为第一次参加比赛&#xff0c;能在接近3000支队…

适用于智能断路器、新能源汽车充电枪锁、电动玩具、电磁门锁等的直流电机驱动芯片D6289ADA介绍

应用领域 适用于智能断路器&#xff08;家用或工业智能空开&#xff09;、新能源汽车充电枪锁、电动玩具、电磁门锁、自动阀门等的直流电机驱动。 功能介绍 D6289ADA是一款直流马达驱动芯片&#xff0c;它有两个逻辑输入端子用来控制电机前进、后退及制动。该电路具有良好的抗干…

[flink 实时流基础] 输出算子(Sink)

学习笔记 Flink作为数据处理框架&#xff0c;最终还是要把计算处理的结果写入外部存储&#xff0c;为外部应用提供支持。 文章目录 **连接到外部系统****输出到文件**输出到 Kafka输出到 mysql自定义 sink 连接到外部系统 Flink的DataStream API专门提供了向外部写入数据的方…