010 - 多线程交互

1 - 使用join线程间排队
  • Join () :

    public final void join() throws InterruptedException
    

    API中意思是,join()是等待线程结束

  • 示例代码

    class Sleeper extends Thread{
       public void run(){
         println("Sleeper线程id=" + Thread.currentThread().getId() +  "   start run");
         try{
           for(i=0 to 50){
              Thread.sleep(1000)
              println("线程" + Thread.currentThread().getId() + "--i = "+i);
           } 
         }catch(){
           
         }
         println("Sleeper线程id=" + Thread.currentThread().getId() +  "   end"
       }
    }
    class Joiner extends  Thread{
      
      Sleeper sleeper;
      
      public Joiner( Sleeper sleeper){
        this.sleeper = sleeper;
      }
      
       public void run(){
         println("Joiner线程id=" + Thread.currentThread().getId() +  "   start run");
         try{
           for(i=0 to 50){
              Thread.sleep(1000);
              println("线程" + Thread.currentThread().getId() + "--i = "+i);
              if(i == 5){
                sleeper.join();
              }
           } 
         }catch(){
           
         }
         println("Joiner线程id=" + Thread.currentThread().getId() +  "   end"
       }
    } 
    public class TestJoiner {
       main(){
        Sleeper sleeper = new Sleeper();
         sleeper.start();
         Joiner joiner = new Joiner(sleeper);
         joiner.start();
       }
    }             
    
  • 输出结果

    Join () 主要目的 : 使得线程之间执行存在先后顺序,看下图输出结果,sleeper.join()之后 线程11先于线程12执行

    Sleeper线程id=11   start run
    Joiner线程id=12   start run
    线程11---i=0
    线程12---i=0
    线程12---i=1
    线程11---i=1
    线程12---i=2
    线程11---i=2
    线程11---i=3
    线程12---i=3
    线程11---i=4
    线程12---i=4
    线程11---i=5
    线程12---i=5
    sleeper准备加入到当前线程线程12
    线程11---i=6
    线程11---i=7
    线程11---i=8
    线程11---i=9
    线程11---i=10
    .....
    线程11---i=49
    Sleeper线程id=11   start end
    线程12---i=6
    线程12---i=7
    线程12---i=8
    线程12---i=9
    线程12---i=10
    ...
    线程12---i=49
    Joiner线程id=12   start end
    
  • 如何进行交互

    在一个线程 b中调用 a线程的 join() , 结果会 阻塞b线程,先执行a线程,等a线程执行完成,在执行b线程。

    最终形成线程之间的执行顺序

2 - 使用wait()、notify() 进行线程间交互
  • API文档中wait()和notify() : Object对象中的方法

    //导致当前线程等到另一个线程调用该对象的notify()方法或notifyAll()方法
    public final void wait() throws InterruptedException
    //唤醒正在等待对象监视器的单个线程。 如果任何线程正在等待这个对象,其中一个被选择被唤醒。 选择是任意的
    public final void notify()
    //唤醒正在等待对象监视器的所有线程
    public final void notifyAll()
    

    注意 :

    ​ notify() 唤起的是监控对象的wait()线程中的其中一个线程 ,

    ​ 唤醒的线程将无法继续,直到当前线程放弃此对象上的锁定为止

  • 示例代码

    一个餐厅、餐厅有订单、有waiter 、有厨师 。 waiter在等待中,厨师一旦有订单,就通知waiter

    class Restaurant{
       Order order;
    }
    class Order{}
    class Waiter extends Thread{
       Restaurant restaurant;
       Waiter(Restaurant restaurant){this.restaurant = restaurant;}
       public void run(){
          while(restaurant.order == null){
             // 同步代码块 直接调用wait,相当于this.wait() , 唤醒时采用waiter.notify()
             synchronized(this){
                println("waiter线程ID"+Thread.currentThread().getId()+"-正在等待中...");
                try {
                      wait();
                      restaurant.order == null;
                      println("waiter线程ID"+Thread.currentThread().getId()+"-取餐");
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
             }
          }
       }
    }
    class Chef extends Thread{
       Restaurant restaurant;
       Waiter waiter;
       Chef(Restaurant restaurant,Waiter){
         this.restaurant = restaurant;
         this.waiter =waiter;
       }
       public void run(){
          while (true){
                if (restaurant.order == null){
                    restaurant.order = new Order();
                    println("厨师线程ID"+Thread.currentThread().getId()+"-接到新订单");
                    synchronized (waiter){
                        println("厨师线程ID"+Thread.currentThread().getId()+"-通知waiter");
                        waiter.notify();
                    }
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
       }
    }
    
  • 输出结果

    输出结果 : 可以看出

    waiter线程ID11-正在等待中...
    厨师线程ID12-接到新订单
    厨师线程ID12-通知waiter
    waiter线程ID11-取餐
    waiter线程ID11-正在等待中...
    厨师线程ID12-接到新订单
    厨师线程ID12-通知waiter
    waiter线程ID11-取餐
    waiter线程ID11-正在等待中...
    厨师线程ID12-接到新订单
    厨师线程ID12-通知waiter
    waiter线程ID11-取餐
    ....
    
  • 如何进行交互

    ​ 一个线程调用 监控对象. wait() 进入"等待阻塞”, 直到另一个线程调用此监控对象.notify() 唤醒

    一个线程执行有了结果,通知另一个线程可以执行了

    注意 :

    ​ wait() 和notify() : 需要加载同步代码中 (synchronized ) , 在上边例子中,是对waiter线程对象加锁,唤醒的时候,调用waiter.notify()即可·

3 - 使用 CountDownLatch进行线程交互
  • 使用场景

    在上边餐厅的例子中, waiter一直在等待,厨师完成一笔订单,就会提醒waiter ,采用的方式是 wait()

    但是设想一下下边 情形 :

    前边很多步骤,然后有一个线程必须等待其他线程执行完成,才会执行。 比如总装一辆车

    前边的步骤,生产发动机、生产轴承、车门、最后在总装汽车 :

    思路: 采用计时器的方案

  • 什么是countDownLatch

    允许一个或多个线程等待直到在其他线程中执行的一组操作完成的同步辅助,A CountDownLatch用给定的计数初始化。 await方法阻塞,直到由于调用countDown()方法,当前计数达到零,此后所有等待线程被释放,并且任何后续的调用await立即返回

  • API中常用方法

    1. await()

      导致当前线程等到锁存器计数到零,除非线程是interrupted , 如果计数器归0,此方法立刻返回; 如果当前技术器大于0,则当前线程将被禁用,并处于休眠状态。

      除非发生以下两种情况:

      调用了countDown()方法,计数器达到零

      其他线程interrupts当前线程

    2. countDown()

      减少锁存器的记数,如果计数递减到0,释放所有等待的线程 ,所有等待的线程都将被重新启用以进行线程调度; 如果当前计数大于0,则递减;如果当前计数已经是0,则无任何反应

  • 示例代码

    // 工作线程, 每完成一个, 就执行一次countDown() 
    class WorkingTask implements Runnable{
      private CountDownLatch my_cdl ; // 省略构造方法
      public void run(){
        try{
          int mills = (int)(Math.random() * 10 );
          Thread.sleep(mills * 1000);
          println("线程ID_"+Thread.currentThread().getId()+"正在工作");
          my_cdl.countDown();
    		}catch(Exception e){
          
        }
      }
    }
    class  WaitingTask implements Runnable{
      private CountDownLatch my_cdl ; // 省略构造方法
      public void run(){
        try{
     			my_cdl.await();
          println("等待线程ID_"+Thread.currentThread().getId()+"正在等待");
          Thread.sleep(1000);
           println("等待线程ID_"+Thread.currentThread().getId()+"等待完成,执行总装");
    		}catch(Exception e){
          
        }
      }
    }
    public class Test{
       public static final int size = 10;  // 计数的个数
      // 主函数简写为main
       main(){
         CountDownLatch cdl = new CountDownLatch(size);
         WaitingTask wat = new WaitingTask(cdl);
         new Thread(wat).start();
         for(int i = 0; i<size;i++){
           new Thread(new WorkingTask(cdl)).start();
         }
       }
    }
    
  • 输出结果

    • 当size 为0 时 输出
    等待线程ID_11正在等待其他线程
    等待线程ID_11汽车总装完成
    
    • 当size不为0,比如为10
    线程ID_21正在工作
    线程ID_16正在工作
    线程ID_17正在工作
    线程ID_13正在工作
    线程ID_20正在工作
    线程ID_12正在工作
    线程ID_18正在工作
    线程ID_19正在工作
    线程ID_14正在工作
    线程ID_15正在工作
    等待线程ID_11正在等待其他线程
    等待线程ID_11汽车总装完成
    
  • 如何交互

    等待线程调用 countDownLatch.await() 处于等待中,

    一直等到计数器计数的线程减到0 countDownLatch.countDown();

4 - 使用信号灯进行线程交互
  • 为什么引入信号量

    ​ 之前,处理竞争资源,可以选择synchronized, 但是 如果竞争资源不是一个,而是多个,比如下边场景,实验室共有五台实验机,现在A版有20个人进行实验,每人一次实验,无需排队

  • 什么是信号量 (API)

    一个计数信号量。 在概念上,信号量维持一组许可证。每个acquire()阻止许可证可用,然后取出。 每个release()都添加了一个许可证,潜在地释放一个阻塞获取方。

  • 示例代码

    引入信号量之后,五台实验机就是五个许可证 ,启动20个线程模拟20个学生

    public class Test {
        public static void main(String[] args) {
            Semaphore smp = new Semaphore(5); // 五个许可证模拟五台实验机
            //20个线程模拟20个学生
            for(int i = 0 ; i < 20 ; i++){
                Student s = new Student(i,smp);
                new Thread(s).start();
            }
        }
    }
    class Student implements  Runnable{
    
        private  int num;
        private  Semaphore m_smp;
    
        public Student(int num, Semaphore m_smp) {
            this.num = num;
            this.m_smp = m_smp;
        }
    
        @Override
        public void run() {
            try {
                m_smp.acquire(); // 排队获取许可证
                Thread.sleep(2000) ; // 模拟学生正在做实验
                System.out.println("线程"+Thread.currentThread().getId()+",学生"+num+"实验完成");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                m_smp.release();
            }
        }
    }
    
  • 输出结果

    线程13,学生2实验完成
    线程15,学生4实验完成
    线程12,学生1实验完成
    线程11,学生0实验完成
    线程14,学生3实验完成
    线程16,学生5实验完成
    线程17,学生6实验完成
    线程20,学生9实验完成
    线程19,学生8实验完成
    线程18,学生7实验完成
    线程21,学生10实验完成
    线程22,学生11实验完成
    线程25,学生14实验完成
    线程24,学生13实验完成
    线程23,学生12实验完成
    线程26,学生15实验完成
    线程27,学生16实验完成
    线程28,学生17实验完成
    线程29,学生18实验完成
    线程30,学生19实验完成
    
  • 如何进行交互

    先申请许可证,申请不成功,会进入阻塞状态 ; 直到获得许可,才继续向下进行。

    public void run(){
       try{
          smp.acquire();
       }catch(Exception e){
         
       }finally{
          smp.release();
       }
    }
    
5. 多线程交互可能出现死锁
  • 多线程编程中,出现死锁的话,很难排查问题,

  • 比如 ,常用的死锁的模拟,哲学家吃饭

    一群哲学家 围着一个圆桌吃饭, 筷子每个位置上只有一只, 每个哲学家先选择左边筷子,再选择右边筷子, 可能会出现死锁 。注意 是一个圆桌,最后一个人右手的筷子是第一个筷子

    public class DieLock {
    	public static void main(String[] args) {
    		Philosopher[] philosophers = new Philosopher[30];	//人越少越容易死锁	
    		ChopStick left,right,first;
    		left = new ChopStick();
    		right = new ChopStick();
    		first = left;		
    		for(int i=0;i<philosophers.length-1;i++){
    			philosophers[i] = new Philosopher(left,right);
    			left = right;                     				//吃饭时先取左边的筷子,再取右边的
    			right = new ChopStick();
    		}	
    		//最后一人的右手chopstick为first
    		philosophers[philosophers.length-1] = new Philosopher(left,first);  		//会死锁
    		//philosophers[i] = new Philosopher(first,left);							//不会死锁
    	}
    }
    // 筷子
    class ChopStick{
    	private static int counter = 0;
    	private int number = counter++;
    	public String toString(){	
    		return "ChopStick" + number;
    	}
    }
    // 哲学家
    class Philosopher extends  Thread{
        private static int counter = 0;
    	  private int number = counter++;
        private ChopStick leftChopstick;	
        private ChopStick rightChopstick;
        
       public Philosopher(ChopStick left,ChopStick right){
        	leftChopstick = left;
        	rightChopstick = right;
        	start();
       }
         public void think(){
            try {			
              sleep(33);                   			//***思考时间越长越不容易发生死锁************
            } catch (Exception e) {
              // TODO: handle exception
            }
        }
        public  void  eat(){
        	synchronized(leftChopstick){
        		System.out.println(leftChopstick + " waiting for  " + rightChopstick);
        		
        		synchronized(rightChopstick){
        			System.out.println(this + " ------now eating ");
        		}    		
        	}    	
        }
         public String toString(){
          return "Philosopher" + number;
        }
    
          public void run(){
            while(true){
              think();
              eat();
            }
          }
    }
    
  • 输出结果

    for  ChopStick4
    Philosopher3 ------now eating 
    ChopStick2 waiting for  ChopStick3
    ChopStick17 waiting for  ChopStick18
    ChopStick18 waiting for  ChopStick19
    ChopStick16 waiting for  ChopStick17
    ChopStick19 waiting for  ChopStick20
    ChopStick1 waiting for  ChopStick2
    ChopStick20 waiting for  ChopStick21
    ChopStick15 waiting for  ChopStick16
    Philosopher2 ------now eating 
    Philosopher20 ------now eating 
    .....
    
  • 出现死锁的条件 (缺一不可)

    1. 互斥条件 : 线程使用的资源中至少有一个不能共享,如一根筷子一次只能被一人使用
    2. 线程持有资源不放并且在等待另一个资源 : 拿着筷子等待另一只筷子
    3. 资源不能被抢张,不能抢占别人的筷子
    4. 循环等待资源,一直不退出
  • 解决方式

    只要上边条件 ,有一个不满足,就不会出现死锁,比如 不让线程一直在等待资源,

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值