最新公告
  • 欢迎您光临波比源码,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入我们
  • Java并发编程的艺术(六)——线程间的通信

    多条线程之间有时需要数据交互,下面介绍5种线程间数据交互的方式,他们的使用处景各有不同。

    1. volatile、synchronized关键字

    PS:关于volatile的详细介绍请移步至:Java并发编程的艺术(3)——volatile

    1.1 如何实现通讯?

    这两种方式都采取了同步机制实现多条线程间的数据通讯。与其说是“通讯”,倒不如说是“同享变量”来的恰当。当1个同享变量被volatile修饰 或 被同步块包裹后,他们的读写操作都会直接操作同享内存,从而各个线程都能看到同享变量最新的值,也就是实现了内存的可见性。

    1.2 特点

    • 这类方式本质上是“同享数据”,而非“传递数据”;只是从结果来看,数据好像是从写线程传递到了读线程;
    • 这类通讯方式没法指定特定的接收线程。当数据被修改后究竟哪条线程最早访问到,这由操作系统随机决定。
    • 总的来讲,这类方式其实不是真正意义上的“通讯”,而是“同享”。

    1.3 使用处景

    这类方式能“传递”变量。当需要传递1些公用的变量时就能够使用这类方式。如:传递boolean flag,用于表示状态、传递1个存储所有任务的队列等。

    1.4 例子

    用这类方式实现线程的开关控制。

    // 用于控制线程当前的履行状态 private volatile boolean running = false; // 开启1条线程 Thread thread = new Thread(new Runnable(){ void run(){ // 开关 while(!running){
                Thread.sleep(1000);
            } // 履行线程任务 doSometing();
        }
    }).start(); // 开始履行 public void start(){
        running = true;
    }

    2. 等待/通知机制

    2.1 如何实现?

    等待/通知机制的实现由Java完成,我们只需调用Object类的几个方法便可。

    • wait():将当前线程的状态改成“等待态”,加入等待队列,释放锁;直到当前线程产生中断或调用了notify方法,这条线程才会被从等待队列转移到同步队列,此时可以开始竞争锁。
    • wait(long):和wait()功能1样,只不过量了个超时动作。1旦超时,就会继续履行wait以后的代码,它不会抛超时异常!
    • notify():将等待队列中的1条线程转移到同步队列中去。
    • notifyAll():将等待队列中的所有线程都转移到同步队列中去。

    2.2 注意点

    • 以上方法都必须放在同步块中;
    • 并且以上方法都只能由所处同步块的锁对象调用;
    • 锁对象A.notify()/notifyAll()只能唤醒由锁对象A wait的线程;
    • 调用notify/notifyAll函数后仅仅是将线程从等待队列转移到阻塞队列,只有当该线程竞争到锁后,才能从wait方法中返回,继续履行接下来的代码;

    2.3 QA

    • 为何wait必须放在同步块中调用?
      由于等待/通知机制需要和同享状态变量配合使用,1般是先检查状态,若状态为true则履行wait,即包括“先检查后履行”,因此需要把这1进程加锁,确保其原子履行。
      举个例子:
    // 同享的状态变量 boolean flag = false; // 线程1 Thread t1 = new Thread(new Runnable(){ public void run(){ while(!flag){
                wait();
            }
        }
    }).start(); // 线程2 Thread t2 = new Thread(new Runnable(){ public void run(){
            flag = true;
            notifyAll();
        }
    }).start();

    上述例子thread1未加同步。当thread1履行到while那行后,判断其状态为true,此时若产生上下文切换,线程2开始履行,并1口气履行完了;此时flag已是true,但是thread1继续履行,遇到wait后便进入等待态;但此时已没有线程能唤醒它了,因此就1直等待下去。

    • 为何notify需要加锁?且必须和wait使用同1把锁?
      首先,加锁是为了保证同享变量的内存可见性,让它产生修改后能直接写入同享内存,好让wait所处的线程立即看见。
      其次,和wait使用同1把锁是为了确保wait、notify之间的互斥,即:同1时刻,只能有其中1条线程履行。

    • 为何必须使用同步块的锁对象调用wait函数?
      首先,由于wait会释放锁,因此通过锁对象调用wait就是告知wait释放哪一个锁。
      其次,告知线程,你是在哪一个锁对象上等待的,只有当该锁对象调用notify时你才能被唤醒。

    • 为何必须使用同步块的锁对象调用notify函数?
      告知notify,只唤醒在该锁对象上等待的线程。

    2.4 代码实现

    等待/通知机制用于实现生产者和消费者模式。

    • 生产者
    synchronized(锁A){
        flag = true;// 或:list.add(xx); 锁A.notify();
    }
    • 消费者
    synchronized(锁A){ // 不满足条件 while(!flag){ // 或:list.isEmpty() 锁A.wait();
        } // doSometing…… }

    2.5 超时等待模式

    在之前的生产者-消费者模式中,如果生产者没有发出通知,那末消费者将永久等待下去。为了不这类情况,我们可以给消费者增加超时等待功能。该功能依托于wait(long)方法,只需在wait前的检查条件中增加超时标识位,实现以下:

    public void get(long mills){
        synchronized( list ){ // 不加超时功能 if ( mills <= 0 ) { while( list.isEmpty() ){ list.wait();
                }
            } // 添加超时功能 else {
                boolean isTimeout = false; while(list.isEmpty() && isTimeout){ list.wait(mills);
                    isTimeout = true;
                } // doSometing…… }
        }
    }

    3. 管道流

    3.1 作用

    管道流用于在两个线程之间进行字节流或字符流的传递。

    3.2 特点

    • 管道流的实现依托PipedOutputStream、PipedInputStream、PipedWriter、PipedReader。分别对应字节流和字符流。
    • 他们与IO流的区分是:IO流是在硬盘、内存、Socket之间活动,而管道流仅在内存中的两条线程间活动。

    3.3 实现

    步骤以下:
    1. 在1条线程中分别创建输入流和输出流;
    2. 将输入流和输出留连接起来;
    3. 将输入流和输出流分外传递给两条线程;
    4. 调用read和write方法就能够实现线程间通讯。

    // 创建输入流与输出流对象 PipedWriter out = new PipedWriter();
    PipedReader in = new PipedReader(); // 连接输入输出流 out.connect(in); // 创建写线程 class WriteThread extends Thread{ private PipedWriter out; public WriteThread(PipedWriter out){ this.out = out;
        } public void run(){
            out.write("hello concurrent world!");
        }
    } // 创建读线程 class ReaderThread extends Thread{ private PipedReader in; public ReaderThread(PipedReader in){ this.in = in;
        } public void run(){
            in.read();
        }
    } // 

    4. join

    4.1 作用

    • join能将并发履行的多条线程串行履行;
    • join函数属于Thread类,通过1个thread对象调用。当在线程B中履行threadA.join()时,线程B将会被阻塞(底层调用wait方法),等到threadA线程运行结束后才会返回join方法。
    • 被等待的那条线程可能会履行很长时间,因此join函数会抛出InterruptedException。当调用threadA.interrupt()后,join函数就会抛出该异常。

    4.2 实现

    public static void main(String[] args){ // 开启1条线程 Thread t = new Thread(new Runnable(){ public void run(){ // doSometing }
        }).start(); // 调用join,等待t线程履行终了 try{
            t.join();
        }catch(InterruptedException e){ // 中断处理…… }
    
    }

    波比源码 – 精品源码模版分享 | www.bobi11.com
    1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长!
    2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
    3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
    4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
    5. 如有链接无法下载、失效或广告,请联系管理员处理!
    6. 本站资源售价只是赞助,收取费用仅维持本站的日常运营所需!
    7. 如遇到加密压缩包,请使用WINRAR解压,如遇到无法解压的请联系管理员!

    波比源码 » Java并发编程的艺术(六)——线程间的通信

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    波比源码
    一个高级程序员模板开发平台
    升级波友尊享更多特权立即升级