2.8.6 并发错误

2.8.6.1 概念

并发错误:多个线程共享操作同一个对象的时候,线程体当中连续的多行操作未必能够连续执行 很可能操作只完成了一部分,时间片突然耗尽,此时,另一个线程抢到时间片,直接拿走并访问了操作不完整的数据(操作不完整的数据,从逻辑上讲是错误数据)

根本原因:多个线程共享操作同一份数据

直接原因:线程体当中连续的多行语句,未必能够连续执行,很可能操作只完成了一半 时间片突然耗尽
此时另一个线程刚好抢到时间片,直接拿走了操作不完整的数据 - 错误数据

导火索:时间片突然耗尽

面试题: 并发错误和并发修改异常什么关系?
并发修改异常:是为了避免出现并发错误,而主动做的校验机制,当迭代器发现自己理想的modCount与集合反馈的modCount
不一致的时候,会认为有别的线程在同时操作这个集合,于是主动throw的异常 ConcurrentModificationException

2.8.6.2 解决并发错误

加锁:

第一种语法级别的加锁 = 互斥锁

  1. 使用synchronized修饰符

    ​ 互斥锁=互斥标记=锁标记=锁旗标=监视器=Monitor

    用法:

    • 修饰代码块

      synchronized(临界资源){需要连续执行的操作1;需要连续执行的操作2;···············;}
      
    • 修饰整个方法

      public synchronized void add(){}
      //等价于
      public void add(){synchronized(){}
      }//都是对对象加锁
      

      注意:即便synchronized加在方法上,其实还是对对象进行加锁,而且锁的是调用方法的那个对象…
      Java世界里只有每个对象才有锁标记,所以加锁只能对对象加锁。

    *:Vector Hashtable StringBuffer之所以线程安全,是因为底层大量方法都使用了synchronized修饰的

    *:单例模式的懒汉式,需要synchronized修饰那个getter方法

    public static  synchronized  Sun  getInstance(){return x;
    }
    

    *: synchronized有什么特性?它不能被子类方法继承得到
    父类当中线程安全的方法,当子类继承得到的时候,就没有synchronized修饰了,必须重写(覆盖)

    *:如果synchronized修饰静态方法,等价于对这个类的.class加锁(其实是对这个类的元对象加锁)

  2. 第二种面向对象思想的加锁 = 可重入锁

    java.util.concurrent.locks.ReentrantLock(jdk 5.0开始):java包的工具包的并发包的 可重入锁

    ReentrantLock :lock(加锁) unlock(解锁):放在finally{}中

    另外 可重入锁的构造方法可以传参指定
    公平锁 或 非公平锁 默认非公平锁

    *:JDK6.0之前这个Lock的机制比synchronized效率高很多
    JDK6.0开始 重新对synchronized修改了底层实现,加入了一堆新的概念 (偏向锁 轻量级锁 锁的自旋机制)
    从JDK6.0开始 synchronized 跟 Lock性能上不相上下

    *:ReentrantLock可以在构造方法中传公平锁和非公平锁(公平与否针对第一个先来的线程而言)

    公平锁:new Reetrantlock(true);

    解决并发错误案例

    import java.util.concurrent.*;
    public class TestCurrentError{public static void main(String[] args){Student stu=new Student("zml","女士");
    Lock lock=new ReentrantLock();
    Print p=new Print(stu);
    Change c=new Change(stu,lock);
    p.start();
    c.start();
    }
    }
    class Change extends Thread{Student stu;Lock lock;public Change(Student stu,Lock lock){this.stu=stu;this.lock=lock;}@Overridepublic void run(){boolean isOkay=true;while(true){//synchronized(stu){try{lock.lock();//ReentrantLock加锁if(isOkay){stu.name="梁朝伟";stu.gender="男士";}else{stu.name="张曼玉";stu.gender="女士";}isOkay=!isOkay;}finally{lock.unlock();//解锁}}//}}}class Print extends Thread{Student stu;public Print(Student stu){this.stu=stu;}@Overridepublic void run(){while(true){synchronized(stu){//synchronized实现加锁System.out.println(stu.name+":"+stu.gender);}}}}
    class Student{String name;
    String gender;
    public Student(String name,String gender){this.name=name;this.gender=gender;
    }
    }
    

    2.8.7 死锁

    互斥锁标记使用过多、或者使用不当,就会造成多个线程相互持有对方想要的资源不释放的情况下
    又去申请对方已经持有的资源,从而双双进入阻塞

    死锁经典案例:中美联合国饿死事件

    public class TestDeadLock{public static void main(String[] args){Resturant r=new Resturant();Resturant.Chinese c=r.new Chinese();Resturant.American a=r.new American();c.start();a.start();}}class Resturant{Object knife=new Object();Object chopsticks=new Object();class Chinese extends Thread{@Overridepublic void run(){System.out.println("中国人进入了餐厅");synchronized(knife){System.out.println("中国人拿了刀具");try{Thread.sleep(100);}catch(Exception e){e.printStackTrace();}synchronized(chopsticks){System.out.println("中国人拿到了筷子");}}System.out.println("中国人可以正常吃面条了");}}class  American extends Thread{@Overridepublic void run(){System.out.println("美国人进入了餐厅");synchronized(chopsticks){System.out.println("美国人拿了筷子");try{Thread.sleep(100);}catch(Exception e){e.printStackTrace();}synchronized(knife){System.out.println("美国人拿到了刀具");}}System.out.println("美国人可以正常吃牛排了");}}}

    2.8.8 如何解决死锁问题

    一块空间:对象的等待池

    三个方法:Object类的

wait():让当前线程释放对象的锁标记,并且进入调用方法的那个对象的等待池

​ notify(): 从调用方法的那个对象的等待池当中,随机的唤醒一个线程

​ notifyAll():从调用方法的那个对象的等待池当中,唤醒所有阻塞的线程

*:这三个方法都是Object类的方法,不是线程的方法,每个对象都有等待池,每个对象都可能操作等待池

*:这三个方法都必须在持有锁标记的前提下才能使用,所以它们必须出现在synchronized的{}当中,如果没有拿到对象的锁标记 就直接操作等待池,不但会操作失败,还会引发运行时异常illegalMonitorStateException

锁池和等待池的概念

Java中,每个对象都有两个池,锁(monitor)池和等待池

锁池:假设线程A已经拥有了某个对象(注意:不是类)的锁,而其它的线程想要调用这个对象的某个synchronized方法(或者synchronized块),由于这些线程在进入对象的synchronized方法之前必须先获得该对象的锁的拥有权,但是该对象的锁目前正被线程A拥有,所以这些线程就进入了该对象的锁池中。

等待池:假设一个线程A调用了某个对象的wait()方法,线程A就会释放该对象的锁(因为wait()方法必须出现在synchronized中,这样自然在执行wait()方法之前线程A就已经拥有了该对象的锁),同时线程A就进入到了该对象的等待池中。如果另外的一个线程调用了相同对象的notifyAll()方法,那么处于该对象的等待池中的线程就会全部进入该对象的锁池中,准备争夺锁的拥有权。如果另外的一个线程调用了相同对象的notify()方法,那么仅仅有一个处于该对象的等待池中的线程(随机)会进入该对象的锁池.

等待池与锁池的区别?

都是java当中每个对象都有一份的空间,而且都是存放线程对象的

锁池:存放想要拿到对象的锁标记,但是还没得到锁标记的线程

等待池:原本已经拿到锁标记,又不想跟别人形成相互制约,于是又主动释放锁标记的线程

三点区别:

进入的时候是否需要释放资源:锁池不需要释放;等待池需要

离开的时候是否需要调用方法:离开锁池不需要操作;离开等待池需要notify()/noyifyAll()

离开之后去到什么状态:离开锁池直接返回就绪;离开等待池直奔锁池

*:利用wait()和notify()实现两个线程交替执行

public class TestSwitchThread{public static void main(String[] args){Right r=new Right();Left l=new Left(r);l.start();}
}
class X {static Object obj=new Object();//定义一个锁机制(锁对象)}class Left extends Thread{Right r=new Right();public Left(Right r){//利用传参方式实现数据共享this.r=r;}@Overridepublic void run(){synchronized(X.obj){r.start();//在左脚拿到锁拥有权时启动右脚线程【此时右脚总会在左脚之后执行操作,就不会出现卡死现象】for(int i=0;i<1000;i++){System.out.println("左脚");//1try{X.obj.wait();}catch(Exception e){e.printStackTrace();}//2左脚等待,右脚执行X.obj.notify();//6左脚执行完毕,通知右脚进入锁池}}}}
class Right extends Thread{@Overridepublic void run(){synchronized(X.obj){for(int i=0;i<1000;i++){System.out.println("                          右脚");//3X.obj.notify();//4右脚执行完毕之后,提醒正在等待池的左脚进入锁池try{X.obj.wait();}catch(Exception e){e.printStackTrace();}//5右脚等待,左脚执行}}}}

感谢您的浏览与点赞,让我们一起快乐学java!

java中的并发错误和死锁相关推荐

  1. 《Java并发编程的艺术》——Java中的并发工具类、线程池、Execute框架(笔记)

    文章目录 八.Java中的并发工具类 8.1 等待多线程完成的CountDownLatch 8.2 同步屏障CyclicBarrier 8.2.1 CyclicBarrier简介 8.2.2 Cycl ...

  2. 《Java并发编程的艺术》读后笔记-Java中的并发工具类(第八章)

    文章目录 <Java并发编程的艺术>读后笔记-Java中的并发工具类(第八章) 1.等待多线程完成的CountDownLatch 2.同步屏障CyclicBarrier 2.1 Cycli ...

  3. Matlab函数打包为.jar后在java中调用出现错误:Exception:com.mathworks.toolbox.javabuilder.MWException: An error occur

    Matlab函数打包为.jar后在java中调用出现错误:Exception:com.mathworks.toolbox.javabuilder.MWException: An error occur ...

  4. 初识Java中的并发

    并发 20170610.是<Thinking in Java>中第21章的读书笔记,只是一些概念上的总结,真正实用还要自己多码. 21.1 并发的多面性 如果你有一台多处理器的机器,那么就 ...

  5. Java中的并发编程

    记录Java并发编程的知识,包括并发编程的详细介绍,并发编程解决的问题,volatile关键字,各种锁机制,synchronized的底层原理,CAS机制,AQS机制,以及JUC里面常见方法 文章目录 ...

  6. 在java中下列描述错误的是_在 JAVA 中 , 关于类的方法 , 下列描述错误的是 ()._学小易找答案...

    [多选题]价值的特性是 [简答题]输入任一字符串,统计其中数字,字母及其它字符个数 .(25分) [填空题]1.产品整体包括哪五个基本层次 2核心层次产品最基本的层次,是产品的_____ [单选题]纸 ...

  7. JAVA中关于并发的一些理解

    2019独角兽企业重金招聘Python工程师标准>>> 一,JAVA线程是如何实现的? 同步,涉及到多线程操作,那在JAVA中线程是如何实现的呢? 操作系统中讲到,线程的实现(线程模 ...

  8. 【搞定Java并发编程】第24篇:Java中的并发工具类之CountDownLatch

    上一篇:Java中的阻塞队列 BlockingQueue 详解 本文目录: 1.CountDownLatch的基本概述 2.CountDownLatch的使用案例 3.CountDownLatch的源 ...

  9. Java中的编译错误和运行错误如何分辨

      昨天小白作者经历了一场冷酷的考试洗礼,4.5个编译错误和运行错误的判断彻底把我整蒙圈了.相信有很多小伙伴跟我一样蒙圈,不过经过不懈的努力查找与思考,总结了自己的一些想法跟大家分享一下~~   在解 ...

最新文章

  1. windows禁用/启用hyper-V,解决hyper-V与模拟器同时启用时造成冲突
  2. ActiveMQ传输文件的几种方式原理与优劣
  3. PostgreSQL 恢复大法 - 恢复部分数据库、跳过坏块、修复无法启动的数据库
  4. VTK:可视化之MultipleActors
  5. Analyzer报表结果行
  6. Form组件之详解字段
  7. python的本质是什么意思_python生成器指的是什么意思
  8. CentOS下搭建docker+.net core
  9. 社会达尔文主义 盛行时间_新达尔文主义的心理理论
  10. Xposed 之旅 -- 微信防撤回
  11. eclipse jade插件安装
  12. 在国产系统(Linux)上,安装运行Steam游戏详解
  13. cyclone小知识(二)——cyclone加载扫描工程的数据
  14. java 纳秒 位数_java-解析少于6位的纳秒
  15. 【FlashDB】第二步 FlashDB 移植 STM32L475 使用QSPI驱动外部 flash W25Q64之 SFUD 移植
  16. 畜牧业适宜性评价算法
  17. Ubuntu设置仅允许特定用户或特定IP通过ssh访问
  18. Linux下磁盘配额设置
  19. IHERB上婴幼儿营养补充保健系列介绍
  20. ffmpeg源码简析(九)av_log(),AVClass,AVOption

热门文章

  1. python制作京东评论词云图
  2. 股市----别人的经验
  3. Mas短信开发增值服务平台建设
  4. ROS学习笔记-cmd_vel转换成两轮小车速度
  5. Java中g的数据类型
  6. python柱状图zzt_Python torch.diag方法代碼示例
  7. Qiskit中的barrier()是干啥的
  8. 数据分析:Day01软件安装及基础
  9. Netty编码解码器
  10. 洛谷 P2842 LJJ算数