Java 多线程学习总结4

 

  线程间的通信
    如果线程只是傻傻地一个劲的“排他的”前行,那必然是愚蠢的。线程间需要通信,他们需协作才完成某项任务。线程间通常都是各顾各地前行,当然他们有公共资源的时候,需要调成按一定秩序执行。打个比方:福特汽车的流水线,for example,有一个流程是这样的:做好底盘的工人把底盘放在传送带上,传给上轮胎的工人。两个部分的工人只是埋头苦干,大部分时间不需要协调。如果做底盘的工人速度慢了,那么上轮胎的工人就闲着了,要等。如果上轮胎的工人很慢,做底盘的可以喝口水,抽口烟了,呵呵。这和线程很像:只关心自己的事情,但是在"边缘"的地方,双方需要协调。这就需要线程能有"停止"和"再启动"的能力。对应的方法是:wait(),notify();其中wait()是释放锁资源。 锁资源相当于CPU的时间片资源,需要注意的是它只能用在synchronized(锁)起来的程序片段中,不可以乱放。notify是通知那些在等的线程,然后在这些等的线程中,产生一个幸运儿,占有这个锁。许多书说notifyAll()好,我们就直接用notifyAll得了,反正功能差不多,偶也说不清,就不说了,呵呵。还有一点,wait能释放锁资源,sleep可不行哦,面试经常要问的哦。
我们还是举例子来说吧:经典的生产者和消费者:
  原始问题描述:有一个大篮子,姑娘不停地在玉米地摘玉米棒子,丢进大篮子里面。小伙子不停地从篮子里面拿玉米,扔到火锅里煮。
  问题升级1.姑娘不止一个,小伙子也不止一个。
  问题升级2.说不定有n个大妈从火锅里里面捞玉米,准备包装上市。
  如果用顺序编程的想法考虑问题就是:玉米被姑娘摘下,放到篮子里面,玉米被小伙拿出篮子,玉米被小伙放进锅里面,玉米被大娘拿出,包装。呵呵,很冗长,不是吗?
  如果一开始篮子里面就有100个棒子,姑娘累了,想歇歇腰,小伙子照样有活干。小伙子想上茅房,大篮子还很空,姑娘 照样可以一个人单干。这是另一种劳动模式了,极大地解放了社会主义的生产力,值得提倡,呵呵。
  那么,我们考虑原始问题的解答:
  第一步考虑到是公共资源,是什么呢?篮子。
  第二步"临界点":篮子满了,姑娘不能放玉米了,要等小伙子;小伙子不能从空篮子里面拿玉米,小伙子要等。
  小伙子如果从满篮子里面拿了一个玉米,要通知姑娘,你该干活了。同理,姑娘从空篮子里面放一个玉米要通知小伙 开工。
  第三步:并发问题的真正瓶颈,搞清楚,哪里该并发,哪里不该。

 问题如图示:

  伪代码:
  Java代码
        vector //模拟篮子
        对小伙子来说:
        public synchronized YuMi getYuMi()
        {   YuMi yumi=new Yumi();
            if(vector.size()==0)
            {
               wait();
               //等待完毕,请立刻取玉米,不妨取最后一个。
               yumi=vector.last();
           
            }
            //如果篮子不为空,尽情取
           else
           {   yumi=vector.last();
               //如果是从满篮子中取的,please notify姑娘
               if(vector.size==vector.MAX-1)
               notifyAll(); 
               }       
        }  

同理,也能很快得到姑娘放玉米的逻辑:省略
        注意:也有人喜欢这样写,用while做判断,这样是简洁许多,但是逻辑不够清晰:

  Java代码

    public synchronized YuMi getYuMi()
        {   YuMi yumi=new Yumi();
            while(vector.size()==0)
            {
               wait();
               //等待完毕,请立刻取玉米,不妨取最后一个。
            }
            //如果篮子不为空,尽情取
             yumi=vector.last();
               //如果是从满篮子中取的,please notify姑娘
              if(vector.size==vector.MAX-1)
              notifyAll(); 
                    
        }   

生产者和消费者模型的代码如下:

公共资源类:

package producer_and_consumer;

import java.util.Vector;

public class Basket {
    private Vector vector=new Vector();
    private int Max=20;
   
    public synchronized String getYuMi()
    {
        String yumi=new String();
        while(vector.size()==0)
        {
            try {
                wait();
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        yumi=(String) vector.elementAt(vector.size()-1);
        vector.remove(vector.size()-1);
        System.out.println(Thread.currentThread()+" "+"Get   "+ yumi);
        if(vector.size()==(Max-1))
        notifyAll();
           
        return yumi;
    }
   
    public synchronized void putYuMi(String str)
    {
        System.out.println(Thread.currentThread()+"  "+"Put  "+ str);
        while(vector.size()==Max)
        {
            try {
                wait();
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        vector.add(vector.size(),str);
        if(vector.size()==1)
        notifyAll();
           
       
    }

}

生产者线程:

java代码:

package producer_and_consumer;

public class Producer extends Thread{
    private Basket basket;
    private String prefix="YuMi";
    private String yumi;
    private int i=0;
   
    public Producer(Basket basket)
    {
        this.basket=basket;
    }
   
    public void run()
    {
        for(int j=0;j<100;j++)
        {  i++;
           yumi=prefix+i;
           try {
            Thread.sleep((int) ((long)1000* Math.random()));
            //为了模拟生产者隔一段时间生产一个,同理也可以在消费线程中
            //加上这个随机模拟,可以观察输出。
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
           basket.putYuMi(yumi);
        }
    }

}

消费者线程:

package producer_and_consumer;

public class Consumer extends Thread{
    private Basket basket;
   
   
    public Consumer(Basket basket)
    {
        this.basket=basket;
    }
    public void run()
    {   while(true)
    { 
        basket.getYuMi();
    }
    }
       

}

程序入口:

package producer_and_consumer;

public class main {
    public static void main(String args[])
    {
        Basket ball= new Basket();
        Thread producer=new Producer(ball);
        Thread consumer=new Consumer(ball);
        producer.start();
        consumer.start();
    }

}

输入在自己机器上看,因为太随机了!

在继续下面的时候,我们得讨论一下锁的对象和锁的大小即锁的粒度。

      class Object1
      {
       private Object2 obj2;
       public Object3 obj3=new Object3();
      
       public synchronize void function1()
       {
      
       }
      
       public void function2()
       {
           synchronized(obj3)
           {
          
          
           }
      
       }  
      } 
     

 

对象Object1中包含了对象Object2和Object3,function1()加锁的粒度是object1,
      function2()加锁的粒度是obj3,可以说,obj3要小一些。当然,不意味着obj1加锁,就
      给obj2,obj3加了锁,虽然类的组织是个包含关系,可是,锁是加在对象或者类上的,(加在类上的
      是静态方法,这里不予讨论,因为我自己都没研究过,呵呵。)

 

 

    为什么要这样分呢,我们下面讨论。
      从上面的那个生产者消费者可以看出锁是加在整个vector上的,所以put和get方法,一次只能进一个。所以
      有同志觉得这不行,效率不高.如果我们现在考虑现在有n个生产者,n个消费者,我们希望把锁的粒度降低为
      vector中的一个个节点,各个结点颜色不同,不是可以并发,效率更好了吗?猛然一想,这样觉得确实挺酷。
      但是我觉得很难:线程都是埋头苦干型的,他最得意的时候就是不停地做,而不要关心其他线程在干什么,但是
      有时候不得不考虑:我们假设有一个容器,放东西的线程在放的时候,需要不需要考虑其他线程。当然需要,我们
      定位的时候,当然需要知道哪些位置是放过东西的,那些是没放过的,这是有先后因果关系的,所以这些线程必须是顺 序执行!,必须一个一个进,不能同时涌进来。当然,这是我个人看法,因为我没有找到,让多个让多个物体并发放入容器的方法。证明过程如下: 如果一个线程要把东西塞进容器,它需要知道容器的size,容器知道size,需要检索每一个节点,每一个精确的检索,不能让其他线程破坏,所以证明没有同时多个线程往容器中放东西的方法。当然也不是不可能,呵呵,如果换了一种数据结构,或者用hash技术,或者其他好的数据结构,希望有人能告诉我,呵呵。 至于图9提出的想法,只能多个1对1的生产者消费者模型的n倍翻版,因为任何一个节点满了,都会导致放的线程 停止,而不会让他自己去寻找其他的空白节点,呵呵。所以我们要讨论一下,我们要在何时并发,哪里能并发。

 

 

真正的瓶颈在哪里?
      还是拿上面姑娘摘玉米,小伙子煮玉米,大娘搞包装来说,姑娘们把玉米放在篮子很简单,小伙子从篮子拿出玉米也简单,呵呵,同理从火锅里拿放玉米也简单,正在花费时间的是姑娘摘玉米,小伙子剥玉米皮,洗玉米,大娘搞包装袋 这些花费的时间是拿、取的好几个数量级,不是吗,这些人可以并发吗?当然,一个小伙子拿起一个自己的玉米,我想,他自己干自己的,就行了,根本就不要和其他小伙子协作。所以这里是真正要并发的地方。

 

  还有最后一个加强问题,如果大妈后面还有许多人,那怎么搞?一直用线程吗?我的建议是没必要搞那么多线程,可以把一些步骤合成一个顺序执行,只在关键点,搞成并发的线程。不要为了多线程而线程。

 

 所以,有了多线程,我们考虑问题的模式就变了:如果你一上来,就把一个"实体"的方法写一个synchronizd, 然后口口声声地说,这个是线程安全的。你一定是绵羊座,O型血的人,您的世界观一定很狭窄。真的猛士是不惧怕并发的。 我们应该以一种完全并发的角度来考虑问题,因为并发太可爱了,他高效,直白。然后再考虑竞争条件,哪里不能并发,并发有没有意义。其中竞争条件是公共资源,并发的意义是搞清楚真正耗时的步骤。就像我一开始就没搞清楚,从数据库load数据到内存是个查询的瓶颈一样,所以搞清真正的瓶颈很关键。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章