Java线程间通信-回调的实现方式

简介:
Java线程间通信-回调的实现方式
 
Java线程间通信是非常复杂的问题的。线程间通信问题本质上是如何将与线程相关的变量或者对象传递给别的线程,从而实现交互。
 
比如举一个简单例子,有一个多线程的类,用来计算文件的MD5码,当多个这样的线程执行的时候,将每个文件的计算的结果反馈给主线程,并从控制台输出。
 
线程之间的通讯主要靠回调来实现,回调的概念说得抽象了很难理解,等于没说。我就做个比喻:比如,地铁的列车上有很多乘客,乘客们你一句他一句的问“到XX站了没?”,列车长肯定会很烦!于是乎,车长告诉大家,你们都各干各的事情,不用问了,到站了我会通知你们的。 这就是回调!
 
在上面这个例子中,列车长是一个多线程类,他的工作就是开车,到站后他要将到站的信息反馈给乘客线程。
 
以上面文件摘要码的计算为蓝本,下面探索Java线程间的通信问题:
 
方式一:静态方法回调
 
import java.io.File; 
import java.io.FileInputStream; 
import java.io.FileNotFoundException; 
import java.io.IOException; 
import java.security.MessageDigest; 
import java.security.NoSuchAlgorithmException; 
import java.security.DigestInputStream; 

/** 
* 求文件的信息摘要码(MD5) 

* @author leizhimin 2008-9-11 22:53:39 
*/
 
public  class CallbackDigest  implements Runnable { 
     private File inputFile;          //目标文件 

     public CallbackDigest(File input) { 
         this.inputFile = input; 
    } 

     public  void run() { 
         try { 
            FileInputStream in =  new FileInputStream(inputFile); 
            MessageDigest sha = MessageDigest.getInstance( "MD5"); 
            DigestInputStream din =  new DigestInputStream(in, sha); 
             int b; 
             while ((b = din.read()) != -1) ; 
            din.close(); 
             byte[] digest = sha.digest();    //摘要码 
             //完成后,回调主线程静态方法,将文件名-摘要码结果传递给住线程  
            CallbackDigestUserInterface.receiveDigest(digest, inputFile.getName()); 
        }  catch (FileNotFoundException e) { 
            e.printStackTrace(); 
        }  catch (NoSuchAlgorithmException e) { 
            e.printStackTrace(); 
        }  catch (IOException e) { 
            e.printStackTrace(); 
        } 
    } 
}
 
import java.io.File; 

/** 
* 静态非同步回调 

* @author leizhimin 2008-9-11 23:00:12 
*/
 
public  class CallbackDigestUserInterface { 
     /** 
     * 接收摘要码,输出到控制台 
     * 
     * @param digest        摘要码 
     * @param inputFileName 输入的文件名 
     */
 
     public  static  void receiveDigest( byte[] digest, String inputFileName) { 
        StringBuffer result =  new StringBuffer(inputFileName); 
        result.append( ": "); 
         for ( int j = 0; j < digest.length; j++) { 
            result.append(digest[j] +  " "); 
        } 
        System.out.println(result); 
    } 

     public  static  void main(String[] args) { 
        String arr[] = { "C:\\xcopy.txt""C:\\x.txt""C:\\xb.txt""C:\\bf2.txt"}; 
        args = arr; 
         for ( int i = 0; i < args.length; i++) { 
            File f =  new File(args[i]); 
            CallbackDigest cb =  new CallbackDigest(f); 
            Thread t =  new Thread(cb); 
            t.start(); 
        } 

    } 
}
 
bf2.txt: 31 -37 46 -53 -26 -45 36 -105 -89 124 119 111 28 72 74 112  
xb.txt: 112 -81 113 94 -65 -101 46 -24 -83 -55 -115 18 -1 91 -97 98  
x.txt: 123 -91 90 -16 -116 -94 -29 -5 -73 25 -57 12 71 23 -8 -47  
xcopy.txt: 123 -91 90 -16 -116 -94 -29 -5 -73 25 -57 12 71 23 -8 -47  

Process finished with exit code 0
 
这里的receiveDigest(byte[] digest, String inputFileName)没有同步控制,当多线程乱序执行的时候,可能会影响输出的次序等问题。
 
因此可以将此方法改为同步方法:有两种方式,一种在方法上加synchronized关键字修饰。一种是用synchronized(System.out)对象锁来同步输入控制台的代码部分。
 
方式二:实例方法回调
上面的方法过于死板,所有的多线程通讯都必须那么掉。不能搞特殊化,为了更加的灵活性,选择实例方法回调是一个不错的选择。
原理是,将回调类定义为一个实现某种接口的类(接口可以省掉),然后在每个多线程类上都注入一个回调对象。当线程执行完毕后,通过回调对象执行自己的回调方法,从而达到线程通信的目的。实现代码如下:
 

import java.io.File; 
import java.io.FileInputStream; 
import java.io.FileNotFoundException; 
import java.io.IOException; 
import java.security.MessageDigest; 
import java.security.NoSuchAlgorithmException; 
import java.security.DigestInputStream; 

/** 
* 求文件的信息摘要码(MD5) 

* @author leizhimin 2008-9-11 22:53:39 
*/
 
public  class InstanceCallbackDigest  implements Runnable { 
     private File inputFile;          //目标文件 
     //每个线程绑定一个回调对象 
     private InstanceCallbackDigestUserInterface instanceCallback; 

     /** 
     * 构件时一次注入回调对象 
     * 
     * @param instanceCallback 
     * @param inputFile 
     */
 
     public InstanceCallbackDigest(InstanceCallbackDigestUserInterface instanceCallback, File inputFile) { 
         this.instanceCallback = instanceCallback; 
         this.inputFile = inputFile; 
    } 

     public  void run() { 
         try { 
            FileInputStream in =  new FileInputStream(inputFile); 
            MessageDigest sha = MessageDigest.getInstance( "MD5"); 
            DigestInputStream din =  new DigestInputStream(in, sha); 
             int b; 
             while ((b = din.read()) != -1) ; 
            din.close(); 
             byte[] digest = sha.digest();    //摘要码 
             //完成后,回调主线程静态方法,将文件名-摘要码结果传递给住线程  
            instanceCallback.receiveDigest(digest); 
        }  catch (FileNotFoundException e) { 
            e.printStackTrace(); 
        }  catch (NoSuchAlgorithmException e) { 
            e.printStackTrace(); 
        }  catch (IOException e) { 
            e.printStackTrace(); 
        } 
    } 
}
 
import java.io.File; 

/** 
* 静态非同步回调 

* @author leizhimin 2008-9-11 23:00:12 
*/
 
public  class InstanceCallbackDigestUserInterface { 
     private File inputFile;      //回调与每个文件绑定 
     private  byte digest[];       //文件的消息摘要码 

     public InstanceCallbackDigestUserInterface(File inputFile) { 
         this.inputFile = inputFile; 
    } 

     /** 
     * 计算某个文件的消息摘要码 
     */
 
     public  void calculateDigest() { 
        InstanceCallbackDigest callback =  new InstanceCallbackDigest( this, inputFile); 
        Thread t =  new Thread(callback); 
        t.start(); 
    } 

     /** 
     * 接收消息摘要码 
     * 
     * @param digest 
     */
 
     public  void receiveDigest( byte[] digest) { 
         this.digest = digest; 
         //将消息摘要码输出到控制台实际上执行的是this.toString()方法 
        System.out.println( this); 
    } 

     /** 
     * 显示结果 
     * 
     * @return 结果 
     */
 
     public String toString() { 
        String result = inputFile.getName() +  ": "
         if (digest !=  null) { 
             for ( byte b : digest) { 
                result += b +  " "
            } 
        }  else { 
            result +=  "digest 不可用!"
        } 
         return result; 
    } 

     public  static  void main(String[] args) { 
        String arr[] = { "C:\\xcopy.txt""C:\\x.txt""C:\\xb.txt""C:\\bf2.txt"}; 
        args = arr; 
         for ( int i = 0; i < args.length; i++) { 
            File f =  new File(args[i]); 
            InstanceCallbackDigestUserInterface cb =  new InstanceCallbackDigestUserInterface(f); 
            cb.calculateDigest(); 
        } 
    } 
}
 
xcopy.txt: 123 -91 90 -16 -116 -94 -29 -5 -73 25 -57 12 71 23 -8 -47  
x.txt: 123 -91 90 -16 -116 -94 -29 -5 -73 25 -57 12 71 23 -8 -47  
xb.txt: 112 -81 113 94 -65 -101 46 -24 -83 -55 -115 18 -1 91 -97 98  
bf2.txt: 31 -37 46 -53 -26 -45 36 -105 -89 124 119 111 28 72 74 112  

Process finished with exit code 0
 
实例方法回调更加的灵活,一个文件对应一个回调对象,这样便于跟踪关于计算过程中信息而不需要额外的数据结构。其次,如果有必要,还可以重新计算指定的摘要(需要继承默认实现,然后覆盖方法)。
 
注意:这里的public void calculateDigest()方法,这个方法可能在逻辑上认为它属于一个构造器。然而,在构造器中启动线程是相当危险的,特别是对开始对象回调的线程。这里存在一个竞争条件:构造器中假如有很多的事情要做,而启动新的线程先做了,计算完成了后要回调,可是这个时候这个对象还没有初始化完成,这样就产生了错误。当然,实际中我还没有发现这样的错误,但是理论上是可能的。 因此,避免从构造器中启动线程是一个明智的选择。
 
方式三、使用回调接口
如果一个以上的类对实例对结果计算结果感兴趣,则可以设计一个所有这些类都实现的接口,接口中声明回调的方法。
 
如果一个以上的对象对线程计算的结果感兴趣,则线程可以保存一个回调对象列表。特定对象可以通过调用Thread或Runnable类中的方法将自己加入到这个表中,从而注册为对计算结果标识的兴趣。
 
/** 
* 回调接口 

* @author leizhimin 2008-9-13 17:20:11 
*/
 
public  interface DigestListener { 
     public  void digestCalculated( byte digest[]); 
}
 
/** 
* Created by IntelliJ IDEA. 

* @author leizhimin 2008-9-13 17:22:00 
*/
 
public  class ListCallbackDigest  implements Runnable { 
     private File inputFile; 
     private List<DigestListener> listenerList = Collections.synchronizedList( new ArrayList<DigestListener>()); 

     public ListCallbackDigest(File inputFile) { 
         this.inputFile = inputFile; 
    } 

     public  synchronized  void addDigestListener(DigestListener ler) { 
        listenerList.add(ler); 
    } 

     public  synchronized  void removeDigestListener(DigestListener ler) { 
        listenerList.remove(ler); 
    } 

     private  synchronized  void sendDigest( byte digest[]) { 
         for (DigestListener ler : listenerList) { 
            ler.digestCalculated(digest); 
        } 
    } 

     public  void run() { 
         try { 
            FileInputStream in =  new FileInputStream(inputFile); 
            MessageDigest sha = MessageDigest.getInstance( "MD5"); 
            DigestInputStream din =  new DigestInputStream(in, sha); 
             int b; 
             while ((b = din.read()) != -1) ; 
            din.close(); 
             byte[] digest = sha.digest();    //摘要码 
             //完成后,回调主线程静态方法,将文件名-摘要码结果传递给住线程 
            System.out.println(digest); 
             this.sendDigest(digest); 
        }  catch (FileNotFoundException e) { 
            e.printStackTrace(); 
        }  catch (NoSuchAlgorithmException e) { 
            e.printStackTrace(); 
        }  catch (IOException e) { 
            e.printStackTrace(); 
        } 
    } 
}
 
/** 
* Created by IntelliJ IDEA. 

* @author leizhimin 2008-9-13 17:35:20 
*/
 
public  class ListCallbackDigestUser  implements DigestListener{ 
     private File inputFile;      //回调与每个文件绑定 
     private  byte digest[];       //文件的消息摘要码 

     public ListCallbackDigestUser(File inputFile) { 
         this.inputFile = inputFile; 
    } 

     /** 
     * 计算某个文件的消息摘要码 
     */
 
     public  void calculateDigest() { 
        ListCallbackDigest callback =  new ListCallbackDigest(inputFile); 
        Thread t =  new Thread(callback); 
        t.start(); 
    } 

     public  void digestCalculated( byte digest[]) { 
         this.digest = digest; 
         //将消息摘要码输出到控制台实际上执行的是this.toString()方法 
        System.out.println( this); 
    } 

     /** 
     * 显示结果 
     * 
     * @return 结果 
     */
 
     public String toString() { 
        String result = inputFile.getName() +  ": "
         if (digest !=  null) { 
             for ( byte b : digest) { 
                result += b +  " "
            } 
        }  else { 
            result +=  "digest 不可用!"
        } 
         return result; 
    } 

     public  static  void main(String[] args) { 
        String arr[] = { "C:\\xcopy.txt""C:\\x.txt""C:\\xb.txt""C:\\bf2.txt"}; 
        args = arr; 
         for ( int i = 0; i < args.length; i++) { 
            File f =  new File(args[i]); 
            ListCallbackDigestUser cb =  new ListCallbackDigestUser(f); 
            cb.calculateDigest(); 
        } 
    } 
}
 
 

本文转自 leizhimin 51CTO博客,原文链接:http://blog.51cto.com/lavasoft/98796,如需转载请自行联系原作者
相关文章
|
13天前
|
监控 安全 Java
在 Java 中使用线程池监控以及动态调整线程池时需要注意什么?
【10月更文挑战第22天】在进行线程池的监控和动态调整时,要综合考虑多方面的因素,谨慎操作,以确保线程池能够高效、稳定地运行,满足业务的需求。
93 38
|
10天前
|
安全 Java
java 中 i++ 到底是否线程安全?
本文通过实例探讨了 `i++` 在多线程环境下的线程安全性问题。首先,使用 100 个线程分别执行 10000 次 `i++` 操作,发现最终结果小于预期的 1000000,证明 `i++` 是线程不安全的。接着,介绍了两种解决方法:使用 `synchronized` 关键字加锁和使用 `AtomicInteger` 类。其中,`AtomicInteger` 通过 `CAS` 操作实现了高效的线程安全。最后,通过分析字节码和源码,解释了 `i++` 为何线程不安全以及 `AtomicInteger` 如何保证线程安全。
java 中 i++ 到底是否线程安全?
|
14天前
|
Java 调度
[Java]线程生命周期与线程通信
本文详细探讨了线程生命周期与线程通信。文章首先分析了线程的五个基本状态及其转换过程,结合JDK1.8版本的特点进行了深入讲解。接着,通过多个实例介绍了线程通信的几种实现方式,包括使用`volatile`关键字、`Object`类的`wait()`和`notify()`方法、`CountDownLatch`、`ReentrantLock`结合`Condition`以及`LockSupport`等工具。全文旨在帮助读者理解线程管理的核心概念和技术细节。
31 1
[Java]线程生命周期与线程通信
|
1天前
|
安全 Java 测试技术
Java并行流陷阱:为什么指定线程池可能是个坏主意
本文探讨了Java并行流的使用陷阱,尤其是指定线程池的问题。文章分析了并行流的设计思想,指出了指定线程池的弊端,并提供了使用CompletableFuture等替代方案。同时,介绍了Parallel Collector库在处理阻塞任务时的优势和特点。
|
1天前
|
安全 Java 编译器
Java多线程编程的陷阱与最佳实践####
【10月更文挑战第29天】 本文深入探讨了Java多线程编程中的常见陷阱,如竞态条件、死锁、内存一致性错误等,并通过实例分析揭示了这些陷阱的成因。同时,文章也分享了一系列最佳实践,包括使用volatile关键字、原子类、线程安全集合以及并发框架(如java.util.concurrent包下的工具类),帮助开发者有效避免多线程编程中的问题,提升应用的稳定性和性能。 ####
15 1
|
4天前
|
存储 设计模式 分布式计算
Java中的多线程编程:并发与并行的深度解析####
在当今软件开发领域,多线程编程已成为提升应用性能、响应速度及资源利用率的关键手段之一。本文将深入探讨Java平台上的多线程机制,从基础概念到高级应用,全面解析并发与并行编程的核心理念、实现方式及其在实际项目中的应用策略。不同于常规摘要的简洁概述,本文旨在通过详尽的技术剖析,为读者构建一个系统化的多线程知识框架,辅以生动实例,让抽象概念具体化,复杂问题简单化。 ####
|
5天前
|
Java 开发者
在Java多线程编程的世界里,Lock接口正逐渐成为高手们的首选,取代了传统的synchronized关键字
在Java多线程编程的世界里,Lock接口正逐渐成为高手们的首选,取代了传统的synchronized关键字
30 4
|
5天前
|
消息中间件 供应链 Java
掌握Java多线程编程的艺术
【10月更文挑战第29天】 在当今软件开发领域,多线程编程已成为提升应用性能和响应速度的关键手段之一。本文旨在深入探讨Java多线程编程的核心技术、常见问题以及最佳实践,通过实际案例分析,帮助读者理解并掌握如何在Java应用中高效地使用多线程。不同于常规的技术总结,本文将结合作者多年的实践经验,以故事化的方式讲述多线程编程的魅力与挑战,旨在为读者提供一种全新的学习视角。
26 3
|
12天前
|
安全 Java
在 Java 中使用实现 Runnable 接口的方式创建线程
【10月更文挑战第22天】通过以上内容的介绍,相信你已经对在 Java 中如何使用实现 Runnable 接口的方式创建线程有了更深入的了解。在实际应用中,需要根据具体的需求和场景,合理选择线程创建方式,并注意线程安全、同步、通信等相关问题,以确保程序的正确性和稳定性。
|
6天前
|
安全 Java 调度
Java中的多线程编程入门
【10月更文挑战第29天】在Java的世界中,多线程就像是一场精心编排的交响乐。每个线程都是乐团中的一个乐手,他们各自演奏着自己的部分,却又和谐地共同完成整场演出。本文将带你走进Java多线程的世界,让你从零基础到能够编写基本的多线程程序。
18 1