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

----------------------- 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学习型技术博客、期待与您交流! ----------------------

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
黑马程序员多线程练习题主要包括两个问题。第一个问题是如何控制四个线程在打印log之前能够同时开始等待1秒钟。一种解决思路是在线程的run方法中调用parseLog方法,并使用Thread.sleep方法让线程等待1秒钟。另一种解决思路是使用线程池,将线程数量固定为4个,并将每个调用parseLog方法的语句封装为一个Runnable对象,然后提交到线程池中。这样可以实现一秒钟打印4行日志,4秒钟打印16条日志的需求。 第二个问题是如何修改代码,使得几个线程调用TestDo.doSome(key, value)方法时,如果传递进去的key相等(equals比较为true),则这几个线程应互斥排队输出结果。一种解决方法是使用synchronized关键字来实现线程的互斥排队输出。通过给TestDo.doSome方法添加synchronized关键字,可以确保同一时间只有一个线程能够执行该方法,从而实现线程的互斥输出。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [黑马程序员——多线程10:多线程相关练习](https://blog.csdn.net/axr1985lazy/article/details/48186039)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值