java中的并发错误和死锁
2.8.6 并发错误
2.8.6.1 概念
并发错误:多个线程共享操作同一个对象的时候,线程体当中连续的多行操作未必能够连续执行 很可能操作只完成了一部分,时间片突然耗尽,此时,另一个线程抢到时间片,直接拿走并访问了操作不完整的数据(操作不完整的数据,从逻辑上讲是错误数据)
根本原因:多个线程共享操作同一份数据
直接原因:线程体当中连续的多行语句,未必能够连续执行,很可能操作只完成了一半 时间片突然耗尽
此时另一个线程刚好抢到时间片,直接拿走了操作不完整的数据 - 错误数据
导火索:时间片突然耗尽
面试题: 并发错误和并发修改异常什么关系?
并发修改异常:是为了避免出现并发错误,而主动做的校验机制,当迭代器发现自己理想的modCount与集合反馈的modCount
不一致的时候,会认为有别的线程在同时操作这个集合,于是主动throw的异常 ConcurrentModificationException
2.8.6.2 解决并发错误
加锁:
第一种语法级别的加锁 = 互斥锁
使用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加锁(其实是对这个类的元对象加锁)
第二种面向对象思想的加锁 = 可重入锁
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中的并发错误和死锁相关推荐
- 《Java并发编程的艺术》——Java中的并发工具类、线程池、Execute框架(笔记)
文章目录 八.Java中的并发工具类 8.1 等待多线程完成的CountDownLatch 8.2 同步屏障CyclicBarrier 8.2.1 CyclicBarrier简介 8.2.2 Cycl ...
- 《Java并发编程的艺术》读后笔记-Java中的并发工具类(第八章)
文章目录 <Java并发编程的艺术>读后笔记-Java中的并发工具类(第八章) 1.等待多线程完成的CountDownLatch 2.同步屏障CyclicBarrier 2.1 Cycli ...
- Matlab函数打包为.jar后在java中调用出现错误:Exception:com.mathworks.toolbox.javabuilder.MWException: An error occur
Matlab函数打包为.jar后在java中调用出现错误:Exception:com.mathworks.toolbox.javabuilder.MWException: An error occur ...
- 初识Java中的并发
并发 20170610.是<Thinking in Java>中第21章的读书笔记,只是一些概念上的总结,真正实用还要自己多码. 21.1 并发的多面性 如果你有一台多处理器的机器,那么就 ...
- Java中的并发编程
记录Java并发编程的知识,包括并发编程的详细介绍,并发编程解决的问题,volatile关键字,各种锁机制,synchronized的底层原理,CAS机制,AQS机制,以及JUC里面常见方法 文章目录 ...
- 在java中下列描述错误的是_在 JAVA 中 , 关于类的方法 , 下列描述错误的是 ()._学小易找答案...
[多选题]价值的特性是 [简答题]输入任一字符串,统计其中数字,字母及其它字符个数 .(25分) [填空题]1.产品整体包括哪五个基本层次 2核心层次产品最基本的层次,是产品的_____ [单选题]纸 ...
- JAVA中关于并发的一些理解
2019独角兽企业重金招聘Python工程师标准>>> 一,JAVA线程是如何实现的? 同步,涉及到多线程操作,那在JAVA中线程是如何实现的呢? 操作系统中讲到,线程的实现(线程模 ...
- 【搞定Java并发编程】第24篇:Java中的并发工具类之CountDownLatch
上一篇:Java中的阻塞队列 BlockingQueue 详解 本文目录: 1.CountDownLatch的基本概述 2.CountDownLatch的使用案例 3.CountDownLatch的源 ...
- Java中的编译错误和运行错误如何分辨
昨天小白作者经历了一场冷酷的考试洗礼,4.5个编译错误和运行错误的判断彻底把我整蒙圈了.相信有很多小伙伴跟我一样蒙圈,不过经过不懈的努力查找与思考,总结了自己的一些想法跟大家分享一下~~ 在解 ...
最新文章
- windows禁用/启用hyper-V,解决hyper-V与模拟器同时启用时造成冲突
- ActiveMQ传输文件的几种方式原理与优劣
- PostgreSQL 恢复大法 - 恢复部分数据库、跳过坏块、修复无法启动的数据库
- VTK:可视化之MultipleActors
- Analyzer报表结果行
- Form组件之详解字段
- python的本质是什么意思_python生成器指的是什么意思
- CentOS下搭建docker+.net core
- 社会达尔文主义 盛行时间_新达尔文主义的心理理论
- Xposed 之旅 -- 微信防撤回
- eclipse jade插件安装
- 在国产系统(Linux)上,安装运行Steam游戏详解
- cyclone小知识(二)——cyclone加载扫描工程的数据
- java 纳秒 位数_java-解析少于6位的纳秒
- 【FlashDB】第二步 FlashDB 移植 STM32L475 使用QSPI驱动外部 flash W25Q64之 SFUD 移植
- 畜牧业适宜性评价算法
- Ubuntu设置仅允许特定用户或特定IP通过ssh访问
- Linux下磁盘配额设置
- IHERB上婴幼儿营养补充保健系列介绍
- ffmpeg源码简析(九)av_log(),AVClass,AVOption