Java基础学习——Java线程(二)同步代码块、同步方法、Lock锁、死锁程序例子、不同类型的锁

2023-11-13

对于之前买票的练习,又出现多个10张票的情况,这里对这一现象进行分析

对于代码

for (int i = 1; i <= 100 ; i++) {
    if (ticketNum>0){
        System.out.println("我在"+this.getName()+"买到了第"+ ticketNum-- +"张票");
    }
}

对于ticketNum-- ,其实分为两步

1)得到ticketNum

2)ticketNum-1

那么,如果在线程执行完1)时,资源就被其他线程抢走,就会出现如下情况

线程1:我在 窗口1 买到了第 10 张票,还未进行--操作,资源被线程2抢走

线程2:我在 窗口3 买到了第 10 张票,还未进行--操作,资源被线程3抢走

线程3:我在 窗口2 买到了第 10 张票,进行--操作,ticketNum=9

线程1:抢到了资源,进行--操作,ticketNum=8

线程2:抢到了资源,进行--操作,ticketNum=7

那么此时就会出现多个10张票,同理,如果在最后一张票的抢票线程执行过程中,资源被抢走,那么就会出现-1、-2的票数。

以上问题都是线程安全引起的问题,原因是多个线程在争抢资源时,都对共享的资源进行操作,而导致共享的资源出现错误。

解决这个问题需要引入“锁”---》同步监视器

一、同步代码块:synchronized关键字(同步监视器){}

必须多个线程使用同一把锁!锁必须是引用数据类型

1.synchronized (this){}:()内是锁住的内容,this指代当前对象

对于只创建一个线程对象(实现Runnable接口),this指代当前这个对象,也就是当前的对象被锁住,即使创建了多个线程,只要t1线程对象正在执行,也就是bt已经被锁住,那么t2线程对象想运行时也会发现bt被锁住,无法抢夺资源

for (int i = 1; i <= 100; i++) {
            synchronized (this) {//this就是这个”锁“
                //只放入具有安全隐患的代码,锁住不需要锁的代码会造成效率变低。
                //只有synchronized{}中的代码执行完毕后才会释放资源,给其他线程进行争抢
                if (ticketNum > 0) {
                    System.out.println("我在" + Thread.currentThread().getName() + "买到了第" + ticketNum-- + "张火车票");
                }
            }
        }

2.synchronized (BuyTicketThread.class){}

对于创建多个线程对象(继承Thread类),使用这种方式,因为BuyTicketThread.class:字节码信息,是唯一的,也就是多个线程只认这同一把锁

for (int i = 1; i <= 100 ; i++) {
            synchronized (BuyTicketThread.class){
                //BuyTicketThread.class:字节码信息,是唯一的,也就是多个线程只认这同一把锁
                if (ticketNum>0){
                    System.out.println("我在"+this.getName()+"买到了第"+ ticketNum-- +"张票");
                }
            }
            
        }

3.同步监视器特点

1)必须是引用数据类型,最好使用final修饰(不可以改变)

2)也可以创建一个没有任何意义的唯一的锁(例如static Objecto=new Object),但一般使用共享资源:字节码信息

3)尽量不使用String和包装类Integer左同步监视器

4.同步代码块的执行过程

1)线程1执行到同步代码块,发现同步监视器(锁)处于open状态,于是close锁后执行代码块中的代码

2)线程1执行过程中,发生了线程切换,线程1失去CPU,但是没有开锁

3)即使线程2抢到了CPU,执行到了同步代码块处,发现锁处于close状态,无法执行代码块中的代码,于是线程2进入阻塞状态

4)线程1再次获取CPU,执行完代码块中的代码后,释放锁

5)线程2获取CPU,发现锁处于open状态,于是close锁,由阻塞状态进入就绪再进入运行状态,执行代码块中的代码

  • 同步代码块中可以发生CPU的切换,但是后续的线程即使抢到了CPU,也无法执行代码(因为锁没有被释放)
  • 对于多个代码块使用同一个同步监视器(锁),锁住其中一个代码块的同时,也锁住了其它使用该锁的代码块,其它线程无法执行这些代码块。而使用其它锁的代码块不受影响

对于下面的代码,a、b方法使用的是同一把锁o,那么当线程1执行a(),锁住了o后,即使线程2获取了CPU可以执行b(),但是由于o被锁住,b()也是无法执行的,只有o被释放才能执行b()。

二、同步方法

1.synchronized

在方法的定义中加上synchronized,同样是只能解决实现Runnable接口的代码,因为只有一个对象,锁住的是同一个方法。

@Override
    public void run() {
        for (int i = 1; i <= 100; i++) {
            //调用if语句的方法
            buyTicket();
        }
    }
    //将具有安全隐患的if语句放入新建的方法中,在方法定义中加入synchronized
    public synchronized void buyTicket(){
        if (ticketNum >0){
            System.out.println("我在"+Thread.currentThread().getName()+"买到了第"+ ticketNum-- +"张火车票");
        }
    }

2.static synchronized

在方法的定义中加上static synchronized,可以解决继承Thread类的代码,因为有多个对象,需要将方法设置为唯一的方法,于是设置为类方法,这样就可以 保证锁住的是同一个方法

static修饰后,源码不能使用

@Override
    public void run() {
        //2.100个人
        for (int i = 1; i <= 100 ; i++) {
            buyTicket();
        }
    }
    public static synchronized void buyTicket(){
        if (ticketNum>0){
            System.out.println("我在"+Thread.currentThread().getName()+"买到了第"+ ticketNum-- +"张票");
        }
    }

3.同步方法总结

1)run()方法不能定义为同步方法

2)非静态同步方法的同步监视器是this,静态同步方法的同步监视器是 类名.class字节码信息对象

3)同步代码块的效率高于同步方法(因为同步方法中可能有很多其他这个方法的代码,会全部被锁住,而同步代码块只会锁住有安全隐患的代码)

4)同步方法的锁是this,一旦锁住一个方法,也就锁住了所有的同步方法,而同步方法块只会锁住使用同一个同步监视器的代码块。

例如上面,当线程1调到这个类下面的a()方法时,锁住了this,由于a()是同步方法,那么线程2来调b()方法也是调不到的,因为这个类的对象this被锁住了。

三、Lock锁(JDK1.5之后新增)

1.synchronized和Lock的区别

1)synchronized是Java中的关键字,是虚拟机级别的,依靠JVM识别;Lock锁是API级别,提供了相应的接口和实现类,更灵活,优于synchronized

2)synchronized是隐式锁,Lock是显式锁(需要手动开启关闭)

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

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

一般使用顺序:Lock > 同步代码块 > 同步方法

2.使用方法

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class BuyTicketThread extends Thread{
    public BuyTicketThread(String name) {
        super(name);
    }

    static int ticketNum=10;
    //Lock是接口,不能直接创建对象,需要使用它的实现类创建对象
    //1.新建锁
    Lock lock = new ReentrantLock();

    @Override
    public void run() {
        //2.100个人
        for (int i = 1; i <= 100 ; i++) {
            //2.在if前打开锁
            lock.lock();
            //由于if可能会执行异常,就不会执行后面的关闭锁的语句,那么其它线程永远无法再次打开锁
            //所以这里需要捕捉异常,将关闭锁的代码放入finally中保证一定会执行
            try {
                if (ticketNum>0){   //里面不能再使用this.getName()
                    System.out.println("我在"+Thread.currentThread().getName()+"买到了第"+ ticketNum-- +"张票");
                }
            }catch (Exception e)
            {
                e.printStackTrace();
            }finally {
                //3.if执行完毕后关闭锁
                lock.unlock();
            }
        }
    }
}

四、线程同步的缺点

1)线程同步-->线程安全,效率低(例如锁住this,所有类的对象可调用的同步方法都会被锁住),线程不安全,效率高

2)可能造成死锁:不同线程分别占用对方需要的同步资源不放,都在等待对方先放开自己需要的同步资源,就造成了死锁。死锁不会出现异常和提示,只会线程都处于阻塞状态

死锁代码示例

public class TestDeadLock implements Runnable{

    public int flag=1;
    static Object o1 = new Object(),o2=new Object();

    @Override
    public void run() {
        System.out.println(flag);

        //当flag=1时锁住o1
        if (flag==1){
            synchronized (o1){
                //有异常,捕捉
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //真正需要执行的代码是下面一行
                synchronized (o2){
                    System.out.println(2);
                }
            }
        }

        //当flag=0时锁住o2
        if (flag==0){
            synchronized (o2){
                //有异常,捕捉
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //真正需要执行的代码是下面一行
                synchronized (o1){
                    System.out.println(1);
                }
            }
        }
    }


    public static void main(String[] args) {
        //创建两个线程类的实例
        TestDeadLock td1 = new TestDeadLock();
        TestDeadLock td2 = new TestDeadLock();

        td1.flag=1;
        td2.flag=0;

        Thread t1 = new Thread(td1);
        Thread t2 = new Thread(td2);
        t1.start();
        t2.start();
    }
}

代码解析:

1)创建的两个线程t1和t2,由于td1=1,td2=0,也就是说当线程启动后,t1走到if(flag==1)中,t2走到if(flag==0)中

2)t1将o1锁住后执行代码:锁住o2,同时t2将o2锁住后执行代码:锁住o1

3)但是由于o2已经被锁住,所以t1无法执行代码:锁住o2;同时o1也已经被锁住,所以t2无法执行代码:锁住o1

4)t1在等待t2解锁o2,t2在等待t1解锁o1,双方都进入阻塞状态,造成死锁

五、各种类型的锁

在单线程环境中,由于不存在资源竞争,所以不需要锁。但在多线程环境中由于存在资源共享和竞争,为了合理的分配资源及公平的使用资源,所以需要锁。在计算机系统中,多线程需要多多核处理器的支持,每个核以时间片的方式进行资源调度,一旦线程获取到时间片,就开始执行代码逻辑,当线程没有获取时间片,就暂停执行代码逻辑。

1)乐观锁:又称为“无锁”,总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制和CAS算法实现。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类似于 write_conditio 机制,其实都是提供的乐观锁。在 Java 中 java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式 CAS 实现的。由于无锁操作中没有锁的存在,因此不可能出现死锁的情况,也就是说乐观锁天生免疫死锁

2)悲观锁:就是我们常说的锁。悲观的认为每次去拿数据都认为别人会修改,所以在每次拿数据的时候都会上锁,这样别人想拿数据时就会阻塞直到拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。Java 中synchronized 和 ReentrantLock 等独占锁就是悲观锁的思想。

3)同步锁

1.CAS的原理

CAS的全称是比较并交换(compare and swap),CAS中有3个值:

  • V:要更新的变量
  • E:预期值
  • N:新值

比较并交换的过程为:判断V是否等于E,如果等于,将V的值设为N;如果不等于,说明已经有其他线程更新了V,则当前线程放弃更新,什么都不做。E本质上是旧值。

举例如下:

1)存在一个多线程共享的变量i,i=5,现在线程A需要设置i为6,

2)首先将i和5对比,若i=5,说明没有其他线程修改过i,那么线程A就更新i为6,

3)若i≠5,说明有其他线程修改过i(例如修改为2),那么线程A什么也不做。i仍等于2

由于CAS是一种原子操作,它是⼀条CPU的原子指令,从CPU层面保证了原子性。当多个线程同时使⽤CAS操作⼀个变量时,只有⼀个会胜出,并成功更新,其余均会失败,但失败的线程并不会被挂起,仅是被告知失败,并且允许再次尝试,当然也允许失败的线程放弃操作。

对于native方法,Java不负责具体实现,交由底层JVM使用c或c++去实现。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Java基础学习——Java线程(二)同步代码块、同步方法、Lock锁、死锁程序例子、不同类型的锁 的相关文章

  • 清理码头 - 删除“不必要”的东西

    我习惯用Jetty http jetty codehaus org jetty 作为我的网络容器 我对我做了什么安装步骤得到原始的焦油球并且清理一些目录和文件从中 我在这里想提出的是 您通常从 Jetty 中删除什么以在生产 登台环境中使用
  • 添加动态数量的监听器(Spring JMS)

    我需要添加多个侦听器 如中所述application properties文件 就像下面这样 InTopics Sample QUT4 Sample T05 Sample T01 Sample JT7 注意 这个数字可以多一些 也可以少一些
  • Android 自定义视图不能以正确的方式处理透明度/alpha

    我正在绘制自定义视图 在此视图中 我使用两个不同的绘画和路径对象在画布上绘画 我基本上是在绘制两个重叠的形状 添加 Alpha 后 视图中重叠的部分比图像的其余部分更暗 这是不希望的 但我不知道如何解决它 这是我的代码片段 用于展示我如何在
  • 如何在 JSP 中导入类?

    我是一个完全的JSP初学者 我正在尝试使用java util List在 JSP 页面中 我需要做什么才能使用除以下类之外的类java lang 使用以下导入语句进行导入java util List 顺便说一句 要导入多个类 请使用以下格式
  • 使用 RecyclerView 适配器在运行时更改布局屏幕

    我有两个布局文件 如下所示 如果列表中存在数据 则我显示此布局 当列表为空时 我会显示此布局 现在我想在运行时更改布局 当用户从列表中删除最后一项时 我想将布局更改为第二张图片中显示的 空购物车布局 In getItemCount Recy
  • 内存一致性 - Java 中的happens-before关系[重复]

    这个问题在这里已经有答案了 在阅读有关内存一致性错误的 Java 文档时 我发现与创建 发生 之前 关系的两个操作相关的点 当语句调用时Thread start 每个具有 与该语句发生之前的关系也有一个 与 new 执行的每个语句之间发生的
  • 如何获取 WebElement 的父级[重复]

    这个问题在这里已经有答案了 我试过了 private WebElement getParent final WebElement webElement return webElement findElement By xpath 但我得到
  • Java 8 中函数式接口的使用

    这是来自的后续问题Java 8 中的 双冒号 运算符 https stackoverflow com questions 20001427 double colon operator in java 8其中 Java 允许您使用以下方式引用
  • 列表应该如何转换为具体的实现?

    假设我正在使用一个我不知道源代码的库 它有一个返回列表的方法 如下所示 public List
  • 如何将 Jfreechart(饼图)添加到 netbeans 的面板中

    我正在使用 netbeans gui 编辑器 并且正在尝试添加一个本身位于内部框架中的 Jfreechart 并且这个内部框架我想将其添加到面板中 正如您在此图中看到的那样 抱歉 我无法直接发布图像 因为我新手 http www flick
  • 计算日期之间的天数差异

    在我的代码中 日期之间的差异是错误的 因为它应该是 38 天而不是 8 天 我该如何修复 package random04diferencadata import java text ParseException import java t
  • 在 Spring Boot Actuator 健康检查 API 中启用日志记录

    我正在使用 Spring boot Actuator APIproject https imobilenumbertracker com 拥有一个健康检查端点 并通过以下方式启用它 management endpoints web base
  • 如何配置 WebService 返回 ArrayList 而不是 Array?

    我有一个在 jax ws 上实现的 java Web 服务 此 Web 服务返回用户的通用列表 它运行得很好 Stateless name AdminToolSessionEJB RemoteBinding jndiBinding Admi
  • 如何通过 Inno Setup for NetBeans 使用自定义 .iss 文件

    我将 Inno Setup 5 与 NetBeans 8 一起使用 并且我已经能够创建一个安装程序来安装该应用程序C users username local appname 但是我希望将其安装在C Programfiles 我如何在 Ne
  • 对象锁定私有类成员 - 最佳实践? (爪哇)

    I asked 类似的问题 https stackoverflow com questions 10548066 multiple object locks in java前几天 但对回复不满意 主要是因为我提供的代码存在一些人们关注的问题
  • 将图像添加到自定义 AlertDialog

    我制作了一个 AlertDialog 让用户可以从我显示的 4 个选项中选择一个 前 3 个让他们在单击号码时直接拨打号码 第 4 个显示不同的视图 现在看起来是这样的 由于第四个选项的目的是不同的任务 我想让它看起来不同 因为用户可能会感
  • 哪个集合更适合存储多维数组中的数据?

    我有一个multi dimensional array of string 我愿意将其转换为某种集合类型 以便我可以根据自己的意愿添加 删除和插入元素 在数组中 我无法删除特定位置的元素 我需要这样的集合 我可以在其中删除特定位置的数据 也
  • Java的-XX:+UseMembar参数是什么

    我在各种地方 论坛等 看到这个参数 并且常见的答案是它有助于高并发服务器 尽管如此 我还是找不到 sun 的官方文档来解释它的作用 另外 它是Java 6中添加的还是Java 5中存在的 顺便说一句 许多热点虚拟机参数的好地方是这一页 ht
  • 启动Java项目时发生类冲突:ClassMetadataReadingVisitor将接口org.springframework.asm.ClassVisitor作为超类

    我正在使用最新的Spring框架版本 3 2 2 RELEASE 开发一个Java Web项目 但是现在项目启动时遇到了问题 详细错误是 java lang IncompleteClassChangeError 类 org springfr
  • 在哪里存储 Java 的 .properties 文件?

    The Java教程 http download oracle com javase tutorial essential environment properties htmlon using Properties 讨论如何使用 Prop

随机推荐

  • Vue.js之事件的绑定(v-on: 或者 @ )

    1 Vue js事件绑定的一般格式 v on click function v on click mouseout mouseover click 2 Vue js事件绑定的实现 2 1 JavaScript代码
  • STM32 使用HAL库实现微秒级长延时

    STM32 使用HAL库实现微秒级长延时 背景 定时器初始化 主程序中的设计 背景 STM32 HAL库中有一个延时函数HAL Delay 可以实现毫秒级的延时 能够满足一般延时需求 在有些场合下 我们需要更精准的延时 同时可能会有较长时间
  • 智慧校园小程序-微信小程序毕业设计(附下载链接)

    2023年微信小程序毕业设计 智慧校园 点我下载项目资源 智慧校园小程序 校园是一个充满创造力和活力的地方 教育和互联网发展向纵深发展 智慧校园小程序开发是当前校园建设中不可或缺的互联网工具 通过智慧校园小程序 可以整合更多的校园服务基础设
  • 2022年油价的暴涨让你意识到了什么?

    2022年才刚刚开始 油价便以迅雷不及掩耳之势快速上涨几次 从本月3月3日24时起 油价上升之窗开启 从全国来看 92号汽油每升上涨0 2元 95号汽油每升上涨0 22元 0号柴油也不甘落后也每升上涨0 22元 而近几天国际原油价格上升幅度
  • 【目标检测】33、AutoAssign:Differentiable Label Assignment for Dense Object Detection

    文章目录 一 背景 二 方法 2 1 Prior level Center Weighting 2 2 Instance level Confidence Weighting 2 3 Loss 三 效果 论文 AutoAssign Diff
  • Oracle 10g RAC Dataguard Faileover

    环境 1 Oracle 10g RAC Oracle 10g RAC Dataguard最大性能模式配置 2 rac1 rac2 Primary Database 3 vmrac1 vmrac2 Physical Standby Datab
  • C语言课设学生籍贯信息记录簿(大作业)

    一 任务概述 文章仅供参考 进一步掌握和利用C语言进行课程设计的能力 进一步理解和运用结构化程序设计的思想和方法 初步掌握开发一个小型实用系统的基本方法 二 设计功能 1 创建信息链表并以磁盘文件保存 2 读取磁盘文件并显示输出所有学生的籍
  • alibaba druid数据库连接池详解

    1 介绍 Druid连接池是阿里巴巴开源的数据库连接池项目 Druid连接池为监控而生 内置强大的监控功能 监控特性不影响性能 功能强大 能防SQL注入 内置Loging能诊断Hack应用行为 2 下载 git地址 https github
  • 漏洞信息收集之——指纹识别

    指纹识别 目录 指纹识别 目的 常见指纹检测的对象 常见指纹识别方式 1 特定文件的MD5 2 正常页面或错误网页中包含的关键字 3 请求头信息的关键字匹配 4 部分URL中包含的关键字 比如wp includes dede等URL关键特征
  • luci 开发中一些小总结

    一 只保存不应用 当修改或者增加一项配置后 如果不是点击 保存 应用 按钮 而是点击 保存 按钮 这些配置不会保存各个到配置文件中 而是暂时保存到如下临时目录下 tmp uci 例如 当修改了网络配置 没有应用时 会生成一个 tmp uci
  • vue-quill-editor富文本编辑器多个音频上传显示覆盖问题

    新建的时候没有问题 正常提交 修改时候后台传过来的数据正确 但是渲染会导致前一个audio被后面一个覆盖掉 刷新audio标签就没有了 1 覆盖问题 将代码 that refs editRef content data content 改成
  • vue拖拽实现app或小程序装修界面

    vue拖拽实现app或小程序装修 一 最终效果图 参考引用作者 作者 李白不吃茶v 原作者源代码git地址 大神的源代码 这里是引用 二 需要安装的依赖 安装 vuedraggable 语法 npm install vuedraggable
  • 对于poll的总结

    参考书籍 linux高性能服务器编程 是个人对看书的总结 上文一致 poll系统调用和select类似 个人认为poll和select掌握一个就好了 也是在指定时间内轮询一定数量的文件描述符 以测试其中是否有就绪者 关于poll的原型 in
  • 数字滤波算法(一)——滑动平均滤波算法

    一 数字滤波器简介 数字滤波器是指通过一定的数据逻辑构成的可以滤除输入信号中的特定的噪声的算法 这里所指的数字滤波器主要包括平均值滤波 FIR滤波 CIC滤波等 在之后的实验中 我们将介绍不同数字滤波器的原理 通过MATLAB和FPGA分别
  • 目标检测标签格式转换 Json -> txt

    目标检测标签格式转换 Json gt txt 将Json格式的标签转换为YOLO格式标签 适用于用labelimg标注后 每个图片 对应一个Json标签的场合 import os import shutil import json 需要根据
  • Mask-RCNN应用 - 数据增强基础准备 - labelme 标注24位深RGB标注图转换为8位深RGB标注图

    MaskRCNN入门路径 gt Mask RCNN应用研究方法 持续更新中 如有问题或需要源码 指导 请私聊留下联系方式或用手机打开https m tb cn h fINaraE tk PCzA2jPp4V0进行咨询 本文介绍如何将24位深
  • ubuntu20.04 安装colmap 报错:Cmake error while running “cmake .. -GNinja“ command during installation

    日常Debug 按照官方文档安装Colmap时 在 这两步的时候报错 比如 1 Call Stack most recent call first usr local share cmake 3 27 Modules CMakeDeterm
  • 最全常用正则表达式大全

    一 校验数字的表达式 1 数字 0 9 2 n位的数字 d n 3 至少n位的数字 d n 4 m n位的数字 d m n 5 零和非零开头的数字 0 1 9 0 9 6 非零开头的最多带两位小数的数字 1 9 0 9 0 9 1 2 7
  • eclipse中包里建包

    1 设置Package Presentation 为Hierarchical 最为关键一步 2 在src下新建一个名为com abc hrm的包 名字根据自己需要而定 3 在父包下新建子包a 4 继续在父包 com abc hrm a 下新
  • Java基础学习——Java线程(二)同步代码块、同步方法、Lock锁、死锁程序例子、不同类型的锁

    对于之前买票的练习 又出现多个10张票的情况 这里对这一现象进行分析 对于代码 for int i 1 i lt 100 i if ticketNum gt 0 System out println 我在 this getName 买到了第