深度分析线程的死锁和解决方案(高效解决生产者和消费者问题)

开始时间:2018年8月30日15:28:19

结束时间:

累计时间:昨天上课两小时+

一:多线程: 
 进程: 内存单元: 
 线程: 内存单元当中的一条执行单元: 
 实现多线程的方式: 
 (1)extends Thread类: 任务和对象。 
 (2)implements Runnable  :  封装了线程任务: run 
    Thread t= new Thread(任务对象);
    
 售票: 
  错误的数据: 
  原因: (1)有共享数据: 
               (2)多条线程对共享的数据进行了相关的数学运算: 
               
               
 解决: 同步块: 
 语法: 
 synchronized(锁对象){
          共享资源
 }
 
 优点: 解决数据安全的问题: 
 弊端: 效率低。 
 
 
 同步的前提: 
 (1)多个线程之间必须使用同一把锁。 
 

需求: 
 两个客户到一个银行去存钱,每个客户一次存100,存3次。  
问题: 该程序是否有线程安全问题发生? 如果有,写出分析过程。 定义解决方案。 


运行程序的结果: 
sum=200
sum=200
sum=400
sum=400
sum=500
sum=600
数据的安全问题:  数据的安全问题发生。 
原因: 
(1)共享数据: sum
(2)对共享数据进行了操作: sum+=100; 

数据安全问题的解决: 

 将共享资源加入同步块:
  synchronized (){
  
  }
  
二: 解决数据安全问题: 
  方式二:同步函数: 
  
  语法: 
  访问权限修饰符  synchronized 返回值 方法的名称(){
  
 }
 同步函数: 同步函数会将函数内部所有的内容,都加锁。 
 同步函数上加的锁: this 锁: 因为: 同步函数必须被对象调用:  
 
 验证: 验证同步方法上的锁 this锁: 
 
 
三: 静态同步函数上的锁: 
   静态同步函数上的锁    字节码文件对象:Class : 
   类名.class 
  
四: 同步函数和同步块的异同点:   
  相同点: 都能解决数据安全问题: 
  不同点: 
    (1)语法上不同: 
    (2) 同步函数上的锁:只能是this锁: 
               同步块上的锁对象可以是任意锁对象。 
     (3)同步函数: 将整个函数当中所有的内容都加锁: 
              同步块: 索取的对象自由。 
    (4)同步块比同步函数灵活,所以建议大家使用同步块。 
    (5)弊端: 同步函数不灵活, 同步块层级深:
      
  什么时候使用同步块 或者是同步函数? 
  当线程当中只使用到了一个锁对象,可以使用同步函数。 
  当线程当中使用了多个锁对象,必须使用同步块。 

 

下面程序验证同步函数的锁是this锁

/*
 * (1)使用同步块解决数据安全: 
 * 
 * (2) 使用同步函数解决数据的安全问题: 
 * 
 * (3)验证 同步函数的锁 是this锁: 
 *  场景:  ticket当中定义一个标志位: 
 *  
 *  run方法当中: 
 *   if(flag){
 *     //同步块售票
 *   }else{
 *     //同步函数:
 *   }
 *  
 *  当同步块和同步函数都存在的时候,出现了安全问题,说明使用不是同一把锁。 
 *  当同步块使用的是this锁, 次数数据安全问题得到解决。 
 *  结论:同步函数和同步块上使用的是用一把锁,从而验证了 同步函数上的锁是this锁。
 *  
 *  Thread-0  0 结果: 
 *  
 */
class Ticket implements Runnable {
   
	private static  int tickets =1000;
	Object obj = new Object();
	
	//定义一个标志位: 
    boolean flag = true;
	
	//使用同步函数: 
	/*public synchronized void sale(){
		if(tickets>0){//说明邮票 当tickets=1;
			// 1    获得了锁对象: 
			try {
				
				Thread.sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName()+"  "+ tickets--);
			
		}
	}*/
   
    //静态的同步函数: 锁对象不是this。 Ticket.class
    public static synchronized void sale(){//字节码文件对象: 
		if(tickets>0){//说明邮票 当tickets=1;
			// 1    获得了锁对象: 
			/*try {
				
				Thread.sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}*/
			System.out.println(Thread.currentThread().getName()+"  "+ tickets--);
			
		}
	}
	@Override
	public  void run() {
 
		//定义循环; 
		if(flag){//使用同步块卖票: 
			while(true){
				synchronized (this) {// 静态方法: Ticket.class 静态方法上的默认锁。
					// 1 : cpu的执行权限,并且获得了锁对象。 
					if(tickets>0){//说明邮票 当tickets=1;
						// 1    获得了锁对象: 
						try {
							
							Thread.sleep(100);
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
						System.out.println(Thread.currentThread().getName()+"  "+ tickets--);
						
					}
				}
			}
		}else{//使用同步方法卖
			while(true){
				sale();
			}
		}
	}

}


public class ThreadDemo02 {
	public static void main(String[] args) throws InterruptedException {
		//创建线程任务对象: 
		Ticket t= new Ticket();//封装了任务:
		//创建线程对象: 
		Thread t1 = new Thread(t);
		Thread t2 = new Thread(t);
	
		
		t1.start();
		
		//由于CPU的切换速度很快, 开启了一条线程后,t1还没来记得售卖, 将标志位设置为false。 
		// 导致只有一天线程在这儿售票: 
		Thread.sleep(10);
		//改变标志位: 
		t.flag=false;
		
		t2.start();
	
		
	}
}

  
 五: 面试问题:
  单例模式:数据安全问题: 
  饿汉模式:不会出现数据安全问题: 
  懒汉模式: 
     * 懒汉模式: 在多线程的环境当中会出现数据安全问题: 
     * 原因: 
     *   共享资源: s这个对象: 
     *   对s这个对象进行了操作。
     *   
     *  解决: 同步块解决: 
     *public static Singleton1 getInstance(){
        // 1 2 3 4  5  6 7 8
        if(s==null){
            // 1 2  3 4 
            synchronized (Singleton1.class) {
                if(s==null){
                    // 1 
                    s= new Singleton1();
                }
            }
        }
        return s; 
    }
    * 建议: 单例模式:使用饿汉模式:

package com.yidongxueyuan.thread;


//单例模式: 
// 饿汉模式:  不会出现数据安全问题: 
class Singleton {
	private static final Singleton s= new Singleton();
	
	private Singleton(){
		
	}
	
	public static Singleton getInstance(){
		return s; 
	}
}

//懒汉模式
class Singleton1 {
	private static  Singleton1 s;// null
	
    private Singleton1(){
		
	}
    
    // 1 2 3 4 
	public static Singleton1 getInstance(){
		// 1 2 3 4  5  6 7 8
		if(s==null){
			// 1 2  3 4 
			synchronized (Singleton1.class) {
				if(s==null){
					// 1 
					s= new Singleton1();
				}
			}
		}
		return s; 
	}
	
	/*
	 * 懒汉模式: 在多线程的环境当中会出现数据安全问题: 
	 * 原因: 
	 *   共享资源: s这个对象: 
	 *   对s这个对象进行了操作。
	 *   
	 *  解决: 同步块解决: 
	 *  
	 */
	
}

  


public class ThreadDemo03 {

}


    
    
 六:  不能在run方法上加锁: 
   原因: run方法当中封装了线程任务,需要被多条线程同时操作,提高效率。 
   如果直接在run方法上加锁, 相当于只能有一条线程操作run方法,此时多线程
   编程了单线程。 
   

 

 

 

关于娱乐圈分手月随便说几句

与其惋惜 不如祝福

你若安好 便是晴天

例: 两个线程等待运行,一个线程占了一根筷子, 运行需要两根筷子。出现死锁。

在下面程序中, 上面的线程握住sb1的锁,睡眠之后 执行下面的线程,下面线程握住sb2的锁,

上面的线程执行完毕需要获得sb2的锁 被下面线程握住。

同理下面线程执行完毕需要的锁被上面线程握住。

两个线程都需要对方释放自己的资源,才能运行。

反观恋爱

1 在一起的时候都不愿先开口 ,谁先开口谁就输了。

2 发现不合适的的时候还是不愿意先开口,我觉得能互相成全最好,与其死耗着那不如我当这个罪人好了。

娱乐圈分手的这么多,与其为之惋惜,不如为其祝福。

 

下面一个简单的典型死锁

//死锁的问题:处理线程同步时容易出现。
//不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁
//写代码时,要避免死锁!
public class TestDeadLock {
	static StringBuffer sb1 = new StringBuffer();
	static StringBuffer sb2 = new StringBuffer();

	public static void main(String[] args) {
		new Thread() {
			public void run() {
				synchronized (sb1) {
					try {
						Thread.currentThread().sleep(10);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					sb1.append("A");
					synchronized (sb2) {
						sb2.append("B");
						System.out.println(sb1);
						System.out.println(sb2);
					}
				}
			}
		}.start();

		new Thread() {
			public void run() {
				synchronized (sb2) {
					try {
						Thread.currentThread().sleep(10);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					sb1.append("C");
					synchronized (sb1) {
						sb2.append("D");
						System.out.println(sb1);
						System.out.println(sb2);
					}
				}
			}
		}.start();
	}

}

主要是线程交叉的时候出现sleep的情况 会出现死锁多一些吧。

 

线程通信

生产者与消费者问题

下例程序实现了线程通信

/*
 * 生产者/消费者问题
 * 生产者(Productor)将产品交给店员(Clerk),而消费者(Customer)从店员处取走产品,
 * 店员一次只能持有固定数量的产品(比如:20),如果生产者试图生产更多的产品,店员会叫生产者停一下,
 * 如果店中有空位放产品了再通知生产者继续生产;如果店中没有产品了,店员会告诉消费者等一下,
 * 如果店中有产品了再通知消费者来取走产品。

	分析:
	1.是否涉及到多线程的问题?是!生产者、消费者
	2.是否涉及到共享数据?有!考虑线程的安全
	3.此共享数据是谁?即为产品的数量
	4.是否涉及到线程的通信呢?存在这生产者与消费者的通信

 */
class Clerk{//店员
	int product;
	
	public synchronized void addProduct(){//生产产品
		if(product >= 20){
			try {
				wait();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}else{
			product++;
			System.out.println(Thread.currentThread().getName() + ":生产了第" + product + "个产品");
			notifyAll();
		}
	}
	public synchronized void consumeProduct(){//消费产品
		if(product <= 0){
			try {
				wait();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}else{
			System.out.println(Thread.currentThread().getName() + ":消费了第" + product + "个产品");
			product--;
			notifyAll();
		}
	}
}

class Producer implements Runnable{//生产者
	Clerk clerk;
	
	public Producer(Clerk clerk){
		this.clerk = clerk;
	}
	public void run(){
		System.out.println("生产者开始生产产品");
		while(true){
			try {
				Thread.currentThread().sleep(100);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			clerk.addProduct();
			
		}
	}
}
class Consumer implements Runnable{//消费者
	Clerk clerk;
	public Consumer(Clerk clerk){
		this.clerk = clerk;
	}
	public void run(){
		System.out.println("消费者消费产品");
		while(true){
			try {
				Thread.currentThread().sleep(10);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			clerk.consumeProduct();
		}
	}
}


public class TestProduceConsume {
	public static void main(String[] args) {
		Clerk clerk = new Clerk();
		Producer p1 = new Producer(clerk);
		Consumer c1 = new Consumer(clerk);
		Thread t1 = new Thread(p1);//一个生产者的线程
		Thread t3 = new Thread(p1);
		Thread t2 = new Thread(c1);//一个消费者的线程
		
		t1.setName("生产者1");
		t2.setName("消费者1");
		t3.setName("生产者2");
		
		t1.start();
		t2.start();
		t3.start();
	}
}

 

生产者和消费者问题有时候会出现连续生产 或者连续消费问题

/*
     *  连续生产: 
     *  1 2 3 4 
     *  flag = false; 
     *   1 --- 1  flag = true
     *   1 --- wait 
     *   2 ----wait
     *   3-----消费了1号  false  唤醒 1 ---->活
     *   3-----wait
     *   4-----wait 
     *   
     *   1-----> 生产商品: 2  true 。 2 ----> 活了: 
     *   1-----》 wait 
     *   2-----》  被唤醒后,没有判断标志位:  执行向下执行, 生产了3 号商品、。 
     *   出现了连续的生产: 
     *   
     *   
     *   
     */ 

*
 * 单生产和单消费的案例: 
 */



/*
 * 描述资源: 
 */
class Resource{
	
	private String name; //商品的名称: 
	private int count=1; // 标识商品的编号(只能自增)
	
	//定义一个标志位: 表示盘子当中是否有商品:
	boolean flag = false; 
	
	
	
	
	//提供生产的方法: 
	public  void set(String name){
		
		synchronized (this) {
			while(flag){
				try {
					this.wait();//生产者等待: 1 2 
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			
			this.name= name+ count; //生产出来的商品带有名称切带有编号;
			count++; //商品没生产一次,编号自增一次。 
			System.out.println(Thread.currentThread().getName()+"...生产了"+this.name);
		   
			// 该标志位: 
			flag= true; //有东西: 
//			this.notify();//唤醒: 
			this.notifyAll();
		}
	}
	
	//提供消费的方法: 
	public void out() {
		
		synchronized (this) {
			while(!flag){//指定了: 表明盘子当中没有东西, 消费者等待: 
				try {
					this.wait();// 2
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			//消费了产品: 
			System.out.println(Thread.currentThread().getName()+"......消费了...."+this.name);
		
			//改变标志位; 
			flag = false; //没商品:
			
			//唤醒生产者生产: 
//			this.notify();
			this.notifyAll();
		}
	}
	
}

//线程任务: 描述生产者的任务: 
class Productor implements Runnable{
  // 引入资源: 
	private Resource r; 
	//为了让消费者和生产者保证操作的对象唯一: 
	public Productor(Resource r){
		this.r= r; 
	}
	
	public void run() {
		while(true){
			r.set("面包");//循环生产:
		}
	}
	
}
//线程任务: 描述消费者: 
class Consumer implements Runnable{

//	 引入资源: 
	private Resource r; 
	//为了让消费者和生产者保证操作的对象唯一: 
	public Consumer(Resource r){
		this.r= r; 
	}
	
	public void run() {
		while(true){
			r.out();//循环消费
		}
	}
	
}

public class ThreadDemo04 {
	public static void main(String[] args) {
		//创建任务共享的资源:
		Resource r = new Resource();
				
		// 创建生产线程任务: 
		Productor pro = new Productor(r);
		//创建消费者的线程任务: 
		Consumer con = new Consumer(r);
		
		//创建线程对象: 
		Thread t1 = new Thread(pro);
		Thread t2 = new Thread(pro);
		Thread t3 = new Thread(con);
		Thread t4 = new Thread(con);
		
		//开启“: 
		t1.start();
		t2.start();
		t3.start();
		t4.start();
		
		/*   
		 *  1 2 3 4 
		 *   1: 
 		 * 
		 */
		
	}
}


  如果直接将if 换成while , 被唤醒的线程会重新判断标志位; 此时容易死锁: 
  使用Object当中提供的notifyAll方法,唤醒所有的等待线程。 此时问题解决: 

但是还是每次会有1/3的概率造成重复判断效率很低

 

2018年8月13日11:32:16 于易动

  如果真的喜欢 ,编程语言会有趣味的。一直感觉java 是有哲理的语言。

 

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

东方-教育技术博主

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值