﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>BlogJava-神奇好望角 The Magical Cape of Good Hope-随笔分类-Java SE</title><link>http://www.blogjava.net/shinzey/category/44813.html</link><description>庸人不必自扰，智者何需千虑？</description><language>zh-cn</language><lastBuildDate>Thu, 16 Feb 2012 11:54:55 GMT</lastBuildDate><pubDate>Thu, 16 Feb 2012 11:54:55 GMT</pubDate><ttl>60</ttl><item><title>非主流并发工具之 ForkJoinPool</title><link>http://www.blogjava.net/shinzey/archive/2012/02/09/368312.html</link><dc:creator>蜀山兆孨龘</dc:creator><author>蜀山兆孨龘</author><pubDate>Thu, 09 Feb 2012 02:40:00 GMT</pubDate><guid>http://www.blogjava.net/shinzey/archive/2012/02/09/368312.html</guid><wfw:comment>http://www.blogjava.net/shinzey/comments/368312.html</wfw:comment><comments>http://www.blogjava.net/shinzey/archive/2012/02/09/368312.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/shinzey/comments/commentRss/368312.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/shinzey/services/trackbacks/368312.html</trackback:ping><description><![CDATA[<div class="article">
    <p><code>ForkJoinPool</code> 是 Java SE 7 新功能“分叉/结合框架”的核心类，现在可能乏人问津，但我觉得它迟早会成为主流。分叉/结合框架是一个比较特殊的线程池框架，专用于需要将一个任务不断分解成子任务（分叉），再不断进行汇总得到最终结果（结合）的计算过程。比起传统的线程池类 <code>ThreadPoolExecutor</code>，<code>ForkJoinPool</code> 实现了工作窃取算法，使得空闲线程能够主动分担从别的线程分解出来的子任务，从而让所有的线程都尽可能处于饱满的工作状态，提高执行效率。</p>
    <p><code>ForkJoinPool</code> 提供了三类方法来调度子任务：</p>
    <dl>
        <dt><code>execute</code> 系列</dt>
        <dd>异步执行指定的任务。</dd>
        <dt><code>invoke</code> 和 <code>invokeAll</code></dt>
        <dd>执行指定的任务，等待完成，返回结果。</dd>
        <dt><code>submit</code> 系列</dt>
        <dd>异步执行指定的任务并立即返回一个 <code>Future</code> 对象。</dd>
    </dl>
    <p>子任务由 <code>ForkJoinTask</code> 的实例来代表。它是一个抽象类，JDK 为我们提供了两个实现：<code>RecursiveTask</code> 和 <code>RecursiveAction</code>，分别用于需要和不需要返回计算结果的子任务。<code>ForkJoinTask</code> 提供了三个静态的 <code>invokeAll</code> 方法来调度子任务，注意只能在 <code>ForkJoinPool</code> 执行计算的过程中调用它们。</p>
    <p><code>ForkJoinPool</code> 和 <code>ForkJoinTask</code> 还提供了很多让人眼花缭乱的公共方法，其实它们大多数都是其内部实现去调用的，对于应用开发人员来说意义不大。</p>
    <p>下面以统计 D 盘文件个数为例。这实际上是对一个文件树的遍历，我们需要递归地统计每个目录下的文件数量，最后汇总，非常适合用分叉/结合框架来处理：</p>
    <pre class="brush: java; highlight: [2, 9, 10, 19, 28, 40]">
// 处理单个目录的任务
public class CountingTask extends RecursiveTask&lt;Integer&gt; {
    private Path dir;

    public CountingTask(Path dir) {
        this.dir = dir;
    }

    @Override
    protected Integer compute() {
        int count = 0;
        List&lt;CountingTask&gt; subTasks = new ArrayList&lt;&gt;();

        // 读取目录 dir 的子路径。
        try (DirectoryStream&lt;Path&gt; ds = Files.newDirectoryStream(dir)) {
            for (Path subPath : ds) {
                if (Files.isDirectory(subPath, LinkOption.NOFOLLOW_LINKS)) {
                    // 对每个子目录都新建一个子任务。
                    subTasks.add(new CountingTask(subPath));
                } else {
                    // 遇到文件，则计数器增加 1。
                    count++;
                }
            }

            if (!subTasks.isEmpty()) {
                // 在当前的 ForkJoinPool 上调度所有的子任务。
                for (CountingTask subTask : invokeAll(subTasks)) {
                    count += subTask.join();
                }
            }
        } catch (IOException ex) {
            return 0;
        }
        return count;
    }
}

// 用一个 ForkJoinPool 实例调度“总任务”，然后敬请期待结果……
Integer count = new ForkJoinPool().invoke(new CountingTask(Paths.get("D:/")));
    </pre>
    <p>在我的笔记本上，经多次运行这段代码，耗费的时间稳定在 600 豪秒左右。普通线程池（<code>Executors.newCachedThreadPool()</code>）耗时 1100 毫秒左右，足见工作窃取的优势。</p>
    <p>结束本文前，我们来围观一个最神奇的结果：单线程算法（使用 <code>Files.walkFileTree(...)</code>）比这两个都快，平均耗时 550 毫秒！这警告我们并非引入多线程就能优化性能，并须要先经过多次测试才能下结论。</p>
</div><img src ="http://www.blogjava.net/shinzey/aggbug/368312.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/shinzey/" target="_blank">蜀山兆孨龘</a> 2012-02-09 10:40 <a href="http://www.blogjava.net/shinzey/archive/2012/02/09/368312.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Java 网络编程从菜鸟到叫兽 4：面向流的套接字 I/O</title><link>http://www.blogjava.net/shinzey/archive/2012/01/19/368751.html</link><dc:creator>蜀山兆孨龘</dc:creator><author>蜀山兆孨龘</author><pubDate>Thu, 19 Jan 2012 06:37:00 GMT</pubDate><guid>http://www.blogjava.net/shinzey/archive/2012/01/19/368751.html</guid><wfw:comment>http://www.blogjava.net/shinzey/comments/368751.html</wfw:comment><comments>http://www.blogjava.net/shinzey/archive/2012/01/19/368751.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/shinzey/comments/commentRss/368751.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/shinzey/services/trackbacks/368751.html</trackback:ping><description><![CDATA[<div class="article">
    <p>前面已经看到，<code>Socket</code> 类的 <code>getInputStream()</code> 和 <code>getOutStream()</code> 方法分别获取套接字的输入流和输出流。输入流用来读取远端发送过来的数据，输出流则用来向远端发送数据。</p>
    <h1>输入流</h1>
    <p>使用套接字的输入流读取数据时，当前线程会进入阻塞状态，直到套接字收到一些数据为止（亦即套接字的接收缓冲区有可用数据）。该输入流的 <code>available()</code> 方法只是返回接收缓冲区的可用字节数量，不可能知道远端还要发送多少字节。使用输入流的时候，最好先将它包装为一个 <code>BufferedInputStream</code>，因为读取接收缓冲区将导致 JVM 和底层系统之间的切换，应当尽量减少切换次数以提高性能。<code>BufferedInputStream</code> 的缓冲区大小最好设为套接字接收缓冲区的大小。</p>
    <p>如果直接调用输入流的 <code>close()</code> 方法来关闭它，则将导致套接字被关闭。对此，<code>Socket</code> 类提供了一个 <code>shutdownInput()</code> 方法来禁用输入流。调用该方法后，每次读操作都将返回 <code>EOF</code>，无法再读取远端发送的数据。对这个 <code>EOF</code> 的检测，不同的输入流包装体现出不同的结果，可能读到 -1 个字节，可能读到的字符串为 <code>null</code>，还可能收到一个 <code>EOFException</code> 等等。禁用输入流后，远端输出流的行为是平台相关的：</p>
    <ul>
        <li>在 BSD 平台上，远端的发送的数据能正常接收，然后直接丢弃。远端无法知道本端的输入流已禁用。这和 JDK 文档描述的行为一致。</li>
        <li>在 WINSOCK 平台上，远端发送数据将会导致“连接被重置”的错误。</li>
        <li>在 Linux 平台上，远端发送的数据能继续接收，直到套接字输入缓冲区填满，之后远端再也无法发送数据（若使用阻塞模式则进入死锁）。</li>
    </ul>
    <p>禁用输入流这种技术并不常用。</p>
    <h1>输出流</h1>
    <p>套接字的输出操作实际上仅仅将数据写到发送缓冲区内，当发送缓冲区填满且上次的发送成功后，由底层系统负责发送。如果发送缓冲区的剩余空间不够，当前线程就会阻塞。和输入流类似，最好将输出流包装为 <code>BufferedOutputStream</code>。</p>
    <p>如果套接字的双发都使用 <code>ObjectInputStream</code> 和 <code>ObjectOutputStream</code> 来读写 Java 对象，则必须先创建 <code>ObjectOutputStream</code>，因为 <code>ObjectInputStream</code> 在构造的时候会试图读取对象头部，如果双发都先创建 <code>ObjectInputStream</code>，则会互相等待对方的输出，造成死锁：</p>
    <pre class="brush: java">
// 创建的顺序不能颠倒！
ObjectOutputStream out = new ObjectOutputStream(socket.getOutputStream());
ObjectInputStream in = new ObjectInputStream(socket.getInputStream());
    </pre>
    <p>类似于输入流，关闭输出流也导致关闭套接字，所以 <code>Socket</code> 类同样提供了一个 <code>shutdownOutput()</code> 来禁用输出流。禁用输出流后，已写入发送缓冲区的数据会正常发送，之后的任何写操作都会导致 <code>IOException</code>，且远端的输入流始终会读到 <code>EOF</code>。禁用输出流非常有用，例如套接字的双发都在发送完毕数据后禁用输入流，然后双方都会收到 <code>EOF</code>，从而知道数据已经全部交换完毕，可以安全关闭套接字。直接关闭套接字会同时关闭输入流和输出流，且断开连接，达不到这种效果。</p>
    <h1>使用流的阻塞套接字的优缺点</h2>
    <p>如果要使用流进行输入和输出，就只能用阻塞模式的套接字。这里总结一下阻塞套接字的优缺点。先看看优点：</p>
    <ol>
        <li>编程模型简单，非常适合初学者上手。</li>
        <li>以装饰器模式设计的 Java I/O 使得开发人员可以轻松地从 I/O 流读写任何类型的数据。</li>
    </ol>
    <p>但在性能方面有致命的缺点：</p>
    <ol>
        <li>由于服务器套接字接受连接，以及套接字的读写都会阻塞，性能低下。</li>
        <li>如果不对 I/O 流手动进行缓冲，则可能造成一次只处理一个字节，性能低下。</li>
        <li>服务器套接字每次只能接受一个连接，导致 JVM 和底层系统之间频繁的调用切换，性能低下。</li>
    </ol>
    <p>下一篇文章开始探讨使用基于 NIO 的套接字通道和缓冲区实现伸缩性更强的 TCP 套接字。</p>
</div><img src ="http://www.blogjava.net/shinzey/aggbug/368751.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/shinzey/" target="_blank">蜀山兆孨龘</a> 2012-01-19 14:37 <a href="http://www.blogjava.net/shinzey/archive/2012/01/19/368751.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Java 网络编程从菜鸟到叫兽 3：套接字初始化详解</title><link>http://www.blogjava.net/shinzey/archive/2012/01/16/368393.html</link><dc:creator>蜀山兆孨龘</dc:creator><author>蜀山兆孨龘</author><pubDate>Mon, 16 Jan 2012 02:31:00 GMT</pubDate><guid>http://www.blogjava.net/shinzey/archive/2012/01/16/368393.html</guid><wfw:comment>http://www.blogjava.net/shinzey/comments/368393.html</wfw:comment><comments>http://www.blogjava.net/shinzey/archive/2012/01/16/368393.html#Feedback</comments><slash:comments>2</slash:comments><wfw:commentRss>http://www.blogjava.net/shinzey/comments/commentRss/368393.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/shinzey/services/trackbacks/368393.html</trackback:ping><description><![CDATA[<div class="article">
    <p><code>ServerSocket</code> 类和 <code>Socket</code> 类都提供了多个公共构造方法。不同的构造方法不仅带的参数不同，所具有的意义也不一样。下面分别解析这两个类的实例初始化过程。</p>
    <h1><code>ServerSocket</code> 实例的初始化</h1>
    <p><code>ServerSocket</code> 类提供了四个构造器：</p>
    <dl>
        <dt><code>public ServerSocket(int port) throws IOException</code></dt>
        <dt><code>public ServerSocket(int port, int backlog) throws IOException</code></dt>
        <dt><code>public ServerSocket(int port, int backlog, InetAddress bindAddr) throws IOException</code></dt>
        <dt><code>public ServerSocket() throws IOException</code></dt>
    </dl>
    <p>带参构造器用来创建已绑定的服务器套接字，也就是说构造成功后它就已经开始侦听指定的端口，且能够调用 <code>accept()</code> 方法来接受客户端连接。默认构造器则会创建未绑定的服务器套接字，构造成功后必须手动将其绑定到一个本地地址才能用，在绑定之前可以进行一些选项配置。</p>
    <h2>带参构造器</h2>
    <p>总的来说，带参构造器提供了三个参数：</p>
    <dl>
        <dt><code>port</code></dt>
        <dd>指定该服务器套接字所要侦听的本地端口。如果为 0，则由系统自动分配一个端口号，这必须以另外的方式让客户端获取端口号。</dd>
        <dt><code>backlog</code></dt>
        <dd>这个名词目前还没有合适的译名。底层系统的 TCP 实现会维护一个连接队列，该队列缓存了已被 TCP 处理完毕，但还没有被服务器套接字接受的客户端连接。一旦某个连接被接受（通过调用 <code>accept()</code> 方法），它就会被从队列中移除。<code>backlog</code> 参数就用于指定队列的最大长度，默认值为 50，但这个值只是一个建议，底层系统可能根据需要自动调整。如果队列满了，则其行为是平台相关的：微软的 WINSOCK 会拒绝新的连接，其他实现则什么都不做。严格地说，微软没有遵守规范，破坏了游戏规则……</dd>
        <dt><code>bindAddr</code></dt>
        <dd>一台机器可能会有多个本地 IP 地址，例如同时使用多块网卡。使用其他两个带参构造器时，该参数为 <code>null</code>，服务器套接字会在所有的本地 IP 地址（<code>0.0.0.0</code> 或 <code>::0</code>）上侦听。如果希望只侦听一个地址，则可使用该参数。</dd>
    </dl>
    <h2>默认构造器</h2>
    <p>如果使用默认构造器，在绑定地址前，还可以做些配置。绑定操作由两个 <code>bind</code> 方法定义，参数类似于带参构造器。配置项包括以下方面（都必须在绑定前配置）：</p>
    <dl>
        <dt>设置是否重用本地地址</dt>
        <dd>该选项由 <code>setReuseAddress(boolean on)</code> 方法配置，对应底层系统的 <code>SO_REUSEADDR</code> 套接字选项。JDK 没有定义该选项的默认值。如果该选项为 <code>false</code>，则在关闭 TCP 连接时，为了保证可靠性，该连接可能在关闭后的一段时间（大约两分钟）内保持超时状态（通常称为 <code>TIME_WAIT</code> 状态或 <code>2MSL</code> 等待状态），这段时间里无法将新建的服务器套接字绑定到同一个地址。在开发阶段，服务器可能不断重启，打开改选项会非常有用。</dd>
        <dt>设置接收缓冲区大小</dt>
        <dd>该选项由 <code>setReceiveBufferSize(int size)</code> 方法配置，对应底层系统的 <code>SO_RCVBUF</code> 套接字选项，单位是字节。《RFC 1323 - TCP Extensions for High Performance》将缓冲区大小定义为 64KB。该选项只是一个建议值，底层系统可能根据需要自行调整。</dd>
        <dt>设置超时值</dt>
        <dd>该选项由 <code>setSoTimeout(int timeout)</code> 方法配置，对应底层系统的 <code>SO_TIMEOUT</code> 套接字选项，单位是毫秒。默认值为 0。该选项影响 <code>accept</code> 方法的阻塞时间长度，如果超时将引发 <code>SocketTimeoutException</code>。如果设为 0，则表示永不超时。</dd>
        <dt>设置性能首选项</dt>
        <dd>性能首选项包括连接时间、延迟和带宽三个选项，由 <code>setPerformancePreferences(int connectionTime, int latency, int bandwidth) </code> 方法配置。这三个数值分别表示短连接时间、低延迟和高带宽的相对重要性，数值越大则越重要；其各自的绝对值没有意义。该方法的初衷是为了让 Java 能在用非 TCP/IP 实现的套接字环境下工作得更好，某些需要对网络进行调优的程序也可以将这三个首选项作为配置参数提供给用户。</dd>
    </dl>
    <h1><code>Socket</code> 实例的初始化</h1>
    <p><code>Socket</code> 类提供了六个公共构造器（已过时的除外）：</p>
    <dl>
        <dt><code>public Socket(String host, int port) throws UnknownHostException, IOException</code></dt>
        <dt><code>public Socket(InetAddress address, int port) throws IOException</code></dt>
        <dt><code>public Socket(String host, int port, InetAddress localAddr, int localPort) throws IOException</code></dt>
        <dt><code>public Socket(InetAddress address, int port, InetAddress localAddr, int localPort) throws IOException</code></dt>
        <dt><code>public Socket()</code></dt>
        <dt><code>public Socket(Proxy proxy)</code></dt>
    </dl>
    <p>前四个构造器创建已连接的客户端套接字，也就是说构造的时候就会去连接服务器。前两个构造器需要提供服务器的地址和端口作为参数，本地地址和端口由系统自动分配；后两个允许手动指定本地地址和端口，但极少使用。后两个构造器创建未连接的套接字，创建后需要调用 <code>connect</code> 方法手动连接，连接之前可以做一些配置。最后一个构造器接受一个代表代理服务其的 <code>Proxy</code> 对象，JDK 支持 HTTP 和 SOCKS（V4 或 V5）两种代理类型。</p>
    <h2>连接前的配置</h2>
    <p>在连接前，客户端套接字不仅像服务器套接字那样可以设置是否重用本地地址、缓冲区大小、超时值和性能首选项，还能够配置以下各项（都必须在连接前配置）：</p>
    <dl>
        <dt>设置是否保持活跃</dt>
        <dd>该选项由 <code>setKeepAlive(boolean on)</code> 方法配置，对应底层系统的 <code>SO_KEEPALIVE</code> 套接字选项。默认值为 <code>false</code>。如果打开该选项，则套接字会定期自动发送保持活跃的探测性消息，类似于心跳检测。根据《RFC 1122 - Requirements for Internet Hosts》的规定，保持活跃机制只是 TCP 的一个可选功能，如果支持的话，默认必须为 <code>false</code>，而且这种机制默认在成功建立连接后，且连续两小时没有数据传输的情况下才会被激活。从另一方面来看，通过套接字的 I/O 操作完全可以知道连接是否还有效，所以该选项的实用价值不大。</dd>
        <dt>设置是否收发带外数据</dt>
        <dl>该选项由 <code>setOOBInline(boolean on)</code> 方法配置，对应底层系统的 <code>SO_OOBINLINE</code> 套接字选项。默认值为 <code>off</code>。带外数据（Out-of-band Data）也叫做紧急数据，表示数据很重要，需要使用不同于发送普通数据的一个专用通道来发送。打开该选项后，就可以调用 <code>sendUrgentData(int data)</code> 方法发送一个字节的紧急数据。JDK 对带外数据只提供了有限支持，紧急数据将会和普通数据一起被收到，并且无法自动区分。该选项对应用开发人员意义不大。</dl>
        <dt>设置是否从容关闭连接</dt>
        <dd>该选项由 <code>setSoLinger(boolean on, int linger)</code> 方法配置，对应底层系统的 <code>SO_LINGER</code> 套接字选项。默认为 <code>false</code>。该选项只会影响套接字的关闭，其中的 <code>linger</code> 参数表示超时时间，单位为秒。如果打开改选项：如果将 <code>linger</code> 设为 0，则关闭套接字的时候，未发送的数据会被丢弃，且另一端会出现连接被同位体重置的异常；如果 <code>linger</code> 非 0，则关闭套接字的线程将被阻塞，直到数据全部发送或超时，超时后的行为与底层系统相关，JDK 无法控制。如果关闭该选项，则套接字正常关闭，数据也会全部发送。由于底层实现的差异性，不提倡应用开发人员打开该选项。</dd>
        <dt>设置是否延迟发送数据</dt>
        <dd>该选项由 <code>setTcpNoDelay(boolean on)</code> 方法配置，对应底层系统的 <code>TCP_NODELAY</code> TCP 选项。默认值为 <code>off</code>。打开该选项将禁用 Nagle 算法，TCP 包会立即发送；关闭该选项则会启用 Nagle 算法，多个较小的 TCP 包会被组合成一个大包一起发送，虽然发送延迟了，但有利于避免网络拥塞。默认为 <code>false</code>。该选项对实时性很强的程序可能有用，但一般的程序不需要关心。</dd>
        <dt>设置流量类别</dt>
        <dd>该选项由 <code>setTrafficClass(int tc)</code> 方法配置，对应底层系统的“流量类别”套接字属性。该选项用于向网络（例如路由器）提示从该套接字发送的包需要获取哪些服务类型，对本地 TCP 协议栈没有影响。IPv4 和 IPv6 分别定义了多个不同的值，例如 IPv4 将 <code>0x08</code> 定义为最大吞吐量，<code>0x10</code> 定义为最小延迟，等等。可以用或运算将多个值合并为一个选项。该选项用来调整性能，需要根据实际情况设置。由于只是建议值，可能被网络忽略。</dd>
    </dl>
</div><img src ="http://www.blogjava.net/shinzey/aggbug/368393.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/shinzey/" target="_blank">蜀山兆孨龘</a> 2012-01-16 10:31 <a href="http://www.blogjava.net/shinzey/archive/2012/01/16/368393.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>单例模式的一个疑问</title><link>http://www.blogjava.net/shinzey/archive/2012/01/10/368251.html</link><dc:creator>蜀山兆孨龘</dc:creator><author>蜀山兆孨龘</author><pubDate>Tue, 10 Jan 2012 09:39:00 GMT</pubDate><guid>http://www.blogjava.net/shinzey/archive/2012/01/10/368251.html</guid><wfw:comment>http://www.blogjava.net/shinzey/comments/368251.html</wfw:comment><comments>http://www.blogjava.net/shinzey/archive/2012/01/10/368251.html#Feedback</comments><slash:comments>6</slash:comments><wfw:commentRss>http://www.blogjava.net/shinzey/comments/commentRss/368251.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/shinzey/services/trackbacks/368251.html</trackback:ping><description><![CDATA[<div class="article">
    <p>网上很多关于单例模式写法的文章，不外乎饿汉和懒汉两种形式的讨论。很多人喜欢用懒汉式，因为觉得它实现了延迟加载，可以让系统的性能更好。但事实果真如此吗？我对此存疑。</p>
    <p>首先我们检查一下饿汉和懒汉单例模式最简单的写法（这里不讨论哪种懒汉写法更好）：</p>
    <pre class="brush: java">
// 饿汉
public final class HungrySingleton {
    private static final HungrySingleton INSTANCE = new HungrySingleton();

    private HungrySingleton() {
        System.out.println("Initializing...");
    }

    public static HungrySingleton getInstance() {
        return INSTANCE;
    }
}

// 懒汉
public final class LazySingleton {
    private static LazySingleton INSTANCE;

    private LazySingleton() {
        System.out.println("Initializing...");
    }

    public static synchronized LazySingleton getInstance() {
        if (INSTANCE == null) {
            INSTANCE = new LazySingleton();
        }
        return INSTANCE;
    }
}
    </pre>
    <p>从理论上来说，<code>HungrySingleton</code> 的单例在该类第一次使用的时候创建，而 <code>LazySingleton</code> 的单例则在其 <code>getInstance()</code> 方法被调用的时候创建。至于网上有人声称“饿汉式不管用不用都会初始化”，纯属走路的时候步子迈得太大。谁的加载更迟？如果你只是调用它们的 <code>getInstance()</code> 方法来得到单例对象，则它们都是延迟加载，这样懒汉式没有任何意义，而且由于 <code>LazySingleton</code> 采取了同步措施，性能更低（可以说任何懒汉式的性能都低于饿汉式）。当你使用一个单例类的时候，难道第一步不是调用 <code>getInstance()</code> 么？所以在自己的代码里，我更喜欢用饿汉式。</p>
    <p>下面用一个例子来测试加载顺序：</p>
    <pre class="brush: java">
// 饿汉
System.out.println("Before");
HungrySingleton.getInstance();
System.out.println("After");

// 懒汉
System.out.println("Before");
LazySingleton.getInstance();
System.out.println("After");
    </pre>
    <p>输出结果都是：</p>
    <pre class="output">Before
Initializing...
After</pre>
    <p>那么，懒汉模式还有什么存在意义？如果系统使用了某些需要在启动时对类进行扫描的框架，使用饿汉式的话，启动时间比懒汉式更长，如果使用了大量单例类，不利于开发阶段。在系统的正式运行阶段，所有的单例类迟早都要加载的，总的说来两者性能持平，但是懒汉式每次都至少多一个判断，所以越到后期越体现饿汉的优越性。</p>
    <p>最后，推荐下《Effective Java》第二版指出的用枚举类型实现的饿汉单例模式：</p>
    <pre class="brush: java">
// 饿汉
public enum HungrySingleton {
    INSTANCE;

    private HungrySingleton() {
    }
}
</pre>
    <p>这种写法不但最简洁，还能轻易扩展为实例数量固定的“多例模式”。</p>
</div><img src ="http://www.blogjava.net/shinzey/aggbug/368251.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/shinzey/" target="_blank">蜀山兆孨龘</a> 2012-01-10 17:39 <a href="http://www.blogjava.net/shinzey/archive/2012/01/10/368251.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Java 网络编程从菜鸟到叫兽 2：TCP 和套接字入门</title><link>http://www.blogjava.net/shinzey/archive/2012/01/04/367846.html</link><dc:creator>蜀山兆孨龘</dc:creator><author>蜀山兆孨龘</author><pubDate>Wed, 04 Jan 2012 14:21:00 GMT</pubDate><guid>http://www.blogjava.net/shinzey/archive/2012/01/04/367846.html</guid><wfw:comment>http://www.blogjava.net/shinzey/comments/367846.html</wfw:comment><comments>http://www.blogjava.net/shinzey/archive/2012/01/04/367846.html#Feedback</comments><slash:comments>5</slash:comments><wfw:commentRss>http://www.blogjava.net/shinzey/comments/commentRss/367846.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/shinzey/services/trackbacks/367846.html</trackback:ping><description><![CDATA[<div class="article">
    <p>JDK 提供了对 TCP（Transmission Control Protocol，传输控制协议）和 UDP（User Datagram Protocol，用户数据报协议）这两个数据传输协议的支持。本文开始探讨 TCP。</p>
    <h1>TCP 基础知识</h1>
    <p>在“服务器-客户端”这种架构中，服务器和客户端各自维护一个端点，两个端点需要通过网络进行数据交换。TCP 为这种需求提供了一种可靠的流式连接，流式的意思是传出和收到的数据都是连续的字节，没有对数据量进行大小限制。一个端点由 IP 地址和端口构成（专业术语为“元组 <code>{IP 地址, 端口}</code>”）。这样，一个连接就可以由元组 <code>{本地地址, 本地端口, 远程地址, 远程端口}</code> 来表示。</p>
    <h2>连接过程</h2>
    <p>在 TCP 编程接口中，端点体现为 TCP 套接字。共有两种 TCP 套接字：主动和被动，“被动”状态也常被称为“侦听”状态。服务器和客户端利用套接字进行连接的过程如下：</p>
    <ol>
        <li>服务器创建一个被动套接字，开始循环侦听客户端的连接。</li>
        <li>客户端创建一个主动套接字，连接服务器。</li>
        <li>服务器接受客户端的连接，并创建一个代表该连接的主动套接字。</li>
        <li>服务器和客户端通过步骤 2 和 3 中创建的两个主动套接字进行数据传输。</li>
    </ol>
    <p>下面是连接过程的图解：</p>
    <p class="center"><img alt="TCP 连接" title="TCP 连接" src="http://www.blogjava.net/images/blogjava_net/shinzey/44806/o_TCP%20%e8%bf%9e%e6%8e%a5.png"><br>TCP 连接</p>
    <h1>一个简单的 TCP 服务器</h1>
    <p>JDK 提供了 <code>ServerSocket</code> 类来代表 TCP 服务器的被动套接字。下面的代码演示了一个简单的 TCP 服务器（多线程阻塞模式），它不断侦听并接受客户端的连接，然后将客户端发送过来的文本按行读取，全文转换为大写后返回给客户端，直到客户端发送文本行 <code>bye</code>：</p>
    <pre class="brush: java; highlight: [6, 14, 16, 35, 36, 38, 39, 42, 48]">
public class TcpServer implements Runnable {
    private ServerSocket serverSocket;

    public TcpServer(int port) throws IOException {
        // 创建绑定到某个端口的 TCP 服务器被动套接字。
        serverSocket = new ServerSocket(port);
    }

    @Override
    public void run() {
        while (true) {
            try {
                // 以阻塞的方式接受一个客户端连接，返回代表该连接的主动套接字。
                Socket socket = serverSocket.accept();
                // 在新线程中处理客户端连接。
                new Thread(new ClientHandler(socket)).start();
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }
    }
}

public class ClientHandler implements Runnable {
    private Socket socket;

    public ClientHandler(Socket socket) {
        this.socket = Objects.requireNonNull(socket);
    }

    @Override
    public void run() {
        try (Socket s = socket) {  // 减少代码量的花招……
            // 包装套接字的输入流以读取客户端发送的文本行。
            BufferedReader in = new BufferedReader(new InputStreamReader(
                    s.getInputStream(), StandardCharsets.UTF_8));
            // 包装套接字的输出流以向客户端发送转换结果。
            PrintWriter out = new PrintWriter(new OutputStreamWriter(
                    s.getOutputStream(), StandardCharsets.UTF_8), true);

            String line = null;
            while ((line = in.readLine()) != null) {
                if (line.equals("bye")) {
                    break;
                }

                // 将转换结果输出给客户端。
                out.println(line.toUpperCase(Locale.ENGLISH));
            }
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }
}
    </pre>
    <p>阻塞模式的编程方式简单，但存在性能问题，因为服务器线程会卡死在接受客户端的 <code>accept()</code> 方法上，不能有效利用资源。套接字支持非阻塞模式，现在暂时略过。</p>
    <h1>一个简单的 TCP 客户端</h1>
    <p>JDK 提供了 <code>Socket</code> 类来代表 TCP 客户端的主动套接字。下面的代码演示了上述服务器的客户端：</p>
    <pre class="brush: java; highlight: [6, 13, 14, 16, 17, 27, 29, 33]">
public class TcpClient implements Runnable {
    private Socket socket;

    public TcpClient(String host, int port) throws IOException {
        // 创建连接到服务器的套接字。
        socket = new Socket(host, port);
    }

    @Override
    public void run() {
        try (Socket s = socket) {  // 再次减少代码量……
            // 包装套接字的输出流以向服务器发送文本行。
            PrintWriter out = new PrintWriter(new OutputStreamWriter(
                    s.getOutputStream(), StandardCharsets.UTF_8), true);
            // 包装套接字的输入流以读取服务器返回的文本行。
            BufferedReader in = new BufferedReader(new InputStreamReader(
                    s.getInputStream(), StandardCharsets.UTF_8));

            Console console = System.console();
            String line = null;
            while ((line = console.readLine()) != null) {
                if (line.equals("bye")) {
                    break;
                }

                // 将文本行发送给服务器。
                out.println(line);
                // 打印服务器返回的文本行。
                console.writer().println(in.readLine());
            }

            // 通知服务器关闭连接。
            out.println("bye");
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }
}
    </pre>
    <p>从 JDK 文档可以看到，<code>ServerSocket</code> 和 <code>Socket</code> 在初始化的时候，可以设定一些参数，还支持延迟绑定。这些东西对性能和行为都有所影响。下一篇文章将详解这两个类的初始化。</p>
</div><img src ="http://www.blogjava.net/shinzey/aggbug/367846.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/shinzey/" target="_blank">蜀山兆孨龘</a> 2012-01-04 22:21 <a href="http://www.blogjava.net/shinzey/archive/2012/01/04/367846.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Java 网络编程从菜鸟到叫兽 1：IP</title><link>http://www.blogjava.net/shinzey/archive/2011/12/30/367593.html</link><dc:creator>蜀山兆孨龘</dc:creator><author>蜀山兆孨龘</author><pubDate>Fri, 30 Dec 2011 09:39:00 GMT</pubDate><guid>http://www.blogjava.net/shinzey/archive/2011/12/30/367593.html</guid><wfw:comment>http://www.blogjava.net/shinzey/comments/367593.html</wfw:comment><comments>http://www.blogjava.net/shinzey/archive/2011/12/30/367593.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/shinzey/comments/commentRss/367593.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/shinzey/services/trackbacks/367593.html</trackback:ping><description><![CDATA[<div class="article">
    <p>我竟然到现在才发现《Fundamental Networking in Java》这本神作，真有点无地自容的感觉。最近几年做的都是所谓的企业级开发，免不了和网络打交道，但在实际工作中，往往会采用框架将底层细节和上层应用隔离开，感觉就像是在一个 Word 模板表单里面填写内容，做出来也没什么成就感。虽然没有不使用框架的理由，但我还真是有点怀念当初直接用套接字做网络编程的日子，既能掌控更多东西，还可以学到更多知识，为研究框架的实现原理打基础。闲话完毕，转入今天的正题：IP（Internet Protocol，互联网协议）。</p>
    <h1>IP 基础知识</h1>
    <p>说到 IP，大多数人的第一反应估计都是 IP 地址。其实 IP 是一种协议，IP 地址只是协议的一部分。《RFC 791 - INTERNET PROTOCOL》说：“互联网协议是为在包交换计算机通信网络的互联系统中使用而设计的。”IP 包含三方面的功能：</p>
    <ol>
        <li>用于查找主机的寻址系统</li>
        <li>包格式的定义</li>
        <li>传输和接收包的规则</li>
    </ol>
    <h1>IP 的相关 Java 类</h1>
    <p>从 Java 的角度来看上面说到的三个功能，只有第一个是开发人员需要关心的。另外两个都依赖底层系统的实现，JDK 也没有提供相关的类去操作。下面一一介绍 JDK 提供的用于处理 IP 地址的类。</p>
    <h2>InetAddress</h2>
    <p>此类用来表示 IP 地址，它有两个子类：<code>Inet4Address</code> 和 <code>Inet6Address</code>，分别用于处理 IPv4 和 IPv6 两个版本。在实际应用中，<code>InetAddress</code> 足以应付绝大多数情况。它提供了一些静态方法来构造实例，能根据参数格式自动识别 IP 版本：</p>
    <dl>
    <dt><code>public static InetAddress[] getAllByName(String host) throws UnknownHostException</code></dt>
    <dd>解析指定的主机地址，并返回其所有的 IP 地址；如果传入 IP 地址字符串，则只会校验格式，返回的数组也只包含一个代表该 IP 地址的实例。例如，想看看谷歌有多少马甲的话，<code>InetAddress.getAllByName("www.google.com")</code> 就可以了。</dd>
    <dt><code>public static InetAddress getByAddress(byte[] addr) throws UnknownHostException</code></dt>
    <dd>用表示 IP 地址的字节数组（专业术语称为“原始 IP 地址”）构造一个实例。IPv4  地址必须是 4 个字节，IPv6 必须 16 个。不常用。</dd>
    <dt><code>public static InetAddress getByAddress(String host, byte[] addr) throws UnknownHostException</code></dt>
    <dd>用主机地址和原始 IP 地址构造一个实例。此方法应该慎用，因为它不会对主机名进行解析。即使主机名为 IP 地址字符串，也不会检查是否与字节数组一致。</dd>
    <dt><code>public static InetAddress getByName(String host) throws UnknownHostException</code></dt>
    <dd>用主机地址构造一个实例，也可以直接传入 IP 地址字符串，等同于 <code>getAllByName(host)[0]</code>。</dd>
    <dt><code>public static InetAddress getLocalHost() throws UnknownHostException</code></dt>
    <dd>返回本机在网络中的地址。</dd>
    <dt><code>public static InetAddress getLoopbackAddress()</code></dt>
    <dd>返回环回地址 <code>127.0.0.1</code>，不抛出异常，等同于 <code>getByName("localhost")</code>（不要和 <code>getLocalHost()</code> 搞混）。环回地址使主机能够自己连接自己，常被用来对在同一台机器上测试网络应用程序。在 IPv4 中，环回地址的网段为 <code>127.0.0.0/8</code>，通常用 <code>127.0.0.1</code>；IPv6 中只有一个 <code>::1</code>。</dd>
    </dl>
    <p>接下来看看 <code>InetAddress</code> 中定义的部分实例方法：</p>
    <dl>
    <dt><code>public byte[] getAddress()</code></dt>
    <dd>返回原始 IP 地址。</dd>
    <dt><code>public String getCanonicalHostName()</code></dt>
    <dd>返回全限定域名。这个方法可以用来探查实际的主机名，例如 <code>InetAddress.getByName("www.google.com").getCanonicalHostName()</code> 返回 <a href="http://we-in-f99.1e100.net">we-in-f99.1e100.net</a>。</dd>
    <dt><code>public String getHostAddress()</code></dt>
    <dd>返回构造时传入的主机地址。</dd>
    <dt><code>public String getHostName()</code></dt>
    <dd>返回主机名。如果构造时传入的主机地址为 IP 地址字符串，则调用 <code>getCanonicalHostName()</code>，否则直接返回构造时传入的主机地址。</dd>
    <dt><code>public boolean isAnyLocalAddress()</code></dt>
    <dd>检查是否为通配符地址。通配符地址为 <code>0.0.0.0</code>（IPv4）或 <code>::0</code>（IPv6），代表所有的本地 IP 地址。例如，假设电脑有两块网卡，各有一个地址，如果想让一个程序同时监听这两个地址，就需要用通配符地址。</dd>
    <dt><code>public boolean isLinkLocalAddress()</code></dt>
    <dd>检查是否为链路本地地址。IPv4 里定义为地址段 <code>169.254.0.0/16</code>，Ipv6 里是以 <code>fe80::/64</code> 为前缀的地址。在电脑没联网的时候查看本机 IP，就能看到这种地址。</dd>
    <dt><code>public boolean isLoopbackAddress()</code></dt>
    <dd>检查是否为环回地址。</dd>
    <dt><code>public boolean isSiteLocalAddress()</code></dt>
    <dd>检查是否为站点本地地址。站点本地地址这个名词实际上已经过时了，现在叫唯一本地地址。IPv4 中未定义；IPv6 中定义为地址段 <code>fc00::/7</code>。这些地址用于私有网络，例如企业内部的局域网。</dd>
    </dl>
    <p>此外还有一些有关多播地址的方法，暂时略过。</p>
    <p>JDK 默认同时支持 IPv4 和 IPv6。如果只想使用一种，可以根据情况将 <code>java.net.preferIPv4Stack</code> 或 <code>java.net.preferIPv6Addresses</code> 这两个系统属性之一设为 <code>true</code>。两个属性的默认值都为 <code>false</code>。一般来说不需要去惊动它们。</p>
    <h2>SocketAddress</h2>
    <p>该类是一个空壳，事实上应用程序使用的是它的唯一子类 <code>InetSocketAddress</code>，目前还看不出这样设计有什么意义。该类只不过在 <code>InetAddress</code> 的基础上增加了一个端口属性。</p>
    <h2>NetworkInterface</h2>
    <p>该类代表网络接口，例如一块网卡。一个网络接口可以绑定一些 IP 地址。具有多个网络接口的主机被称为多宿主主机。下面的代码可打印出所有本机网络接口的信息：</p>
    <pre class="brush: java">
for (NetworkInterface ni : Collections.list(NetworkInterface.getNetworkInterfaces())) {
    System.out.println(ni);
    for (InterfaceAddress ia : ni.getInterfaceAddresses()) {
        System.out.println("\t" + ia);
    }
    System.out.println();
}
    </pre>
    <p>在我的笔记本上运行结果为：</p>
<pre class="output">name:lo (Software Loopback Interface 1)
	/127.0.0.1/8 [/127.255.255.255]
	/0:0:0:0:0:0:0:1/128 [null]
name:net0 (WAN Miniport (SSTP))
name:net1 (WAN Miniport (L2TP))
name:net2 (WAN Miniport (PPTP))
name:ppp0 (WAN Miniport (PPPOE))
name:eth0 (WAN Miniport (IPv6))
name:eth1 (WAN Miniport (Network Monitor))
name:eth2 (WAN Miniport (IP))
name:ppp1 (RAS Async Adapter)
name:net3 (WAN Miniport (IKEv2))
name:net4 (Intel(R) Wireless WiFi Link 4965AGN)
	/fe80:0:0:0:288a:2daf:3549:1811%11/64 [null]
name:eth3 (Broadcom NetXtreme 57xx Gigabit Controller)
	/10.140.1.133/24 [/10.140.1.255]
	/fe80:0:0:0:78c7:e420:1739:f947%12/64 [null]
name:net5 (Teredo Tunneling Pseudo-Interface)
	/fe80:0:0:0:e0:0:0:0%13/64 [null]
name:net6 (Bluetooth Device (RFCOMM Protocol TDI))
name:eth4 (Bluetooth Device (Personal Area Network))
name:eth5 (Cisco AnyConnect VPN Virtual Miniport Adapter for Windows x64)
name:net7 (Microsoft ISATAP Adapter)
	/fe80:0:0:0:0:5efe:a8c:185%17/128 [null]
name:net8 (Microsoft ISATAP Adapter #2)
name:net9 (Intel(R) Wireless WiFi Link 4965AGN-QoS Packet Scheduler-0000)
name:eth6 (Broadcom NetXtreme 57xx Gigabit Controller-TM NDIS Sample LightWeight Filter-0000)
name:eth7 (Broadcom NetXtreme 57xx Gigabit Controller-QoS Packet Scheduler-0000)
name:eth8 (Broadcom NetXtreme 57xx Gigabit Controller-WFP LightWeight Filter-0000)
name:eth9 (WAN Miniport (Network Monitor)-QoS Packet Scheduler-0000)
name:eth10 (WAN Miniport (IP)-QoS Packet Scheduler-0000)
name:eth11 (WAN Miniport (IPv6)-QoS Packet Scheduler-0000)
name:net10 (Intel(R) Wireless WiFi Link 4965AGN-Native WiFi Filter Driver-0000)
name:net11 (Intel(R) Wireless WiFi Link 4965AGN-TM NDIS Sample LightWeight Filter-0000)
name:net12 (Intel(R) Wireless WiFi Link 4965AGN-WFP LightWeight Filter-0000)</pre>
</div><img src ="http://www.blogjava.net/shinzey/aggbug/367593.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/shinzey/" target="_blank">蜀山兆孨龘</a> 2011-12-30 17:39 <a href="http://www.blogjava.net/shinzey/archive/2011/12/30/367593.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>非主流并发工具之 Exchanger</title><link>http://www.blogjava.net/shinzey/archive/2011/12/27/367313.html</link><dc:creator>蜀山兆孨龘</dc:creator><author>蜀山兆孨龘</author><pubDate>Tue, 27 Dec 2011 02:50:00 GMT</pubDate><guid>http://www.blogjava.net/shinzey/archive/2011/12/27/367313.html</guid><wfw:comment>http://www.blogjava.net/shinzey/comments/367313.html</wfw:comment><comments>http://www.blogjava.net/shinzey/archive/2011/12/27/367313.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/shinzey/comments/commentRss/367313.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/shinzey/services/trackbacks/367313.html</trackback:ping><description><![CDATA[<div class="article">
    <p><code>Exchanger</code> 用来让两个线程互相等待并交换计算结果。这个类的用法很简单，因为它就定义了两个重载的 <code>exchange</code> 方法，参数多的那个无非增加了对超时的支持。当一个线程调用 <code>exchange</code> 的时候（以计算结果作为参数），它就开始等待另一个线程调用 <code>exchange</code>，然后两个线程分别收到对方调用 <code>exchange</code> 时传入的参数，从而完成了计算结果的交换。</p>
    <p>不用太多的解释，运行下面这个例子就一清二楚：</p>
    <pre class="brush: java; highlight: [12, 28]">
final Exchanger&lt;String&gt; e = new Exchanger&lt;&gt;();

new Thread() {
    @Override
    public void run() {
        long id = Thread.currentThread().getId();
        String s = "abc";
        System.out.println("线程 [" + id + "] 算出 " + s);

        try {
            TimeUnit.SECONDS.sleep(new Random().nextInt(5));
            System.out.println("线程 [" + id + "] 收到 " + e.exchange(s));
        } catch (InterruptedException ex) {
            ex.printStackTrace();
        }
    }
}.start();

new Thread() {
    @Override
    public void run() {
        long id = Thread.currentThread().getId();
        String s = "xyz";
        System.out.println("线程 [" + id + "] 算出 " + s);

        try {
            TimeUnit.SECONDS.sleep(new Random().nextInt(5));
            System.out.println("线程 [" + id + "] 收到 " + e.exchange(s));
        } catch (InterruptedException ex) {
            ex.printStackTrace();
        }
    }
}.start();
    </pre>
    <p>运行结果（可能为）：</p>
    <pre class="output">线程 [9] 算出 abc
线程 [10] 算出 xyz
线程 [10] 收到 abc
线程 [9] 收到 xyz</pre>
    <p>最后强调下，该类只适用于两个线程，妄图用它来处理多个生产者和消费者之间的数据交换是注定要失败的……</p>
</div><img src ="http://www.blogjava.net/shinzey/aggbug/367313.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/shinzey/" target="_blank">蜀山兆孨龘</a> 2011-12-27 10:50 <a href="http://www.blogjava.net/shinzey/archive/2011/12/27/367313.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>基于 NIO2 的 ZIP 文件系统</title><link>http://www.blogjava.net/shinzey/archive/2011/12/01/365280.html</link><dc:creator>蜀山兆孨龘</dc:creator><author>蜀山兆孨龘</author><pubDate>Thu, 01 Dec 2011 05:12:00 GMT</pubDate><guid>http://www.blogjava.net/shinzey/archive/2011/12/01/365280.html</guid><wfw:comment>http://www.blogjava.net/shinzey/comments/365280.html</wfw:comment><comments>http://www.blogjava.net/shinzey/archive/2011/12/01/365280.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://www.blogjava.net/shinzey/comments/commentRss/365280.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/shinzey/services/trackbacks/365280.html</trackback:ping><description><![CDATA[<div class="article">
    <p>以前我曾用两个类（<a href="http://zhyi.googlecode.com/svn/trunk/zse/src/main/java/zhyi/zse/zip/ZipItem.java"><code>ZipItem</code></a> 和 <a href="http://zhyi.googlecode.com/svn/trunk/zse/src/main/java/zhyi/zse/zip/ZipSystem.java"><code>ZipSystem</code></a>）实现了一个简单的 ZIP 文件系统（以下简称 ZFS）。其实这两个小类挺好用的，而且支持嵌套的 ZIP 文件，但是，但是……JDK 7 丢下来一枚叫做 NIO2 的笑气炸弹，引入了一套标准的文件系统 API，我承认我中弹了，手痒了，又根据这套 API 重新实现了 ZIP 文件系统，终于在今天初步完工，哈。</p>
    <p>话说，JDK 7 其实捆绑销售了一个 ZFS，demo 目录下还有源代码。可……它达不到我的奢求，而且 BUG 不少。随便逮两个：</p>
    <pre class="brush: java; highlight: [11, 12, 13, 20, 21]">
        // com.sun.nio.zipfs.ZipFileSystemProvider 类中的方法
        @Override
        public Path getPath(URI uri) {

            String spec = uri.getSchemeSpecificPart();
            int sep = spec.indexOf("!/");
            if (sep == -1)
                throw new IllegalArgumentException("URI: "
                    + uri
                    + " does not contain path info ex. jar:file:/c:/foo.zip!/BAR");
            // 难怪该方法始终抛 IllegalArgumentException 异常，原来你小子把文件的 URI
            // 当成 ZFS 的 URI 在用……
            return getFileSystem(uri).getPath(spec.substring(sep + 1));
        }

        // com.sun.nio.zipfs.ZipFileSystem 类中的方法
        @Override
        public PathMatcher getPathMatcher(String syntaxAndInput) {
            int pos = syntaxAndInput.indexOf(':');
            // 丫的，pos == syntaxAndInput.length()？！谁写的？抓出来鞭尸。
            if (pos <= 0 || pos == syntaxAndInput.length()) {
                throw new IllegalArgumentException();
    </pre>
    <p>很明显，官方 ZFS 没有经过代码审阅、没有经过测试、没有经过……然后，<code>@author Xueming Shen</code>，真是丢咱华夏民族的脸……</p>
    <p>下面列个表格详细比较官方 ZFS 和山寨 ZFS：</p>
    <table border = "1">
        <tr>
            <td align="center"><b>比较内容</b></td>
            <td align="center"><b>官方 ZFS</b></td>
            <td align="center"><b>山寨 ZFS</b></td>
        </tr>
        <tr>
            <td>实现方式</td>
            <td>另起炉灶，用纯 Java 重新实现了对 ZIP 文件格式的处理代码。</td>
            <td>基于 <code>ZipFile</code> 和 <code>ZipInputStream</code> 这两个已经稳定多年的类，但涉及了大量本地代码调用，也许会影响性能。</td>
        </tr>
        <tr>
            <td>读操作</td>
            <td>支持，且通过解压到临时文件支持随机访问。</td>
            <td>支持，但不支持随机访问。</td>
        </tr>
        <tr>
            <td>写操作</td>
            <td>通过解压到临时文件进行支持，但无法检测到其他进程对同一个 ZIP 文件的写操作，不适用于并发环境。</td>
            <td>不支持。ZIP 文件事实上是一个整体，对内部条目的任何修改都可能导致重构整个文件，因此所谓的写操作必须通过临时文件来处理，效率低下，意义不大，而且难以处理嵌套 ZIP 文件。这也符合我的原则：不解压。</td>
        </tr>
        <tr>
            <td>嵌套 ZIP 文件</td>
            <td>不支持。</td>
            <td>支持，当然读取嵌套 ZIP 文件会慢一些。</td>
        </tr>
        <tr>
            <td>反斜线分隔符</td>
            <td>不支持，直接瓜掉。</td>
            <td>支持，且和标准的斜线分隔符区别对待。例如，<code>/abc/</code> 和 <code>/abc\</code> 算不同的文件，实际上这两个能够并存于 ZIP 文件中。</td>
        </tr>
        <tr>
            <td>空目录名</td>
            <td>不支持，直接瓜掉。</td>
            <td>支持。例如 <code>/a/b</code> 和 <code>/a//b</code> 是两个可以并存且不同的文件。</td>
        </tr>
    </table>
    <p>山寨 ZFS 的用法示例：</p>
    <pre class="brush: java">
        Map&lt;String, Object&gt; env = new HashMap&lt;&gt;();
        // 用于解码 ZIP 条目名。默认为 Charset.defaultCharset()。
        env.put("charset", StandardCharsets.UTF_8);
        // 指示是否自动探测嵌套的 ZIP 文件。默认为 false。
        env.put("autoDetect", true);
        // 默认目录，用于创建和解析相对路径。默认为“/”。
        env.put("defaultDirectory", "/dir/");

        // 从文件创建一个 ZFS。
        try (FileSystem zfs = FileSystems.newFileSystem(
                URI.create("zip:" + Paths.get("docs.zip").toUri()), env)) {
            Path path = zfs.getPath("app.jar");
            if ((Boolean) Files.getAttribute(path, "isZip")) {
                // 创建一个嵌套的 ZFS。
                try (FileSystem nestedZfs = zfs.provider().newFileSystem(path, env)) {
                    // 此处省略若干行。
                }
            }
        }
    </pre>
    <p>最后双手奉上源代码：<a href="https://github.com/zhyi/zse/tree/master/src/main/java/zhyi/zse/io/zip/zfs">请猛击此处！</a></p>
</div><img src ="http://www.blogjava.net/shinzey/aggbug/365280.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/shinzey/" target="_blank">蜀山兆孨龘</a> 2011-12-01 13:12 <a href="http://www.blogjava.net/shinzey/archive/2011/12/01/365280.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>钻石运算符的一个 BUG</title><link>http://www.blogjava.net/shinzey/archive/2011/11/11/363493.html</link><dc:creator>蜀山兆孨龘</dc:creator><author>蜀山兆孨龘</author><pubDate>Fri, 11 Nov 2011 03:06:00 GMT</pubDate><guid>http://www.blogjava.net/shinzey/archive/2011/11/11/363493.html</guid><wfw:comment>http://www.blogjava.net/shinzey/comments/363493.html</wfw:comment><comments>http://www.blogjava.net/shinzey/archive/2011/11/11/363493.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/shinzey/comments/commentRss/363493.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/shinzey/services/trackbacks/363493.html</trackback:ping><description><![CDATA[<div class="article">
    <p>先看出错的代码：</p>
    <pre class="brush: java">
        public class Holder&lt;T&gt; {
            private T value;

            public Holder() {
            }

            public Holder(T value) {
                this.value = value;
            }

            public void setValue(T value) {
                this.value = value;
            }

            // 此处省略若干行。
        }

        Holder&lt;Object&gt; holder = new Holder&lt;&gt;("xxx");
    </pre>
    <p>看起来还好，但编译的时候却报错：</p>
    <pre class="output">Uncompilable source code - incompatible types
  required: zhyi.test.Holder&lt;java.lang.Object&gt;
  found:    zhyi.test.Holder&lt;java.lang.String&gt;</pre>
    <p>老老实实把类型写出来就没问题：</p>
    <pre class="brush: java">
        Holder&lt;Object&gt; holder = new Holder&lt;Object&gt;("xxx");
    </pre>
    <p>如果非要用钻石运算符的话，可以采取下列两种方式之一：</p>
    <pre class="brush: java">
        // 使用默认构造器，再调用 setValue 方法。
        Holder&lt;Object&gt; holder = new Holder&lt;&gt;();
        holder.setValue("xxx");

        // 使用泛型通配符，但之后就不能调用 setValue 了，否则编译出错。
        Holder&lt;? extends Object&gt; holder = new Holder&lt;&gt;("xxx");
    </pre>
</div><img src ="http://www.blogjava.net/shinzey/aggbug/363493.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/shinzey/" target="_blank">蜀山兆孨龘</a> 2011-11-11 11:06 <a href="http://www.blogjava.net/shinzey/archive/2011/11/11/363493.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>非主流并发工具之 CyclicBarrier</title><link>http://www.blogjava.net/shinzey/archive/2011/10/17/361408.html</link><dc:creator>蜀山兆孨龘</dc:creator><author>蜀山兆孨龘</author><pubDate>Mon, 17 Oct 2011 03:21:00 GMT</pubDate><guid>http://www.blogjava.net/shinzey/archive/2011/10/17/361408.html</guid><wfw:comment>http://www.blogjava.net/shinzey/comments/361408.html</wfw:comment><comments>http://www.blogjava.net/shinzey/archive/2011/10/17/361408.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/shinzey/comments/commentRss/361408.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/shinzey/services/trackbacks/361408.html</trackback:ping><description><![CDATA[<div class="article">
    <p><code>CyclicBarrier</code> 的功能类似于前面说到的 <code>CountDownLatch</code>，用于让多个线程（子任务）互相等待，直到共同到达公共屏障点（common barrier point），在这个点上，所有的子任务都已完成，从而主任务完成。</p>
    <p>该类构造的时候除了必须要指定线程数量，还可以传入一个 <code>Runnable</code> 对象，它的 <code>run</code> 方法将在到达公共屏障点后执行一次。子线程完成计算后，分别调用 <code>CyclicBarrier#await</code> 方法进入阻塞状态，直到其他所有子线程都调用了 <code>await</code>。</p>
    <p>下面仍然以运动员准备赛跑为例来说明 <code>CyclicBarrier</code> 的用法：</p>
    <pre class="brush: java; highlight: [4, 5, 6, 7, 8, 9, 24]">
            final int count = 8;
            System.out.println("运动员开始就位。");

            final CyclicBarrier cb = new CyclicBarrier(count, new Runnable() {
                @Override
                public void run() {
                    System.out.println("比赛开始...");
                }
            });

            for (int i = 1; i <= count; i++) {
                final int number = i;
                new Thread() {
                    @Override
                    public void run() {
                        System.out.println(number + " 号运动员到场并开始准备...");
                        try {
                            // 准备 2~5 秒钟。
                            TimeUnit.SECONDS.sleep(new Random().nextInt(4) + 2);
                        } catch (InterruptedException ex) {
                        }
                        System.out.println(number + " 号运动员就位。");
                        try {
                            cb.await();
                        } catch (InterruptedException | BrokenBarrierException ex) {
                        }
                    }
                }.start();
            }
            System.out.println("等待所有运动员就位...");
    </pre>
    <p>运行输出（可能）为：</p>
    <pre class="output">运动员开始就位。
1 号运动员到场并开始准备...
2 号运动员到场并开始准备...
等待所有运动员就位...
3 号运动员到场并开始准备...
4 号运动员到场并开始准备...
6 号运动员到场并开始准备...
8 号运动员到场并开始准备...
5 号运动员到场并开始准备...
7 号运动员到场并开始准备...
1 号运动员就位。
3 号运动员就位。
8 号运动员就位。
6 号运动员就位。
2 号运动员就位。
7 号运动员就位。
5 号运动员就位。
4 号运动员就位。
比赛开始...</pre>
    <p>最后看看 <code>CyclicBarrier</code> 和 <code>CountDownLatch</code> 的主要异同：</p>
    <ol>
        <li>两者在构造的时候都必须指定线程数量，而且该数量在构造后不可修改。</li>
        <li>前者可以传入一个 <code>Runnable</code> 对象，在任务完成后自动调用，执行者为某个子线程；后者可在 <code>await</code> 方法后手动执行一段代码实现相同的功能，但执行者为主线程。</li>
        <li>前者在每个子线程上都进行阻塞，然后一起放行；后者仅在主线程上阻塞一次。</li>
        <li>前者可以重复使用；后者的倒计数器归零后就作废了。</li>
        <li>两者的内部实现完全不同。</li>
    <ol>
</div><img src ="http://www.blogjava.net/shinzey/aggbug/361408.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/shinzey/" target="_blank">蜀山兆孨龘</a> 2011-10-17 11:21 <a href="http://www.blogjava.net/shinzey/archive/2011/10/17/361408.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>非主流并发工具之 CountDownLatch</title><link>http://www.blogjava.net/shinzey/archive/2011/10/14/361277.html</link><dc:creator>蜀山兆孨龘</dc:creator><author>蜀山兆孨龘</author><pubDate>Fri, 14 Oct 2011 06:22:00 GMT</pubDate><guid>http://www.blogjava.net/shinzey/archive/2011/10/14/361277.html</guid><wfw:comment>http://www.blogjava.net/shinzey/comments/361277.html</wfw:comment><comments>http://www.blogjava.net/shinzey/archive/2011/10/14/361277.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://www.blogjava.net/shinzey/comments/commentRss/361277.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/shinzey/services/trackbacks/361277.html</trackback:ping><description><![CDATA[<div class="article">
    <p>顾名思义，<code>CountDownLatch</code> 是一个用来倒计数的咚咚。如果某项任务可以拆分成若干个子任务同时进行，然后等待所有的子任务完成，可以考虑使用它。</p>
    <p>该类的用法非常简单。首先构造一个 <code>CountDownLatch</code>，唯一的参数是任务数量，一旦构造完毕就不能修改。接着启动所有的子任务（线程），且每个子任务在完成自己的计算后，调用 <code>CountDownLatch#countDown</code> 方法将倒计数减一。最后在主线程中调用 <code>CountDownLatch#await</code> 方法等待计数器归零。</p>
    <p>例如赛跑的准备阶段，八名运动员先后到达起点做好准备，然后裁判打响发令枪，准备工作就结束了，比赛开始。如果把从运动员就位到发令枪响看做赛跑准备任务，那么每个运动员的准备过程就是其子任务，可以用 <code>CountDownLatch</code> 模拟如下：</p>
    <pre class="brush: java; highlight: [5, 19, 27]">
        final int count = 8;
        System.out.println("运动员开始就位。");

        // 构造 CountDownLatch。
        final CountDownLatch cdl = new CountDownLatch(count);
        for (int i = 1; i <= count; i++) {
            final int number = i;
            new Thread() {
                @Override
                public void run() {
                    System.out.println(number + " 号运动员到场并开始准备...");
                    try {
                        // 让运动员随机准备 2~5 秒钟。
                        TimeUnit.SECONDS.sleep(new Random().nextInt(4) + 2);
                    } catch (InterruptedException ex) {
                    }
                    System.out.println(number + " 号运动员就位。");
                    // 倒计数减一。
                    cdl.countDown();
                }
            }.start();
        }

        System.out.println("等待所有运动员就位...");
        try {
            // 等待倒计数变为 0。
            cdl.await();
            System.out.println("比赛开始。");
        } catch (InterruptedException ex) {
        }
    </pre>
    <p>运行输出（可能）为：</p>
    <pre class="output">运动员开始就位。
1 号运动员到场并开始准备...
2 号运动员到场并开始准备...
4 号运动员到场并开始准备...
等待所有运动员就位...
8 号运动员到场并开始准备...
6 号运动员到场并开始准备...
3 号运动员到场并开始准备...
7 号运动员到场并开始准备...
5 号运动员到场并开始准备...
6 号运动员就位。
1 号运动员就位。
5 号运动员就位。
4 号运动员就位。
7 号运动员就位。
8 号运动员就位。
2 号运动员就位。
3 号运动员就位。
比赛开始。</pre>
    <p>从上面的例子还可以看出 <code>CountDownLatch</code> 的局限性和 <code>CompletionService</code> 类似，在于无法处理子任务数量不确定的情况，例如统计某个文件夹中的文件数量。另外，如果某个子任务在调用 <code>countDown</code> 之前就挂掉了，倒计数就永远不会归零。对于这种情况，要么用 <code>finally</code> 之类的手段保证 <code>countDown</code> 一定会被调用，要么用带参数的 <code>await</code> 方法指定超时时间。</p>
</div>
<img src ="http://www.blogjava.net/shinzey/aggbug/361277.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/shinzey/" target="_blank">蜀山兆孨龘</a> 2011-10-14 14:22 <a href="http://www.blogjava.net/shinzey/archive/2011/10/14/361277.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>非主流并发工具之 CompletionService</title><link>http://www.blogjava.net/shinzey/archive/2011/09/29/359781.html</link><dc:creator>蜀山兆孨龘</dc:creator><author>蜀山兆孨龘</author><pubDate>Thu, 29 Sep 2011 05:37:00 GMT</pubDate><guid>http://www.blogjava.net/shinzey/archive/2011/09/29/359781.html</guid><wfw:comment>http://www.blogjava.net/shinzey/comments/359781.html</wfw:comment><comments>http://www.blogjava.net/shinzey/archive/2011/09/29/359781.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/shinzey/comments/commentRss/359781.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/shinzey/services/trackbacks/359781.html</trackback:ping><description><![CDATA[<div class="article">
    <p><code>CompletionService</code> 接口的实例可以充当生产者和消费者的中间处理引擎，从而达到将提交任务和处理结果的代码进行解耦的目的。生产者调用 <code>submit</code> 方法提交任务，而消费者调用 <code>poll</code>（非阻塞）或 <code>take</code>（阻塞）方法获取下一个结果：这一特征看起来和阻塞队列（<code>BlockingQueue</code>）类似，两者的区别在于 <code>CompletionService</code> 要负责任务的处理，而阻塞队列则不会。</p>
    <p>在 JDK 中，该接口只有一个实现类 <code>ExecutorCompletionService</code>，该类使用创建时提供的 <code>Executor</code> 对象（通常是线程池）来执行任务，然后将结果放入一个阻塞队列中：果然本就是一家亲啊！<code>ExecutorCompletionService</code> 将线程池和阻塞队列糅合在一起，仅仅通过三个方法，就实现了任务的异步处理，可谓并发编程初学者的神兵利器！</p>
    <p>接下来看一个例子。楼主有一大堆 *.java 文件，需要计算它们的代码总行数。利用 <code>ExecutorCompletionService</code> 可以写出很简单的多线程处理代码：</p>
    <pre class="brush: java; highlight: [7, 11, 25]">
        public int countLines(List&lt;Path&gt; javaFiles) throws Exception {
            // 根据处理器数量创建线程池。虽然多线程并不保证能够提升性能，但适量地
            // 开线程一般可以从系统骗取更多资源。
            ExecutorService es = Executors.newFixedThreadPool(
                    Runtime.getRuntime().availableProcessors() * 2);
            // 使用 ExecutorCompletionService 内建的阻塞队列。
            CompletionService cs = new ExecutorCompletionService(es);

            // 按文件向 CompletionService 提交任务。
            for (final Path javaFile : javaFiles) {
                cs.submit(new Callable&lt;Integer&gt;() {
                    @Override
                    public Integer call() throws Exception {
                        // 略去计算单个文件行数的代码。
                        return countLines(javaFile);
                    }
                });
            }

            try {
                int loc = 0;
                int size = javaFiles.size();
                for (int i = 0; i < size; i++) {
                    // take 方法等待下一个结果并返回 Future 对象。不直接返回计算结果是为了
                    // 捕获计算时可能抛出的异常。
                    // poll 不等待，有结果就返回一个 Future 对象，否则返回 null。
                    loc += cs.take().get();
                }
                return loc;
            } finally {
                // 关闭线程池。也可以将线程池提升为字段以便重用。
                // 如果任务线程（Callable#call）能响应中断，用 shutdownNow 更好。
                es.shutdown();
            }
        }
    </pre>
    <p>最后，<code>CompletionService</code> 也不是到处都能用，它不适合处理任务数量有限但个数不可知的场景。例如，要统计某个文件夹中的文件个数，在遍历子文件夹的时候也会“递归地”提交新的任务，但最后到底提交了多少，以及在什么时候提交完了所有任务，都是未知数，无论 <code>CompletionService</code> 还是线程池都无法进行判断。这种情况只能直接用线程池来处理。</p>
</div><img src ="http://www.blogjava.net/shinzey/aggbug/359781.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/shinzey/" target="_blank">蜀山兆孨龘</a> 2011-09-29 13:37 <a href="http://www.blogjava.net/shinzey/archive/2011/09/29/359781.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>