<dl id="opymh"></dl>

<div id="opymh"></div>
      <div id="opymh"><tr id="opymh"></tr></div>

        <em id="opymh"><ins id="opymh"><mark id="opymh"></mark></ins></em><sup id="opymh"><menu id="opymh"></menu></sup>

        <em id="opymh"></em>

        <em id="opymh"><ol id="opymh"></ol></em>

              频道栏目
              首页 > 程序开发 > 软件开发 > Java > 正文
              java线程之间的通信方式实例讲解
              2018-07-27 14:46:21         来源£ºnwpu_geeker的博客  
              收藏   我要投稿

              引言

              当多个线程需要协作来完成一件事情的时候£¬如何去等待其他线程执行£¬又如何当线程执行完去通知其他线程结束等待¡£

              线程与进程的区别

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

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

              他们之间的联系£º

              一个线程属于一个进程£¬而一个进程有多个线程£¬多个线程共享该进程的所有资源¡£

              区别£º

              1.调度£º线程作为CPU调度和分派的基本单位£¬进程拥有系统资源的独立单位¡£

              2.并发性£º进程之间并发运行£¬同一个进程的多个线程也能并发执行¡£

              3.拥有资源£¬进程是拥有系统资源的基本单位£¬线程不拥有资源£¬但能访?#24335;?#31243;的资源¡£

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

              1.join

              2.共享变量(volatile¡¢AtomicInteger)

              3.notify/wait

              4.lock/condition

              5.管道

              join

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

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

              需要使用thread1.join();//?#19968;?#31561;待线程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方法而进入等待状态£¬该线程?#19981;?#36827;入阻塞队列?#23567;?#32780;执行了notify那个线程在执行完同步代码之后会通知在阻塞队列中的线程£¬使其进入就绪状态¡£被重新唤醒的线程会试图重新获得临界区的控制权£¬也就是对象锁£¬然后继续执行临界区也就是同步语句块中wait之后的代码¡£

              上面这个描述£¬可以看出一些细节¡£

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

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

              3.被重新唤醒的线程会试图重新获?#30431;ø£?#20063;就说£¬在notify方法的线程释放掉锁以后£¬其通知的线程是不确定的£¬看具体是哪一个阻塞队列中的线程获取到对象锁¡£

              这里详细说一下£¬这个结果¡£wait使线程进入了阻塞状态£¬阻塞状态可以细分为3种£º

              ¡ñ 等待阻塞£º运行的线程执行wait方法£¬JVM会把该线程放入等待队列?#23567;?/p>

              ¡ñ 同步阻塞£º运行的线程在获取对象的同步锁时£¬若该同步锁被别的线程占用£¬则JVM会把该线程放入锁池当?#23567;?/p>

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

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

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

              Lock和Condition

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

              当使用LOCK对象保持同步时£¬JAVA为我们提供了Condition类来协调线程的运行¡£关于Condition类£¬JDK文?#36947;?#36827;行了详细的解释.£¬再次就不啰嗦了¡£

              代码如下£º

              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里面£¬子线程对该变量的读写直?#26377;?#20837;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往管道流?#34892;?#20837;一段数据后£¬每一个时刻只有一个线程能获取到数据£¬并不是两个线程都能获取到producer发送来的数据£¬因此一个管道流只能用于两个线程间的通讯¡£不仅仅是管道流£¬其他IO方式都是一对一传输¡£

              2£©管道流只能实现单向发?#20572;?#22914;果要两个线程之间互通讯£¬则需要两个管道流

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

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

              关于我们 | 联系我们 | 广告服务 | 投资合作 | 版权申明 | 在线帮助 | 网站地图 | 作品发布 | Vip技术培训 | 举报?#34892;?/a>

              版权所有: 红黑联盟--致力于做实用的IT技术学习网站

              ¼«ËÙ·ÉͧºÃ¼Ù
              <dl id="opymh"></dl>

              <div id="opymh"></div>
                  <div id="opymh"><tr id="opymh"></tr></div>

                    <em id="opymh"><ins id="opymh"><mark id="opymh"></mark></ins></em><sup id="opymh"><menu id="opymh"></menu></sup>

                    <em id="opymh"></em>

                    <em id="opymh"><ol id="opymh"></ol></em>

                          <dl id="opymh"></dl>

                          <div id="opymh"></div>
                              <div id="opymh"><tr id="opymh"></tr></div>

                                <em id="opymh"><ins id="opymh"><mark id="opymh"></mark></ins></em><sup id="opymh"><menu id="opymh"></menu></sup>

                                <em id="opymh"></em>

                                <em id="opymh"><ol id="opymh"></ol></em>