• 线程间的通信


    一,概述。 

      1.什么叫做线程间通信: 在1个进程中,线程往往不是孤立存在的,线程之间需要一些协调通信,来共同完成一件任务。也就是通过一定的方法来实现线程间的“交流”。

      2.线程间通信的体现:
        - 1个线程传递数据给另1个线程
        - 在1个线程中执行完特定任务后,转到另1个线程继续执行任务
    二,线程通信的方法。
      Object类中相关的方法有两个notify方法和三个wait方法:notify() / notifyAll() / wait()

      因为wait和notify方法定义在Object类中,因此会被所有的类所继承。这些方法都是final的,即它们都是不能被重写的,不能通过子类覆写去改变它们的行为。

      1.wait()方法。

      语法:锁对象.wait()

      特点:wait()方法的调用使得当前线程必须要等待,直到另外一个线程调用notify()或者notifyAll()方法(注意必须调用notify/notifyAll方法才能唤醒)。

            wait()方法的调用必须在同步的前提下。(因为该方法是要用锁对象调用,而只有在同步的情况下才有锁)

            wait()方法的调用会导致锁的释放。

      线程调用wait()方法,释放它对锁的拥有权,然后等待另外的线程来通知它(通知的方式是notify()或者notifyAll()方法),这样它才能重新获得锁的拥有权和恢复执行。要确保调用wait()方法的时候拥有锁,即,wait()方法的调用必须放在synchronized方法或synchronized块中。

      一个小比较:

      当线程调用了wait()方法时,它会释放掉对象的锁。

      另一个会导致线程暂停的方法:Thread.sleep(),它会导致线程睡眠指定的毫秒数,但线程在睡眠的过程中是不会释放掉对象的锁的

      2.notify和notifyAll()
      语法:锁对象.notify()/notifyAll()
      特点:notify()方法的调用可以唤醒当前锁对象下等待的另一个单个线程。优先级较高的优先唤醒,如果优先级一样,则随机唤醒。notifyAll()方法的调用可以唤醒当前锁对象下等待的所有线程。
         notify()和notifyAll()方法必须在同步的前提下使用(也就是notify方法调用必须放在synchronized方法或synchronized块中)。
         被唤醒的线程是不能被执行的,需要等到当前线程放弃这个对象的锁,也就是想要唤醒其他线程,说光调用notify()不行,还必须要调用wait()方法让当前线程释放锁对象,当另外一个线程获得了锁对象即可以执行。
      注意:wait() / notify() / notifyAll()方法使用的锁对象必须和同步代码块中的锁对象是同一个,否则报异常!!!
     
     【代码演示】:线程间通信。
     1 class Bank1 {//公共资源
     2     int total=100;
     3 }
     4 class PresonA implements Runnable{
     5     Bank1  b;
     6     //因为要确保两个用户使用的是同一家银行,所以在主函数中new一个银行,然后通过构造函数将这一银行传递给这两个用户
     7     public PresonA(Bank1 b){
     8         this.b=b;//将银行的引用传给两个用户
     9     }
    10     @Override
    11     public void run() {
    12         while (true){
    13             synchronized (b){//同步代码块,因为要确保两个用户使用的是同一个锁,所以选择银行对象作为锁
    14                 if(b.total>=0){//打钱,当卡里钱大于0,就唤醒另一个线程来取钱
    15                     b.notify();//唤醒另一个线程来取钱,注意该方法是要用锁对象调用的
    16                     try {
    17                         b.wait();//接着将自己睡眠,然后把锁释放给两一个线程
    18                     } catch (InterruptedException e) {
    19                         e.printStackTrace();
    20                     }
    21                 }
    22                 b.total=b.total+100;//A先存了100
    23                 System.out.println("A存了100,目前存款为:"+b.total);
    24             }
    25         }
    26     }
    27 }
    28 class PresonB implements Runnable{
    29     Bank1  b;
    30     public PresonB(Bank1 b){
    31         this.b=b;
    32     }
    33     @Override
    34     public void run() {
    35         while (true){
    36             synchronized (b){
    37                 if(b.total<=0){//取钱,当卡里钱小于0,就唤醒另一个线程来存钱
    38                     b.notify();//唤醒另一个线程
    39                     try {
    40                         b.wait();//将自己睡眠
    41                     } catch (InterruptedException e) {
    42                         e.printStackTrace();
    43                     }
    44                 }
    45                 b.total=b.total-100;
    46                 System.out.println("****B取了100,目前存款为:"+b.total);
    47             }
    48         }
    49     }
    50 }
    51 public class CommunicationDemo {
    52     public static void main(String[] args) {
    53         Bank1 b=new Bank1();//作为公共资源传递给两个用户
    54         new Thread(new PresonA(b)).start();
    55         new Thread(new PresonB(b)).start();
    56     }
    57 }

     运行结果:

      【代码演示】:生产者与消费者 

     1 class Resource{
     2     int count=0;//商品库存数
     3 }
     4 class Producer implements Runnable{
     5     Resource r;
     6     public Producer(Resource r){
     7         this.r=r;
     8     }
     9     public void run(){
    10         while (true){
    11             synchronized (r){
    12                 if(r.count>0){
    13                     try {
    14                         r.wait();
    15                     } catch (InterruptedException e) {
    16                         e.printStackTrace();
    17                     }
    18                 }
    19                 System.out.println("生产了一件商品,此时商品数为:"+(++r.count));
    20                 r.notify();
    21             }
    22         }
    23     }
    24 }
    25 class Consumer implements Runnable{
    26     Resource r;
    27     public Consumer(Resource r){
    28         this.r=r;
    29     }
    30     public void run(){
    31         while (true){
    32             synchronized (r){
    33                 if(r.count<=0){
    34                     try {
    35                         r.wait();
    36                     } catch (InterruptedException e) {
    37                         e.printStackTrace();
    38                     }
    39                 }
    40                 System.out.println("消费了一件商品,此时商品数为:"+(--r.count));
    41                 r.notify();
    42             }
    43         }
    44     }
    45 }
    46 public class ProducerConsumerDemo {
    47     public static void main(String[] args) {
    48         Resource r=new Resource();
    49         new Thread(new Producer(r)).start();
    50         new Thread(new Consumer(r)).start();
    51     }
    52 }

    运行结果:

       这是在只有两个线程在运行时的结果。如果有四个线程在同时运行呢?(两个生产者两个个消费者)

       会发现运行结果是混乱的,为什么这样??我们先来分析一下这个运行的过程,假设是0号线程(生产者)先获得了CPU的执行权,于是它生产了一个商品,然后接着调用了notify()方法来唤醒其他线程,假设这次随机唤醒的是1号线程(生产者),虽然1号线程这时被唤醒了,但是由于没有CPU 的执行权,1号线程还在运行不了,在进行第二次while循环时,由于判断资源数是大于0的,所以调用了wait()方法,此时0号线程进行等待,并释放了CPU 的执行权,由于被唤醒的只有1号线程,所以1号线程获得了CPU的执行权开始运行,当进入while循环后,判断资源数发现是大于0的,所以1号线程也被挂起进行等代,此时只剩下2号线程和3号线程(消费者),假设2线程先获得CPU执行权,进行判断满足条件,于是就消费了一个商品,然后调用了notify()方法,因为0号线程先进入线程池,所以此时先唤醒的是0号线程,在2号线程在进行第二次while循环时,由于不满足条件,所以调用了wait()方法,此时2号线程进行等待,由于0号线程已被唤醒,所以0号线程接着被锁之前的代码接着运行,生产了一个商品,然后唤醒了1号线程,当0号线释放掉CPU后,1号线程也接着被锁之前的代码进行运算,没有进行判断就直接生产了一个商品。

      由于线程在被唤醒后是接着被冻结之前的代码接着运行的,所以唤醒之后不会进行判断,就直接生产或消费了。所以就会出现问题。

      解决的方法就是:就是让各个线程在被唤醒之后仍然进行条件判断,所以将if改为while,同时将notify()改为notifyAll(),否则会发生死锁情况。

      定义while判断标记:让被唤醒的线程再一次判断标记。

      定义notifyAll():因为需要唤醒对方线程。如果只要notify,容易出现只唤醒本方线程的情况,导致程序中的所有都等待。

    改过之后运行的结果:

    1、 总结notify()notifyAll()的区别与联系

    1) notify只是唤醒一个正在wait当前对象锁的线程,而notifyAll唤醒所有。值得注意的是:notify是本地方法,具体唤醒哪一个线程由虚拟机控制;如果有多个线程等待,则线程规划器任意挑选出其中一个wait()状态的线程来发出通知

    2) 当有线程调用了对象的notifyAll()方法(唤醒所有wait线程)或 notify()方法(只随机唤醒一个 wait 线程),被唤醒的的线程便会进入该对象的锁池中,锁池中的线程会去竞争该对象锁。也就是说,调用了notify后只有一个线程会由等待池进入锁池,而notifyAll会将该对象等待池内的所有线程移动到锁池中,等待锁竞争

    3) 调用notify和notifyAll方法后,当前线程并不会立即放弃锁的持有权,而必须要等待当前同步代码块执行完才会让出锁

    4) 优先级高的线程竞争到对象锁的概率大,假若某线程没有竞争到该对象锁,它还会留在锁池中,唯有线程再次调用wait()方法,它才会重新回到等待池中。而竞争到对象锁的线程则继续往下执行,直到执行完了synchronized代码块,它会释放掉该对象锁,这时锁池中的线程会继续竞争该对象锁。

     锁池:获取同一把锁的线程,在为抢夺锁时会进入到锁池
    等待池:调用wait操作的线程,会释放掉锁,进入到该对象的等待池中

    三,线程另一种通信的方法---Condition,Lock1的具体使用。

      使用notifyAll()方法是将所有被冻结的线程都唤醒,那如果只唤醒对方线程该怎么办?

      在jdk1.5之后添加了java.util.concurrent.locks包,在此包中有Condition,Lock,ReadWriteLock接口供使用。与synchronized相比,lock更加灵活,使用更加广泛。

      其中Lock代替了同步代码块synchronized;Condition的await(),signal(),signalAll()代替了Object中的wait(),notify(),notifyAll()方法。

    1 public interface Condition{
    2   void await() throws InterruptedException;//使线程进入休眠,类似于wait()
    3   void awaitUninterruptibly();
    4   long awaitNanos(long nanosTimeout) throws InterruptedException;
    5   boolean await(long time,TimeUnit unit) throws InterruptedException;
    6   boolean awaitUnit(Date deadline) throws InterruptedException;
    7   void  signal();//唤醒因await进入休眠的一个线程,类似于notify();
    8   void  signalAll();//唤醒因await进入休眠的所有线程,类似于notifyAll();
    9 }

      注意:1.Condition需要和lock结合使用。且lock和Condition必须是同一个ReentrantLock下的,否则会报iLLegalMonitorStateException异常。

         2.作用于同一个Condition实例下的线程之间才能进行通信。

        Lock lock=new ReentrantLock();
        Condition condition=lock.newCondition();

      【代码演示】:多个线程间的通信。 (使用signalAll方法,唤醒所有线程)

     1 import java.util.concurrent.locks.Condition;
     2 import java.util.concurrent.locks.Lock;
     3 import java.util.concurrent.locks.ReentrantLock;
     4 class Resource{
     5     int count=1;//商品库存数
     6     Lock lock=new ReentrantLock();
     7     Condition condition=lock.newCondition();
     8 }
     9 class Producer implements Runnable{
    10     Resource r;
    11     Lock lock;
    12     Condition condition;
    13     public Producer(Resource r){
    14         this.r=r;
    15         this.lock=r.lock;
    16         this.condition=r.condition;
    17     }
    18     public void run(){
    19             lock.lock();//加锁,如果获取失败,将进入休眠状态
    20             try {
    21                 while(true){
    22                     while (r.count>0){
    23                         condition.await();//将自己阻塞
    24                     }
    25                     System.out.println(Thread.currentThread().getName()+"生产了一件商品,此时商品数为:"+(++r.count));
    26                     condition.signalAll();//唤醒其他所有线程
    27                 }
    28             } catch (InterruptedException e) {
    29                 e.printStackTrace();
    30             }finally {
    31                 lock.unlock();//释放锁
    32             }
    33     }
    34 }
    35 class Consumer implements Runnable{
    36     Resource r;
    37     Lock lock;
    38     Condition condition;
    39     public Consumer(Resource r){
    40         this.r=r;
    41         this.lock=r.lock;
    42         this.condition=r.condition;
    43     }
    44     public void run(){
    45             lock.lock();//加锁
    46             try {
    47                 while(true){
    48                     while (r.count<=0){
    49                         condition.await();
    50                     }
    51                     System.out.println(Thread.currentThread().getName()+"消费了一件商品,此时商品数为:"+(--r.count));
    52                     condition.signalAll();
    53                 }
    54             } catch (InterruptedException e) {
    55                  e.printStackTrace();
    56             }finally {
    57                  lock.unlock();//释放锁()
    58             }
    59     }
    60 }
    61 public class ProducerConsumerDemo {
    62     public static void main(String[] args) {
    63         Resource r=new Resource();
    64         new Thread(new Producer(r)).start();
    65         new Thread(new Producer(r)).start();
    66         new Thread(new Consumer(r)).start();
    67         new Thread(new Consumer(r)).start();
    68     }
    69 }

      在Condition中,用await()替换wait(),用signal()替换notify(),用signalAll()替换notifyAll(),传统线程的通信方式,使用Condition都可以实现,这里注意,Condition是被绑定到Lock上的,要创建一个Lock的Condition必须用newCondition()方法。

       这样看来,Condition和传统的线程通信可能没什么区别,但Condition的强大之处在于它可以为多个线程间建立不同的Condition,用下面这个代码演示一下。

      【代码演示】:多个线程间的通信。(使用signalAll()方法,只唤醒对方的线程)

     1 import java.util.concurrent.locks.Condition;
     2 import java.util.concurrent.locks.Lock;
     3 import java.util.concurrent.locks.ReentrantLock;
     4 class Resource{
     5     int count=0;//商品库存数
     6     Lock lock=new ReentrantLock();
     7     Condition condition_con=lock.newCondition();
     8     Condition condition_pro=lock.newCondition();
     9 }
    10 class Producer implements Runnable {
    11     Resource r;
    12     Lock lock;
    13     Condition condition_con;
    14     Condition condition_pro;
    15     public Producer(Resource r) {
    16         this.r = r;
    17         this.lock = r.lock;
    18         this.condition_con = r.condition_con;
    19         this.condition_pro = r.condition_pro;
    20     }
    21     public void run() {
    22         lock.lock();//加锁
    23         try {
    24             while(true){
    25 
    26                 while (r.count > 0) {
    27                     condition_pro.await();
    28                 }
    29                 System.out.println(Thread.currentThread().getName() + "生产了一件商品,此时商品数为:" + (++r.count));
    30                 condition_con.signal();
    31             }
    32         } catch (InterruptedException e) {
    33             e.printStackTrace();
    34         } finally {
    35             lock.unlock();
    36             //释放锁,因为await方法发生异常时,程序停止运行,则就会一直持有锁,而造成死锁。所以将unlock放在finally中
    37         }
    38     }
    39 }
    40 
    41 class Consumer implements Runnable{
    42     Resource r;
    43     Lock lock;
    44     Condition condition_con;
    45     Condition condition_pro;
    46     public Consumer(Resource r){
    47         this.r=r;
    48         this.lock=r.lock;
    49         this.condition_con=r.condition_con;
    50         this.condition_pro=r.condition_pro;
    51     }
    52     public void run(){
    53         lock.lock();//加锁
    54             try {
    55                 while (true){
    56                     while (r.count<=0) {
    57                         condition_con.await();
    58                     }
    59                     System.out.println(Thread.currentThread().getName()+"消费了一件商品,此时商品数为:"+(--r.count));
    60                     condition_pro.signal();
    61                 }
    62             } catch (InterruptedException e) {
    63                 e.printStackTrace();
    64             }finally {
    65                 lock.unlock();//释放锁
    66             }
    67     }
    68 }
    69 public class ProducerConsumerDemo {
    70     public static void main(String[] args) {
    71         Resource r=new Resource();
    72         new Thread(new Producer(r)).start();
    73         new Thread(new Producer(r)).start();
    74         new Thread(new Consumer(r)).start();
    75         new Thread(new Consumer(r)).start();
    76     }
    77 }

     线程通信的方法--CountDownLatch方式

    • CountDownLatch是在java1.5被引入的,存在java.util.concurrent包下。
    • CountDownLatch能够使一个线程等待其他线程完成各自的工作之后,在执行。
    • CountDownLatch是通过一个计数器来实现的,计数器的初始值设置为要等待的线程的数量。每当一个线程完成自己的任务后,计数器的值就会减一,当计数器的值到达0时,它表示所有线程已经完成了任务,然后在闭锁上等待的线程就可以恢复执行任务。

      【代码演示】:教练需要等待所有运动员到齐且准备好之后,教练才能开始训练。

     1 import java.util.concurrent.CountDownLatch;
     2 public class CoachRacerDemo {
     3     private CountDownLatch countDownLatch=new CountDownLatch(3);//设置要等待的运动员是3个
     4     /**
     5      * 运动员方法
     6      */
     7     public void racer(){
     8         //获取运动员的名称
     9         String name=Thread.currentThread().getName();
    10         //运动员开始准备:打印准备信息
    11         System.out.println(name+"正在准备。。。");
    12         //准备中
    13         try {
    14             Thread.sleep(1000);
    15         } catch (InterruptedException e) {
    16             e.printStackTrace();
    17         }
    18         //准备完毕:打印准备完毕的信息
    19         System.out.println(name+"准备完毕");
    20         countDownLatch.countDown();
    21     }
    22 
    23     /**
    24      *教练方法
    25      */
    26     public void coach(){
    27         //获取教练线程的名称
    28         String name=Thread.currentThread().getName();
    29         //教练等待运动员准备完毕
    30         System.out.println(name+"等待运动员准备");
    31         //等待中。。。。
    32         try {
    33             countDownLatch.await();
    34         } catch (InterruptedException e) {
    35             e.printStackTrace();
    36         }
    37         //运动员都准备好了,教练开始训练
    38         System.out.println("所有运动员都准备完毕"+name+"开始训练");
    39     }
    40     public static void main(String[] args) {
    41         //创建CoachRacerDemo实例
    42         CoachRacerDemo coachRacerDemo=new CoachRacerDemo();
    43         //创建三个运动员线程
    44         Thread thread1=new Thread(new Runnable() {
    45             @Override
    46             public void run() {
    47                 coachRacerDemo.racer();
    48             }
    49         },"运动员1");
    50         Thread thread2=new Thread(new Runnable() {
    51             @Override
    52             public void run() {
    53                 coachRacerDemo.racer();
    54             }
    55         },"运动员2");
    56         Thread thread3=new Thread(new Runnable() {
    57             @Override
    58             public void run() {
    59                 coachRacerDemo.racer();
    60             }
    61         },"运动员3");
    62         //创建一个教练线程
    63         Thread thread4=new Thread(new Runnable() {
    64             @Override
    65             public void run() {
    66                 coachRacerDemo.coach();
    67             }
    68         },"教练");
    69         thread4.start();
    70         thread1.start();
    71         thread2.start();
    72         thread3.start();
    73 
    74     }
    75 }

      运行结果:

    五,线程通信的方法--CyclicBarrier方法

    • CyclicBarrier是在Java1.5被引入的,存在于java.util.concurrent包下。
    • CyclicBarrier实现让一组线程等待至某种状态之后在全部同时执行。
    • CyclicBarrier底层是基于ReentrantLock和Condition实现的。

     【代码演示】:类似于运动员比赛,当所有运动员都准备好之后,在枪声一响,所有运动员立马同时起跑。

     1 import java.util.Date;
     2 import java.util.concurrent.BrokenBarrierException;
     3 import java.util.concurrent.CyclicBarrier;
     4 
     5 public class ThreadRunnerDemo {
     6     private CyclicBarrier cyclicBarrier=new CyclicBarrier(4);//设置线程数量
     7     public void StartRun(){
     8         //获取线程的信息
     9         String name=Thread.currentThread().getName();
    10         //调用CyclicBarrier的await方法开始准备
    11         try {
    12             cyclicBarrier.await();
    13         } catch (InterruptedException e) {
    14             e.printStackTrace();
    15         } catch (BrokenBarrierException e) {
    16             e.printStackTrace();
    17         }
    18         System.out.println(name+"已经准备好"+new Date().getTime());
    19     }
    20     public static void main(String[] args) {
    21         final ThreadRunnerDemo threadRunnerDemo=new ThreadRunnerDemo();
    22         Thread thread1=new Thread(new Runnable() {
    23             @Override
    24             public void run() {
    25                 threadRunnerDemo.StartRun();
    26             }
    27         },"运动员1号");
    28         Thread thread2=new Thread(new Runnable() {
    29             @Override
    30             public void run() {
    31                 threadRunnerDemo.StartRun();
    32             }
    33         },"运动员2号");
    34         Thread thread3=new Thread(new Runnable() {
    35             @Override
    36             public void run() {
    37                 threadRunnerDemo.StartRun();
    38             }
    39         },"运动员3号");
    40         Thread thread4=new Thread(new Runnable() {
    41             @Override
    42             public void run() {
    43                 threadRunnerDemo.StartRun();
    44             }
    45         },"运动员4号");
    46         thread1.start();
    47         thread2.start();
    48         thread3.start();
    49         thread4.start();
    50     }
    51 }

      运行结果: 

     

       根据后面的时间,我们可以确定四个线程是同时启动的。

     六,线程间通信的方法--Semaphore方法

    •  Semaphore是在Java1.5被引入的,存在于java.util.concurrent包下。
    •  Semaphore用于控制对某组资源的访问权限。

    【代码演示】:8个工人使用3台机器工作,机器为互斥资源(即每次只能一个人使用)。

     1 import java.util.concurrent.Semaphore;
     2 public class WorkerDemo {
     3     static class Work implements Runnable{
     4         private int workId=0;//工人的工号
     5         private Semaphore machineNumber;//机器数量
     6         public Work(int workId,Semaphore machineNumber){
     7             this.workId=workId;
     8             this.machineNumber=machineNumber;
     9         }
    10         @Override
    11         public void run() {
    12             //工人要去获取机器
    13             try {
    14                 machineNumber.acquire();
    15             } catch (InterruptedException e) {
    16                 e.printStackTrace();
    17             }
    18             //打印当前工作的工人的信息
    19             String name=Thread.currentThread().getName();
    20             System.out.println(name+"获取机器,开始工作了。。。");
    21             //工作过程
    22             try {
    23                 Thread.sleep(1000);
    24             } catch (InterruptedException e) {
    25                 e.printStackTrace();
    26             }
    27             //工作完释放机器
    28             machineNumber.release();
    29             System.out.println(name+"工作完了,释放机器");
    30         }
    31     }
    32     public static void main(String[] args) {
    33         int workers=8;//工人数
    34         Semaphore semaphore=new Semaphore(3);//机器数
    35         for (int i = 0; i < workers; i++) {
    36             new Thread(new Work(i,semaphore)).start();
    37         }
    38     }
    39 }

    运行结果:

    面试题:wait和sleep的区别?

    面试题:wait和notify的异同?

    相同点:

    • 都Object中的方法。
    • 调用必须在同步的前提下。(因为该方法是要用锁对象调用,而只有在同步的情况下才有锁,也就是notify方法调用必须放在synchronized方法或synchronized块中)。

    不同点:

    wait():

    • wait()方法的调用使得当前线程必须要等待,直到另外一个线程调用notify()或者notifyAll()方法(注意必须调用notify/notifyAll方法才能唤醒)。
    • wait()方法的调用会导致锁的释放。

    notify():

    • notify()方法的调用可以唤醒当前锁对象下等待的另一个单个线程。优先级较高的优先唤醒,如果优先级一样,则随机唤醒。notifyAll()方法的调用可以唤醒当前锁对象下等待的所有线程。
  • 相关阅读:
    fckeditor上传问题的解决
    重装系统
    JQuery中文日期控件
    Log4Net使用心得
    not过滤的几种写法
    三级日期选择
    Log4Net本地正常,发布到服务器却不能工作
    工具栏设置大按钮
    Log4Net与Log2Console配合时中文问题的解决
    服务器不能发邮件
  • 原文地址:https://www.cnblogs.com/ljl150/p/12389548.html
Copyright © 2020-2023  润新知