黑马程序员--深入探究多线程

来源:互联网 发布:php echo输出中文乱码 编辑:程序博客网 时间:2024/06/08 12:23

----------------------- android培训java培训java学习型技术博客、期待与您交流! ----------------------

深入探究--多线程

(一) 线程间通信:多个线程在处理同一资源,但是任务却不同。

1,  出现输出一片一片的情况:

当有线程InputOutputResource中输入输出资源的时候,如果Input抢到执行权,它不会只输入一次,会一直往里输入,这个时候就会将之前输入的数据覆盖(我们在输入与输出的代码区间中加入了同步),而当输出数据的时候,它也同样不会只输出一次,所以,这个时候就会产生输出一片一片的情况。

2,  等待唤醒机制:

涉及到的方法:

1),wait():让线程处于冻结状态,被wait的线程会被存储到线程池中

2),notify():唤醒线程池中一个线程(任意)

3),notifyAll():唤醒线程池中所有的线程

这些方法都必须定在同步中,因为这些方法是用于操作线程状态的方法,

必须要明确到底操作的是哪个锁上的线程。

(二)经典案例:多生产者与多消费者

初次代码容易出现的问题归纳:

1)出现大片输出的情况(输入也有可能,不过被覆盖,你是看不出来的)

     当出现多个生产者与消费者的同时,此时,使用if条件判断标记,notify方法唤醒线程池中的线程容易出现大片输出(即消费者使劲消费,但是没有生产)的情况。

        分析原因:1if条件只判断一次。当线程池中有三个线程在等待的时候,此时,本方法中notify方法唤醒的线程有可能是本方法中的线程,此时,就会不停的出现消费或者生产。

2)死锁:

       当我们把判断条件if改成while的时候(while多次循环),多次判断标记。notify方法只能唤醒线程中任意一个线程,如上述情况描述相似,唤醒本方法中的线程,当唤醒它的线程在wait的时候,此时,它醒来判断条件不符合,也挂在那了。这个时候,所有的线程都在wait,就会出现死锁情况。

3nofifyAll方法

        nofifyAll方法唤醒线程池中所有的方法,当生产者生产完以后,唤醒线程池中所有的线程,如果本方法线程执行,但是需要多次判断条件不符合,则由对方线程开始执行,这就解决了多生产者与多消费者一次生产一次消费的这种情况。

4)以下是修改过后的部分参考代码:

classResource{

       private String name;

       private int count=0;

       private boolean flag=false;

       public synchronized void set(Stringname){

              while(flag){//每次醒的线程都需要多标记是否成立(如果使用的是if,只判断一次,当线程wait的时候,就不会再次判断flag条件)

                     try {this.wait();}catch(InterruptedException e) {e.printStackTrace();}

              this.name=name+count++;

              System.out.println(Thread.currentThread().getName()+"..生产者.."+this.name);

              flag=true;

              notifyAll();//唤醒所有线程池中的线程(本方线程被唤醒需要判断标记,对象线程被唤醒则执行)。

       }

}

       public synchronized void out(){

              while(!flag){//注意下面的格式在开发中不允许,但是为了方便练习,暂且这样

                     try {this.wait();}catch(InterruptedException e) {e.printStackTrace();}

              }

              System.out.println(Thread.currentThread().getName()+"....消费者...."+this.name);

              flag=false;

              notifyAll();

       }

}

总结归纳:

        if判断标记只有一次,会导致不该运行的线程运行了,出现数据错误。

while判断标记:解决了线程获取执行权之后,是否要运行。

notify:只能唤醒一个线程,如本方唤醒本方就没有意义。而且while判断标记加上notify会导致死锁

notifyAll:解决了本方线程一定会唤醒对方线程的问题

后期在开发中,一般对于多生产多消费的这种情况,都是使用while判断标记,notifyAll方法唤醒所有线程。

提醒:notifyAll虽然解决了该类的问题,但是,它在唤醒对方的线程的同时也唤醒了本方法中的线程,这样就导致了效率的低下。在jdk1.5升级以后,对该类问题有了新的改善就是通过将同步和锁封装成了对象,并将操作锁的隐式方式定义到了该对象中,将隐式动作变成了显示动作。如:

Lock lock=new ReentrantLock();

void show(){

     try{

            lock.lock();//获取锁

            code...throwException();

     }

     finally{

            lock.unlock();//释放锁

     }

}

当需要执行的代码中出现异常的时候,此时,必须使用finally将锁进行释放。

      好处:以前只有一组监视器,既要监视生产者又要监视消费者,这就意味着这组监视器既能将生产者与消费者全都wait也能将它们全部唤醒(如果用notify,那么就不能保证是哪个线程会被唤醒)。但是现在:我们的线程有分类,一组负责生产者,一组负责消费者,我们希望的是生产者能唤醒消费者,消费者能唤醒生产者,那么这个时候就需要两个监视器,一组负责生产者,一组负责消费者,不就ok啦。如:

classResource{

       private String name;

       private int count=0;

       private boolean flag=false;

       //创建一个锁

       Lock lock=new ReentrantLock();

       //通过已有的锁获取该锁上的监视器对象

//     Condition con=lock.newCondition();

      

//     通过已有的锁获取两组监视器,一组监视生产者,另一组监视消费者

       Conditionproducer_con=lock.newCondition();

       Condition consumer_con=lock.newCondition();

       public void set(String name){

              lock.lock();

              try{

                     while(flag){

                            try{producer_con.await();}catch (InterruptedException e) {e.printStackTrace();}

                     }

                     this.name=name+count++;

                     System.out.println(Thread.currentThread().getName()+"..生产者.."+this.name);

                     flag=true;

                     consumer_con.signal();//一次唤醒对方一个线程

              }

              finally{

                     lock.unlock();

              }

       }

       public void out(){

              lock.lock();

              try{

                     while(!flag){

                            try{consumer_con.await();}catch (InterruptedException e) {e.printStackTrace();}

                     }

                     System.out.println(Thread.currentThread().getName()+"....消费者...."+this.name);

                     flag=false;

                     producer_con.signal();

              }

              finally{

                     lock.unlock();

              }

       }

}

总结:

Lock接口:它的出现替代了同步代码块或者同步函数。将同步的隐式锁操作变成了显示锁操作。同时更为灵活,可以一个锁上加上多组监视器

lock():获取锁

unlock():释放锁,通常需要定义在finally代码块中

Condition接口:它的出现替代了Object中的wait notify notifyAll方法。将这些监视器方法进行了单独的封装,变成Condition监视器对象,可以任意锁进行组合。

Condition接口中的方法:await();  signal();  signalAll();

 

(三)注意比较wait() sleep()

wait():释放CPU执行权,释放锁。

sleep():释放CPU执行权,不释放锁。

颠覆你的世界观同步代码块

面试题:

(1)       t0 t1  t2这三个线程能同时出现在同步代码块中吗?

(2)       假设线程t4唤醒了线程t0,那么t0线程会执行吗?

classDemo{

       void show(){  

              synchronized(this){

                     wait();//t0  t1 t2 

              }

       }

       void method(){

              synchronized(this){

                     //wait();

                     notifyAll();//t4进来唤醒了t0  t1线程,但是在同步里面某一时刻只能有一个线程

              }

       }

}

此时,线程t0  t1  t2都醒了,如果cpu切换到t0,但是t0也不一定运行。理由:在同步中,想要被运行必须要满足条件:持有锁。t4虽然唤醒了其它三个线程,但是t4还没有出去,仍然持有锁。

 

 

----------------------- android培训java培训java学习型技术博客、期待与您交流! ----------------------

 

原创粉丝点击