zoukankan      html  css  js  c++  java
  • java多线程为什么要用while而不是if

    对于java多线程的wait()方法,我们在jdk1.6的说明文档里可以看到这样一段话

    从上面的截图,我们可以看出,在使用wait方法时,需要使用while循环来判断条件十分满足,而不是if,那么我们思考以下,如果使用if会怎么样?

    为方便讲解,我们来看一个被广泛使用的生产消费的例子。代码部分参考  郝斌java视频教程  部分改编。

    /*
    	生产和消费
    */
    package multiThread;
    
    class SynStack 
    {
    	private char[] data = new char[6];
    	private int cnt = 0; //表示数组有效元素的个数
    	
    	public synchronized void push(char ch)
    	{
    		if (cnt >= data.length)
    		{
    			try
    			{
    				System.out.println("生产线程"+Thread.currentThread().getName()+"准备休眠");
    				this.wait();
    				System.out.println("生产线程"+Thread.currentThread().getName()+"休眠结束了");
    			}
    			catch (Exception e)
    			{
    				e.printStackTrace();
    			}
    		}
    		this.notify(); 
    		data[cnt] = ch;
    		++cnt;
    		System.out.printf("生产线程"+Thread.currentThread().getName()+"正在生产第%d个产品,该产品是: %c
    ", cnt, ch);
    	}
    	
    	public synchronized char pop()
    	{
    		char ch;
    		if (cnt <= 0)
    		{
    			try
    			{
    				System.out.println("消费线程"+Thread.currentThread().getName()+"准备休眠");
    				this.wait();
    				System.out.println("消费线程"+Thread.currentThread().getName()+"休眠结束了");
    			}
    			catch (Exception e)
    			{
    				e.printStackTrace();
    			}
    		}
    		this.notify();
    		ch = data[cnt-1];
    		System.out.printf("消费线程"+Thread.currentThread().getName()+"正在消费第%d个产品,该产品是: %c
    ", cnt, ch);
    		--cnt;
    		return ch;		
    	}	
    }
    
    class Producer implements Runnable
    {
    	private SynStack ss = null;
    	public Producer(SynStack ss)
    	{
    		this.ss = ss;
    	}
    	
    	public void run()
    	{
    		char ch;
    		for (int i=0; i<10; ++i)
    		{
    //			try{
    //			Thread.sleep(100);
    //			}
    //			catch (Exception e){			
    //			}
    				
    			ch = (char)('a'+i);
    			ss.push(ch);
    		}
    	}
    }
    
    class Consumer implements Runnable
    {
    	private SynStack ss = null;
    	
    	public Consumer(SynStack ss)
    	{
    		this.ss = ss;
    	}
    	
    	public void run()
    	{
    		for (int i=0; i<10; ++i)
    		{
    			/*try{
    			Thread.sleep(100);
    			}
    			catch (Exception e){			
    			}*/
    			
    			//System.out.printf("%c
    ", ss.pop());
    			ss.pop();
    		}
    	}
    }
    
    
    public class TestPC2
    {
    	public static void main(String[] args)
    	{
    		SynStack ss = new SynStack();
    		Producer p = new Producer(ss);
    		Consumer c = new Consumer(ss);
    		
    		
    		Thread t1 = new Thread(p);
    		t1.setName("1号");
    		t1.start();
    		/*Thread t2 = new Thread(p);
    		t2.setName("2号");
    		t2.start();*/
    				
    		Thread t6 = new Thread(c);
    		t6.setName("6号");
    		t6.start();
    		/*Thread t7 = new Thread(c);
    		t7.setName("7号");
    		t7.start();*/
    	}
    }
    

      上面的代码只有一个消费者线程和一个生产者线程,程序运行完美,没有任何错误,那为为什么jdk里面强调要用while呢?

    这个问题,我之前也向了很久,同事提到了一点,这个程序如果用到多个生产者和消费者的情况,就会出错,我试了一下,确实会出错。但是我不能明白为什么就会出错。

    不是有synchronized关键字加锁了吗?

    其实,这里也错在我对wait方法原理的实际运行效果不是很了解,当我在wait方法的前后都加上输出提示语句后,我明白了。

    一个线程执行了wait方法以后,它不会再继续执行了,直到被notify唤醒。

    那么唤醒以后从何处开始执行?

    这是解决这里出错原因的关键。

    我们尝试修改代码,实现一个生产线程,两个消费线程。

    /*
        生产和消费
    */
    package multiThread;
    
    class SynStack 
    {
        private char[] data = new char[6];
        private int cnt = 0; //表示数组有效元素的个数
        
        public synchronized void push(char ch)
        {
            if (cnt >= data.length)
            {
                try
                {
                    System.out.println("生产线程"+Thread.currentThread().getName()+"准备休眠");
                    this.wait();
                    System.out.println("生产线程"+Thread.currentThread().getName()+"休眠结束了");
                }
                catch (Exception e)
                {
                    e.printStackTrace();
                }
            }
            this.notify(); 
            data[cnt] = ch;
            ++cnt;
            System.out.printf("生产线程"+Thread.currentThread().getName()+"正在生产第%d个产品,该产品是: %c
    ", cnt, ch);
        }
        
        public synchronized char pop()
        {
            char ch;
            if (cnt <= 0)
            {
                try
                {
                    System.out.println("消费线程"+Thread.currentThread().getName()+"准备休眠");
                    this.wait();
                    System.out.println("消费线程"+Thread.currentThread().getName()+"休眠结束了");
                }
                catch (Exception e)
                {
                    e.printStackTrace();
                }
            }
            this.notify();
            ch = data[cnt-1];
            System.out.printf("消费线程"+Thread.currentThread().getName()+"正在消费第%d个产品,该产品是: %c
    ", cnt, ch);
            --cnt;
            return ch;        
        }    
    }
    
    class Producer implements Runnable
    {
        private SynStack ss = null;
        public Producer(SynStack ss)
        {
            this.ss = ss;
        }
        
        public void run()
        {
            char ch;
            for (int i=0; i<10; ++i)
            {
    //            try{
    //            Thread.sleep(100);
    //            }
    //            catch (Exception e){            
    //            }
                    
                ch = (char)('a'+i);
                ss.push(ch);
            }
        }
    }
    
    class Consumer implements Runnable
    {
        private SynStack ss = null;
        
        public Consumer(SynStack ss)
        {
            this.ss = ss;
        }
        
        public void run()
        {
            for (int i=0; i<10; ++i)
            {
                /*try{
                Thread.sleep(100);
                }
                catch (Exception e){            
                }*/
                
                //System.out.printf("%c
    ", ss.pop());
                ss.pop();
            }
        }
    }
    
    
    public class TestPC2
    {
        public static void main(String[] args)
        {
            SynStack ss = new SynStack();
            Producer p = new Producer(ss);
            Consumer c = new Consumer(ss);
            
            
            Thread t1 = new Thread(p);
            t1.setName("1号");
            t1.start();
            /*Thread t2 = new Thread(p);
            t2.setName("2号");
            t2.start();*/
                    
            Thread t6 = new Thread(c);
            t6.setName("6号");
            t6.start();
            Thread t7 = new Thread(c);
            t7.setName("7号");
            t7.start();
        }
    }

    上面代码就是在main函数里增加了一个消费线程。

    然后错误出现了。

    数组越界,为什么会这样?

    问题的关键就在于7号消费线程唤醒了6号消费线程,而6号消费线程被唤醒以后,它从哪里开始执行是关键!!!!

    它会执行

    System.out.println("消费线程"+Thread.currentThread().getName()+"休眠结束了");

    这行代码。

    不是从pop()方法的开始处执行。

    那么这跟使用if方法有什么关系?

    因为,7号线程唤醒了6号线程,并执行了以下4行代码。

    		ch = data[cnt-1];
    		System.out.printf("消费线程"+Thread.currentThread().getName()+"正在消费第%d个产品,该产品是: %c
    ", cnt, ch);
    		--cnt;
    		return ch;	
    

    7号线程执行完上面的代码后,cnt就=0了

      又因为6号线程被唤醒时已经处在if方法体内,它不会再去执行if条件判断,所以就顺序往下执行,这个时候执行

    ch = data[cnt-1];
    就会出现越界异常。
    假如使用while就不会,因为当唤醒了6号线程以后,它依然会去执行循环条件检测。所以不可能执行下去,保证了程序的安全。


  • 相关阅读:
    《走近心理学》第一章之行为和心理学
    《解忧杂货铺》读书笔记
    追求得到之日即其终止之时, 寻觅的过程亦即失去的过程。——村上
    简朴的生活、高贵的灵魂是人生的至高境界。——杨绛
    Laravel Seeder
    Git的使用 checkout push merge
    基于 GraphQL 构建 Laravel API —— 基本使用篇
    awk基础04-内置函数
    awk基础03-分支和循环语句
    awk基础02-变量-分隔符-数组
  • 原文地址:https://www.cnblogs.com/audi-car/p/6063567.html
Copyright © 2011-2022 走看看