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();
        }
    }
}

Java线程间通信-回调的实现方式的更多相关文章

  1. Java线程间通信之wait/notify

    Java中的wait/notify/notifyAll可用来实现线程间通信,是Object类的方法,这三个方法都是native方法,是平台相关的,常用来实现生产者/消费者模式.我们来看下相关定义: w ...

  2. java线程间通信:一个小Demo完全搞懂

    版权声明:本文出自汪磊的博客,转载请务必注明出处. Java线程系列文章只是自己知识的总结梳理,都是最基础的玩意,已经掌握熟练的可以绕过. 一.从一个小Demo说起 上篇我们聊到了Java多线程的同步 ...

  3. 说说Java线程间通信

    序言 正文 [一] Java线程间如何通信? 线程间通信的目标是使线程间能够互相发送信号,包括如下几种方式: 1.通过共享对象通信 线程间发送信号的一个简单方式是在共享对象的变量里设置信号值:线程A在 ...

  4. 说说 Java 线程间通信

    序言 正文 一.Java线程间如何通信? 线程间通信的目标是使线程间能够互相发送信号,包括如下几种方式: 1.通过共享对象通信 线程间发送信号的一个简单方式是在共享对象的变量里设置信号值:线程A在一个 ...

  5. Java——线程间通信

    body, table{font-family: 微软雅黑; font-size: 10pt} table{border-collapse: collapse; border: solid gray; ...

  6. VC 线程间通信的三种方式

    1.使用全局变量(窗体不适用)     实现线程间通信的方法有很多,常用的主要是通过全局变量.自定义消息和事件对象等来实现的.其中又以对全局变量的使用最为简洁.该方法将全局变量作为线程监视的对象,并通 ...

  7. 【转】VC 线程间通信的三种方式

    原文网址:http://my.oschina.net/laopiao/blog/94728 1.使用全局变量(窗体不适用)      实现线程间通信的方法有很多,常用的主要是通过全局变量.自定义消息和 ...

  8. 线程间通信的三种方式(NSThread,GCD,NSOperation)

    一.NSThread线程间通信 #import "ViewController.h" @interface ViewController ()<UIScrollViewDel ...

  9. java线程间通信1--简单实例

    线程通信 一.线程间通信的条件 1.两个以上的线程访问同一块内存 2.线程同步,关键字 synchronized 二.线程间通信主要涉及的方法 wait(); ----> 用于阻塞进程 noti ...

随机推荐

  1. Soap UI 数据库脚本(转)

    3:在SoapUI的Test Case中新建Groovy Script连接数据库 接口如下 def sql = Sql.newInstance( 地址, 用户名, 密码, 驱动 ) 实现样例如下: i ...

  2. 使用Jquery.load()方法,出现-此页的状态信息无效,可能已损坏。[转]

    今天遇到此页的状态信息无效,可能已损坏,在以下页面找到解决办法,特记录下来: 转自:http://www.cnblogs.com/liuwenlong/archive/2011/05/09/20410 ...

  3. light oj 1297 Largest Box

    1297 - Largest Box   PDF (English) Statistics Forum Time Limit: 2 second(s) Memory Limit: 32 MB In t ...

  4. nyoj 168 房间安排(区间覆盖)

    房间安排 时间限制:3000 ms  |  内存限制:65535 KB 难度:2   描述 2010年上海世界博览会(Expo2010),是第41届世界博览会.于2010年5月1日至10月31日期间, ...

  5. Supervisor的安装与使用入门

    Supervisor是一个进程管理工具,官方的说法 自己开发的应用往往也希望做到随系统自动启动, 而且启动之后最好还能方便的控制其停止/重启. 传统的做法是在 /etc/init.d/ 下建立启动脚本 ...

  6. UVA 557 - Burger(概率 递推)

     Burger  When Mr. and Mrs. Clinton's twin sons Ben and Bill had their tenth birthday, the party was ...

  7. iOS 使用fir、 蒲公英 进行内部测试

    fir 蒲公英需要去注册账号并认证,按提示即可完成. 测了公司账号.个人开发账号,2个都可以用,就是要在配置文件里加上测试者的udid. 步骤: 1.添加测试机的udid edit配置文件,添加刚刚加 ...

  8. myeclipse中使用gradle开发项目

    gradle可以直接使用maven的代码库,并且支持编程,可以说是maven的加强版.今天我们学习下,如何在MyEclipse下使用gradle开发项目.我们的开发环境:myeclipse 2015, ...

  9. AWS SQS DOC AND RUBY DEMO

    # Amazon SQS 搜集整理aws sqs 的文档以及使用Ruby demo ## Amazon Simple Queue Service (SQS) 是一个可伸缩且可靠的消息传递框架,能够使用 ...

  10. jqgrid表格列动态加载的实现

    选中几个测点名,在表格中就显示几列. 具体代码如下: function reloadGrid(postData){ $('#gridTable').jqGrid('GridUnload'); var ...