java中的并发错误和死锁

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;
    		}
    	@Override
    	public 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;
    		}
    	@Override
    	public 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{
    		@Override
    		public 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{
    			@Override
    			public 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;
		}
	@Override
	public void run(){

			synchronized(X.obj){
				r.start();//在左脚拿到锁拥有权时启动右脚线程【此时右脚总会在左脚之后执行操作,就不会出现卡死现象】
				for(int i=0;i<1000;i++){
				System.out.println("左脚");//1
				try{X.obj.wait();}catch(Exception e){e.printStackTrace();}//2左脚等待,右脚执行
				X.obj.notify();//6左脚执行完毕,通知右脚进入锁池
				}
			}
		}
	}
class Right extends Thread{
	@Override
	public void run(){

			synchronized(X.obj){
				for(int i=0;i<1000;i++){
				System.out.println("							右脚");//3
				X.obj.notify();//4右脚执行完毕之后,提醒正在等待池的左脚进入锁池
				try{X.obj.wait();}catch(Exception e){e.printStackTrace();}//5右脚等待,左脚执行
				}
			}
		}
	}

在这里插入图片描述

感谢您的浏览与点赞,让我们一起快乐学java!
在这里插入图片描述

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值