• 媒体宣传报道重庆日报 王国平:扮靓重庆两江四岸” 让城市有机更新 2019-04-26
  • 我相信“交警雨中护送高考生”是真,“交警雨中护送高考生”反被该高考生家长投诉是假。 2019-04-16
  • 14名消防员日巡逻28公里 洗冷水澡 2019-04-10
  • 靶壕有了“蓝军”,百发百中的“神枪手”练起来 2019-04-10
  • 不是秀强大了,别人就会来做朋友,这逻辑不对 2019-04-01
  • 候选企业:中国石油呼和浩特石化公司 2019-03-26
  • 航天员沙漠野外生存训练完美收官!为第一天团打call 2019-03-25
  • 请问,建立市场经济后,原计划经济哪里去?改革后,我们还在实行计划经济,为何没有提及? 2019-03-25
  • 构建年轻干部梯次培养链 2019-03-19
  • 孙实的专栏作者中国国家地理网 2019-03-15
  • 湖南师范大学举行研究阐释党的十九大精神国家社科基金重大专项学术研讨会 2019-03-15
  • [雷人]蠢货!土地处于不同的城市和地段,关联的资源不一样,价值也不一样。不然给咱俩同样面积的土地,咱的在北上广深,你的在边远山区,你干么? 2019-03-08
  • 国际社会持续热议上合青岛峰会:上合组织发展进入新阶段 彰显中国领导力 2019-03-08
  • 珍惜野生动物频现甘孜境内 生态环境质量不断提升 2019-03-06
  • "新经济形势下金融创新的变革与机遇"论坛 2019-03-06
  • 频道栏目
    神奇公式秒杀全国11选5 > 程序开发 > 软件开发 > Java > 正文
    java线程之间的通信方式实例讲解
    2018-07-27 14:46:21         来源:nwpu_geeker的博客  
    收藏   我要投稿

    11选5三组合必中4个:引言

    神奇公式秒杀全国11选5 www.2zfa.com 当多个线程需要协作来完成一件事情的时候,如何去等待其他线程执行,又如何当线程执行完去通知其他线程结束等待。

    线程与进程的区别

    进程可以独立运行,它是系统进行资源分配和调度的独立单位。

    线程是进程的一个实体,是CPU调度和分派的基本单位,比进程更小的独立单位,它基本上不拥有系统资源。

    他们之间的联系:

    一个线程属于一个进程,而一个进程有多个线程,多个线程共享该进程的所有资源。

    区别:

    1.调度:线程作为CPU调度和分派的基本单位,进程拥有系统资源的独立单位。

    2.并发性:进程之间并发运行,同一个进程的多个线程也能并发执行。

    3.拥有资源,进程是拥有系统资源的基本单位,线程不拥有资源,但能访问进程的资源。

    线程之间的通信方式有哪些

    1.join

    2.共享变量(volatile、AtomicInteger)

    3.notify/wait

    4.lock/condition

    5.管道

    join

    首先,开启两个线程,分别打印123,线程A和线程B会不按套路打印。

    如果必须要线程A在线程B之前打印123呢?

    需要使用thread1.join();//我会等待线程1执行完成后再进行执行

    join的原理

    实际上join方法内部是通过wait实现的。

    上一段jdk源码

    public final synchronized void join(long millis)
     throws InterruptedException {
      long base = System.currentTimeMillis();
      long now = 0;
    
      if (millis < 0) {
    throw new IllegalArgumentException("timeout value is negative");
      }
    
      if (millis == 0) {
    while (isAlive()) {
     wait(0);
    }
      } else {
    while (isAlive()) {
     long delay = millis - now;
     if (delay <= 0) {
      break;
     }
     wait(delay);
     now = System.currentTimeMillis() - base;
    }
      }
     }

    这个join的原理很简单,前面那些if条件不管,主要看while循环里面的,while循环就是不断去判断this.isAlive的结果,用上面的例子,这个this就是joinThread。然后关键的代码就是wait(delay);一个定时的wait。这个wait的对象也是this,就是joinThread。上面我们已经讲了wait一定要在同步方法或者同步代码块中,源码中join方法的修饰符就是一个synchronized,表明这是一个同步的方法。

    不要看调用wait是joinThread,是一个线程。但是真正因为wait进入阻塞状态的,是持有对象监视器的线程,这里的对象监视器是joinThread,持有他的是main线程,因为在main线程中执行了join这个同步方法。

    所以main线程不断的wait,直到调用join方法那个线程对象销毁,才继续向下执行。

    但是源码中只有wait的方法,没有notify的方法。因为notify这个操作是JVM通过检测线程对象销毁而调用的native方法,是C++实现的,在源码中是找不到对应这个wait方法而存在的notify方法的。

    也就是说。

    利用Thread.join()方法来实现,join()方法的作用是等待调用线程执行完之后再执行任务。

    这个是必须线程A全部执行完,再去执行B.

    wait/notify

    如果是交替打印呢?

    必须使用wait()和notify()方法。

    一道面试题。

    编写两个线程,一个线程打印1~52,另一个线程打印字母A~Z,打印顺序为12A34B56C……5152Z,要求使用线程间的通信。

    代码如下:

    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    /**
     * Created by Edison Xu on 2017/3/2.
     */
    public enum Helper {
    
     instance;
    
     private static final ExecutorService tPool = Executors.newFixedThreadPool(2);
    
     public static String[] buildNoArr(int max) {
      String[] noArr = new String[max];
      for(int i=0;i

    使用wait和notify

    public class MethodOne {
     private final ThreadToGo threadToGo = new ThreadToGo();
     public Runnable newThreadOne() {
      final String[] inputArr = Helper.buildNoArr(52);
      return new Runnable() {
    private String[] arr = inputArr;
    public void run() {
     try {
      for (int i = 0; i < arr.length; i=i+2) {
    synchronized (threadToGo) {
     while (threadToGo.value == 2)
      threadToGo.wait();
     Helper.print(arr[i], arr[i + 1]);
     threadToGo.value = 2;
     threadToGo.notify();
    }
      }
     } catch (InterruptedException e) {
      System.out.println("Oops...");
     }
    }
      };
     }
     public Runnable newThreadTwo() {
      final String[] inputArr = Helper.buildCharArr(26);
      return new Runnable() {
    private String[] arr = inputArr;
    public void run() {
     try {
      for (int i = 0; i < arr.length; i++) {
    synchronized (threadToGo) {
     while (threadToGo.value == 1)
      threadToGo.wait();
     Helper.print(arr[i]);
     threadToGo.value = 1;
     threadToGo.notify();
    }
      }
     } catch (InterruptedException e) {
      System.out.println("Oops...");
     }
    }
      };
     }
     class ThreadToGo {
      int value = 1;
     }
     public static void main(String args[]) throws InterruptedException {
      MethodOne one = new MethodOne();
      Helper.instance.run(one.newThreadOne());
      Helper.instance.run(one.newThreadTwo());
      Helper.instance.shutdown();
     }
    }
    

    注意:

    wait和notify方法必须放在同步块里面,因为要对应同一个对象监视器。而sleep没有

    原理详解:

    wait方法会使执行该wait方法的线程停止,直到等到了notify的通知。细说一下,执行了wait方法的那个线程会因为wait方法而进入等待状态,该线程也会进入阻塞队列中。而执行了notify那个线程在执行完同步代码之后会通知在阻塞队列中的线程,使其进入就绪状态。被重新唤醒的线程会试图重新获得临界区的控制权,也就是对象锁,然后继续执行临界区也就是同步语句块中wait之后的代码。

    上面这个描述,可以看出一些细节。

    1.wait方法进入了阻塞队列,而上文讲过执行notify操作的线程与执行wait的线程是拥有同一个对象监视器,也就说wait方法执行之后,立刻释放掉锁,这样,另一个线程才能执行同步代码块,才能执行notify。

    2.notify线程会在执行完同步代码之后通知在阻塞队列中的线程,也就是说notify的那个线程并不是立即释放锁,而是在同步方法执行完,释放锁以后,wait方法的那个线程才会继续执行。

    3.被重新唤醒的线程会试图重新获得锁,也就说,在notify方法的线程释放掉锁以后,其通知的线程是不确定的,看具体是哪一个阻塞队列中的线程获取到对象锁。

    这里详细说一下,这个结果。wait使线程进入了阻塞状态,阻塞状态可以细分为3种:

    ● 等待阻塞:运行的线程执行wait方法,JVM会把该线程放入等待队列中。

    ● 同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池当中。

    ● 其他阻塞: 运行的线程执行了Thread.sleep或者join方法,或者发出I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止,或者超时、或者I/O处理完毕时,线程重新转入可运行状态。

    可运行状态就是线程执行start时,就是可运行状态,一旦CPU切换到这个线程就开始执行里面的run方法就进入了运行状态。

    上面会出现这个结果,就是因为notify仅仅让一个线程进入了可运行状态,而另一个线程则还在阻塞中。而notifyAll则使所有的线程都从等待队列中出来,而因为同步代码的关系,获得锁的线程进入可运行态,没有得到锁的则进入锁池,也是阻塞状态,但是会因为锁的释放而重新进入可运行态。所以notifyAll会让所有wait的线程都会继续执行。

    Lock和Condition

    如何程序不使用synchronized关键字来保持同步,而是直接适用Lock对像来保持同步,则系统中不存在隐式的同步监视器对象,也就不能使用wait()、notify()、notifyAll()来协调线程的运行.

    当使用LOCK对象保持同步时,JAVA为我们提供了Condition类来协调线程的运行。关于Condition类,JDK文档里进行了详细的解释.,再次就不啰嗦了。

    代码如下:

    public class MethodTwo {
     private Lock lock = new ReentrantLock(true);
     private Condition condition = lock.newCondition();
     private final ThreadToGo threadToGo = new ThreadToGo();
     public Runnable newThreadOne() {
      final String[] inputArr = Helper.buildNoArr(52);
      return new Runnable() {
    private String[] arr = inputArr;
    public void run() {
     for (int i = 0; i < arr.length; i=i+2) {
      try {
    lock.lock();
    while(threadToGo.value == 2)
     condition.await();
    Helper.print(arr[i], arr[i + 1]);
    threadToGo.value = 2;
    condition.signal();
      } catch (InterruptedException e) {
    e.printStackTrace();
      } finally {
    lock.unlock();
      }
     }
    }
      };
     }
     public Runnable newThreadTwo() {
      final String[] inputArr = Helper.buildCharArr(26);
      return new Runnable() {
    private String[] arr = inputArr;
    public void run() {
     for (int i = 0; i < arr.length; i++) {
      try {
    lock.lock();
    while(threadToGo.value == 1)
     condition.await();
    Helper.print(arr[i]);
    threadToGo.value = 1;
    condition.signal();
      } catch (Exception e) {
    e.printStackTrace();
      } finally {
    lock.unlock();
      }
     }
    }
      };
     }
     class ThreadToGo {
      int value = 1;
     }
     public static void main(String args[]) throws InterruptedException {
      MethodTwo two = new MethodTwo();
      Helper.instance.run(two.newThreadOne());
      Helper.instance.run(two.newThreadTwo());
      Helper.instance.shutdown();
     }
    }
    

    输出结果和上面是一样的! 只不过这里 显示的使用Lock对像来充当同步监视器,使用Condition对象来暂停指定线程,唤醒指定线程!

    共享变量(volatile、AtomicInteger)

    volatile修饰的变量值直接存在main memory里面,子线程对该变量的读写直接写入main memory,而不是像其它变量一样在local thread里面产生一份copy。volatile能保证所修饰的变量对于多个线程可见性,即只要被修改,其它线程读到的一定是最新的值。

    代码如下:

    public class MethodThree {
     private volatile ThreadToGo threadToGo = new ThreadToGo();
     class ThreadToGo {
      int value = 1;
     }
     public Runnable newThreadOne() {
      final String[] inputArr = Helper.buildNoArr(52);
      return new Runnable() {
    private String[] arr = inputArr;
    public void run() {
     for (int i = 0; i < arr.length; i=i+2) {
      while(threadToGo.value==2){}
      Helper.print(arr[i], arr[i + 1]);
      threadToGo.value=2;
     }
    }
      };
     }
     public Runnable newThreadTwo() {
      final String[] inputArr = Helper.buildCharArr(26);
      return new Runnable() {
    private String[] arr = inputArr;
    public void run() {
     for (int i = 0; i < arr.length; i++) {
      while(threadToGo.value==1){}
      Helper.print(arr[i]);
      threadToGo.value=1;
     }
    }
      };
     }
     public static void main(String args[]) throws InterruptedException {
      MethodThree three = new MethodThree();
      Helper.instance.run(three.newThreadOne());
      Helper.instance.run(three.newThreadTwo());
      Helper.instance.shutdown();
     }
    }
    

    管道

    管道流是JAVA中线程通讯的常用方式之一,基本流程如下:

    1)创建管道输出流PipedOutputStream pos和管道输入流PipedInputStream pis

    2)将pos和pis匹配,pos.connect(pis);

    3)将pos赋给信息输入线程,pis赋给信息获取线程,就可以实现线程间的通讯了

    代码如下:

    import java.io.IOException;
    import java.io.PipedInputStream;
    import java.io.PipedOutputStream;
    
    public class testPipeConnection {
    
     public static void main(String[] args) {
      /**
    * 创建管道输出流
    */
      PipedOutputStream pos = new PipedOutputStream();
      /**
    * 创建管道输入流
    */
      PipedInputStream pis = new PipedInputStream();
      try {
    /**
     * 将管道输入流与输出流连接 此过程也可通过重载的构造函数来实现
     */
    pos.connect(pis);
      } catch (IOException e) {
    e.printStackTrace();
      }
      /**
    * 创建生产者线程
    */
      Producer p = new Producer(pos);
      /**
    * 创建消费者线程
    */
      Consumer1 c1 = new Consumer1(pis);
      /**
    * 启动线程
    */
      p.start();
      c1.start();
     }
    }
    
    /**
     * 生产者线程(与一个管道输入流相关联)
     * 
     */
    class Producer extends Thread {
     private PipedOutputStream pos;
    
     public Producer(PipedOutputStream pos) {
      this.pos = pos;
     }
    
     public void run() {
      int i = 0;
      try {
    while(true)
    {
    this.sleep(3000);
    pos.write(i);
    i++;
    }
      } catch (Exception e) {
    e.printStackTrace();
      }
     }
    }
    
    /**
     * 消费者线程(与一个管道输入流相关联)
     * 
     */
    class Consumer1 extends Thread {
     private PipedInputStream pis;
    
     public Consumer1(PipedInputStream pis) {
      this.pis = pis;
     }
    
     public void run() {
      try {
    while(true)
    {
    System.out.println("consumer1:"+pis.read());
    }
      } catch (IOException e) {
    e.printStackTrace();
      }
     }
    }
    

    程序启动后,就可以看到producer线程往consumer1线程发送数据。

    consumer1:0
    consumer1:1
    consumer1:2
    consumer1:3
    ......
    

    管道流虽然使用起来方便,但是也有一些缺点

    1)管道流只能在两个线程之间传递数据

    线程consumer1和consumer2同时从pis中read数据,当线程producer往管道流中写入一段数据后,每一个时刻只有一个线程能获取到数据,并不是两个线程都能获取到producer发送来的数据,因此一个管道流只能用于两个线程间的通讯。不仅仅是管道流,其他IO方式都是一对一传输。

    2)管道流只能实现单向发送,如果要两个线程之间互通讯,则需要两个管道流

    可以看到上面的例子中,线程producer通过管道流向线程consumer发送数据,如果线程consumer想给线程producer发送数据,则需要新建另一个管道流pos1和pis1,将pos1赋给consumer1,将pis1赋给producer,具体例子本文不再多说。

    点击复制链接 与好友分享!回本站首页
    上一篇:java排序算法学习之冒泡,选择排序,二分查找,工具类等实现讲解
    下一篇:java线程方法之join简单总结(代码实例)
    相关文章
    图文推荐
    点击排行

    关于我们 | 联系我们 | 广告服务 | 投资合作 | 版权申明 | 在线帮助 | 网站地图 | 作品发布 | Vip技术培训 | 神奇公式秒杀全国11选5

    版权所有: 神奇公式秒杀全国11选5--致力于做实用的IT技术学习网站

  • 媒体宣传报道重庆日报 王国平:扮靓重庆两江四岸” 让城市有机更新 2019-04-26
  • 我相信“交警雨中护送高考生”是真,“交警雨中护送高考生”反被该高考生家长投诉是假。 2019-04-16
  • 14名消防员日巡逻28公里 洗冷水澡 2019-04-10
  • 靶壕有了“蓝军”,百发百中的“神枪手”练起来 2019-04-10
  • 不是秀强大了,别人就会来做朋友,这逻辑不对 2019-04-01
  • 候选企业:中国石油呼和浩特石化公司 2019-03-26
  • 航天员沙漠野外生存训练完美收官!为第一天团打call 2019-03-25
  • 请问,建立市场经济后,原计划经济哪里去?改革后,我们还在实行计划经济,为何没有提及? 2019-03-25
  • 构建年轻干部梯次培养链 2019-03-19
  • 孙实的专栏作者中国国家地理网 2019-03-15
  • 湖南师范大学举行研究阐释党的十九大精神国家社科基金重大专项学术研讨会 2019-03-15
  • [雷人]蠢货!土地处于不同的城市和地段,关联的资源不一样,价值也不一样。不然给咱俩同样面积的土地,咱的在北上广深,你的在边远山区,你干么? 2019-03-08
  • 国际社会持续热议上合青岛峰会:上合组织发展进入新阶段 彰显中国领导力 2019-03-08
  • 珍惜野生动物频现甘孜境内 生态环境质量不断提升 2019-03-06
  • "新经济形势下金融创新的变革与机遇"论坛 2019-03-06