多线程(四)死锁与等待唤醒机制

版权声明: https://blog.csdn.net/qq_33278885/article/details/81584174

虽然我们可以理解同步代码块和同步方法的锁对象问题,
但是我们并没有直接看到在哪里加上了锁,
在哪里释放了锁,
为了更清晰的表达如何加锁和释放锁,
JDK5以后提供了一个新的锁对象Lock
Lock  接口

ReentrantLock 实现类

void lock()
void unlock()

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

/*锁Lock
 *     void lock       加锁
 *     void unlock     释放锁
 *    
 * ReentrantLock    lock的实现类
 * 
 * */
public class MyLock implements Runnable {
	private int piao=100;
	private Lock lock=new ReentrantLock();
	@Override
	public void run() {
		while(true){
			//加锁
			lock.lock();
			if(piao>0){
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println("正在卖出第"+piao--+"票");
			}
			//释放锁
			lock.unlock();
		}
	}
	
}

public class LockDemo {
public static void main(String[] args) {
		MyLock l=new MyLock();
		Thread t1=new Thread(l,"窗口1");
		Thread t2=new Thread(l,"窗口2");
		Thread t3=new Thread(l,"窗口3");
		
		t1.start();
		t2.start();
		t3.start();
}
}

死锁问题


同步弊端:
        A 效率低
        B 如果出现了同步嵌套,就容易产生死锁问题
死锁问题及其代码

        A 是指两个或者两个以上的线程在执行的过程中,因争夺资源产生的一种互相等待现象
        B 同步代码块的嵌套案例
        

public class MyLock {
  public static final  Object objA=new Object();
  public static final  Object objB=new Object();
}
public class DieLock extends Thread {
	private boolean flag;
	DieLock(boolean flag){
		this.flag=flag;
	}
	@Override
	public void run() {
		if(flag){
			synchronized(MyLock.objA){
			System.out.println("if OBJA");
			synchronized(MyLock.objB){
				System.out.println("if OBJB");
			}
			}
			
		}else{
			synchronized(MyLock.objB){
			System.out.println("else OBJB");
			synchronized(MyLock.objA){
				System.out.println("else OBJA");
			    }
		    }
	}
}
}
public class DieLockDemo {
public static void main(String[] args) {
	DieLock d=new DieLock(true);
	DieLock d2=new DieLock(false);
	d.start();
	d2.start();
}
}

线程间通信:
针对同一个资源的操作有不同种类的线程
举例:卖票有进的,也有出的。
通过设置线程(生产者)和获取线程(消费者)针对同一个学生对象进行操作

public class Student {
	 String name;
	 int age;
}

public class GetThread implements Runnable {
	private Student s;
	GetThread(Student s){this.s=s;}
	@Override
	public void run() {
		//Student s=new Student();
		System.out.println(s.name+"---"+s.age);
		
	}
}

public class SetThread implements Runnable {
	private Student s;
	SetThread(Student s){this.s=s;}
	public void run() {
		//Student s=new Student();
		s.name="周杰伦";
		s.age=27;
	}
}

/*
 * 分析:
 * 		资源类:Student
 * 		设置学生数据:SetThread(生产者)
 * 		获取学生数据:GetThread(消费者)
 * 		测试类:StudentDemo
 * 
 * 		   问题1:按照思路写代码,发现数据每次都是:null---0
 *       原因:我们在每个线程中都创建了新的资源,而我们要求的时候设置和获取线程的资源应该是同一个
 *       如何实现呢?
 * 		   在外界把这个数据创建出来,通过构造方法传递给其他的类。
 * 
 * 
 * */
public class StudentDemo {
public static void main(String[] args) {
	        //创建资源
			Student s = new Student();
			
			//设置和获取的类
			SetThread st = new SetThread(s);
			GetThread gt = new GetThread(s);

			//线程类
			Thread t1 = new Thread(st);
			Thread t2 = new Thread(gt);

			//启动线程
			t1.start();
			t2.start();
}
}

这里有一个问题,虽然我们模拟了,当有生产后,其值会输出,但是因为线程存在随机性,所以当线程GetThread第一抢到执行权的时候,这时参数还没有设置,他的值同样还是nul---0,如    如下所示    我们故意将其调度权限设置为一个最低(设置)    一个最高(获取),得到如下画面。

改进:

public class GetThread implements Runnable {
	private Student s;
	GetThread(Student s){this.s=s;}
	@Override
	public void run() {
		while(true){
			synchronized(s){
		System.out.println(s.name+"---"+s.age);
		}
		}
	}
}

public class SetThread implements Runnable {
	private Student s;
	SetThread(Student s){this.s=s;}
	public void run() {
		int x=0;
		while(true){
		synchronized(s){	
		if(x%2==0){
		s.name="周杰伦";
		s.age=27;
		}else{
		s.name="张杰";
		s.age=18;
		}
		x++;
		}
	}
}
}

public class Student {
	String name;
	int age;
}


/*
 * 分析:
 * 		资源类:Student
 * 		设置学生数据:SetThread(生产者)
 * 		获取学生数据:GetThread(消费者)
 * 		测试类:StudentDemo
 * 
 * 		   问题1:按照思路写代码,发现数据每次都是:null---0
 *       原因:我们在每个线程中都创建了新的资源,而我们要求的时候设置和获取线程的资源应该是同一个
 *       如何实现呢?
 * 		   在外界把这个数据创建出来,通过构造方法传递给其他的类。
 * 
 * 
 * 		问题2:为了数据的效果好一些,我加入了循环和判断,给出不同的值,这个时候产生了新的问题
 * 		A:同一个数据出现多次
 * 		B:姓名和年龄不匹配
 * 
 * 		原因:
 * 		A:同一个数据出现多次
 * 				cpu一点点时间片的执行权,就足够执行很多次
 * 		B:姓名和年龄不匹配
 * 				线程运行的随机性
 * 
 * 	        线程安全问题:
 * 		A:是否是多线程环境		          是
 * 		B:是否有共享数据		          是
 * 		C:是否有多条语句操作共享数据	是
 * 
 * 
 *      解决方案:
 * 		加锁。
 * 		注意:
 * 			A:不同种类的线程都要加锁。
 * 			B:不同种类的线程加的锁必须是同一把。
 * 
 * 
 * 
 * 
 * */
public class StudentDemo {
public static void main(String[] args) {
	        //创建资源
			Student s = new Student();
			
			//设置和获取的类
			SetThread st = new SetThread(s);
			GetThread gt = new GetThread(s);

				
			//线程类
			Thread t1 = new Thread(st);
			Thread t2 = new Thread(gt);
			//t1.setPriority(1);
			//t2.setPriority(10);
			
			//启动线程
			t1.start();
			t2.start();
}
}

等待唤醒机制:

利用等待唤醒机制,我们可以实现上述问题

public class GetThread implements Runnable {
	private Student s;
	GetThread(Student s){this.s=s;}
	@Override
	public void run() {
		while(true){
		synchronized(s){
		if(!s.flag){
			try {
				s.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		System.out.println(s.name+"---"+s.age);
		//获取完成后,将状态改为false
		s.flag=false;
		//唤醒线程
		s.notify();
		}
		}
	}
}


public class SetThread implements Runnable {
	private Student s;
	SetThread(Student s){this.s=s;}
	public void run() {
		int x=0;
		while(true){
		synchronized(s){
		if(s.flag){
			try {
				s.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		if(x%2==0){
		s.name="周杰伦";
		s.age=27;
		}else{
		s.name="张杰";
		s.age=18;
		}
		x++;
		s.flag=true;
		s.notify();
		}
		}
	}
}


public class Student {
	 String name;
	 int age;
	 boolean flag;//默认情况下是没有数据的 
}


/*
 * 分析:
 * 		资源类:Student	
 * 		设置学生数据:SetThread(生产者)
 * 		获取学生数据:GetThread(消费者)
 * 		测试类:StudentDemo
 * 
 * 问题1:按照思路写代码,发现数据每次都是:null---0
 * 原因:我们在每个线程中都创建了新的资源,而我们要求的时候设置和获取线程的资源应该是同一个
 * 如何实现呢?
 * 		在外界把这个数据创建出来,通过构造方法传递给其他的类。
 * 
 * 问题2:为了数据的效果好一些,我加入了循环和判断,给出不同的值,这个时候产生了新的问题
 * 		A:同一个数据出现多次
 * 		B:姓名和年龄不匹配
 * 原因:
 * 		A:同一个数据出现多次
 * 			CPU的一点点时间片的执行权,就足够你执行很多次。
 * 		B:姓名和年龄不匹配
 * 			线程运行的随机性
 * 线程安全问题:
 * 		A:是否是多线程环境		是
 * 		B:是否有共享数据		是
 * 		C:是否有多条语句操作共享数据	是
 * 解决方案:
 * 		加锁。
 * 		注意:
 * 			A:不同种类的线程都要加锁。
 * 			B:不同种类的线程加的锁必须是同一把。
 * 
 * 问题3:虽然数据安全了,但是呢,一次一大片不好看,我就想依次的一次一个输出。
 * 如何实现呢?
 * 		通过Java提供的等待唤醒机制解决。
 * 
 * 等待唤醒:
 * 		Object类中提供了三个方法:
 * 			wait():等待
 * 			notify():唤醒单个线程
 * 			notifyAll():唤醒所有线程
 * 		为什么这些方法不定义在Thread类中呢?
 * 			这些方法的调用必须通过锁对象调用,而我们刚才使用的锁对象是任意锁对象。
 * 			所以,这些方法必须定义在Object类中。
 */
public class StudentDemo {
	public static void main(String[] args) {
		//创建资源
		Student s = new Student();
		
		//设置和获取的类
		SetThread st = new SetThread(s);
		GetThread gt = new GetThread(s);

		//线程类
		Thread t1 = new Thread(st);
		Thread t2 = new Thread(gt);

		//启动线程
		t1.start();
		t2.start();
	}
}

但是这里同样有一个问题,在资源类Student中,我们将成员没有封装起来,这个是不对的,不符合保密要求的,那么应该怎样进行改进呢?

public class GetThread implements Runnable {
	private Student s;
	GetThread(Student s){this.s=s;}
	@Override
	public void run() {
		while(true){
		s.get();
		}
	}
}

package Test_16;

public class Student {
	 private String name;
	 private int age;
	 private boolean flag;//默认情况下是没有数据的 
	 
	 public synchronized void set(String name,int age){
		 if(this.flag){
			 try {
				this.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		 }
		 //赋值
		 this.name=name;
		 this.age=age;
		 //改变状态
		 this.flag=true;
		 //唤醒线程
		 this.notify();
	 }
	 
	 public synchronized void get(){
		 if(!this.flag){
			try {
				this.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		 }
		 System.out.println(this.name+"-----"+this.age);
		 //改变状态
		 this.flag=false;
		 //唤醒线程
		this.notify();
	 } 
}



public class SetThread implements Runnable {
	private Student s;
	private  int x=0;
	SetThread(Student s){this.s=s;}
	public void run() {
	  //  int x=0;
		while(true){	
		if(x%2==0){
			s.set("周杰伦", 27);
		}else{
			s.set("张杰", 18);
		}
		x++;	
		}
	}
}



/*
 * 分析:
 * 		资源类:Student	
 * 		设置学生数据:SetThread(生产者)
 * 		获取学生数据:GetThread(消费者)
 * 		测试类:StudentDemo
 * 
 * 问题1:按照思路写代码,发现数据每次都是:null---0
 * 原因:我们在每个线程中都创建了新的资源,而我们要求的时候设置和获取线程的资源应该是同一个
 * 如何实现呢?
 * 		在外界把这个数据创建出来,通过构造方法传递给其他的类。
 * 
 * 问题2:为了数据的效果好一些,我加入了循环和判断,给出不同的值,这个时候产生了新的问题
 * 		A:同一个数据出现多次
 * 		B:姓名和年龄不匹配
 * 原因:
 * 		A:同一个数据出现多次
 * 			CPU的一点点时间片的执行权,就足够你执行很多次。
 * 		B:姓名和年龄不匹配
 * 			线程运行的随机性
 * 线程安全问题:
 * 		A:是否是多线程环境		是
 * 		B:是否有共享数据		是
 * 		C:是否有多条语句操作共享数据	是
 * 解决方案:
 * 		加锁。
 * 		注意:
 * 			A:不同种类的线程都要加锁。
 * 			B:不同种类的线程加的锁必须是同一把。
 * 
 * 问题3:虽然数据安全了,但是呢,一次一大片不好看,我就想依次的一次一个输出。
 * 如何实现呢?
 * 		通过Java提供的等待唤醒机制解决。
 * 
 * 等待唤醒:
 * 		Object类中提供了三个方法:
 * 			wait():等待
 * 			notify():唤醒单个线程
 * 			notifyAll():唤醒所有线程
 * 		为什么这些方法不定义在Thread类中呢?
 * 			这些方法的调用必须通过锁对象调用,而我们刚才使用的锁对象是任意锁对象。
 * 			所以,这些方法必须定义在Object类中。
 * 
 * 最终版代码中:
 * 		把Student的成员变量给私有的了。
 * 		把设置和获取的操作给封装成了功能,并加了同步。
 * 		设置或者获取的线程里面只需要调用方法即可。
 */
public class StudentDemo {
	public static void main(String[] args) {
		//创建资源
		Student s = new Student();
		
		//设置和获取的类
		SetThread st = new SetThread(s);
		GetThread gt = new GetThread(s);

		//线程类
		Thread t1 = new Thread(st);
		Thread t2 = new Thread(gt);

		//启动线程
		t1.start();
		t2.start();
	}
}

线程状态转换图

猜你喜欢

转载自blog.csdn.net/qq_33278885/article/details/81584174