21 世纪初,Spring 框架的诞生和崛v让沉重而腐朽的 J2EE 遭到了当头棒喝,随后大批开发h员{?Spring 阵营Q呼吔Rp J2EE 阵营大伤元气。然而这U命悬一U的危机q没有造成毁灭性的打击Q尤其是对于 Java q种提倡开攄q_而言Q取长补短,互相促进才是正道。于是,JCP 委员会痛定思痛Q在 2006 q推?Java EE 5 规范Q主要是?EJB 的开发进行了极大q度的简化?008 q发布的 Java EE 6 引入?CDI、BV、JAX-RS {一pd新功能,q且以配|文ӞprofileQ的方式?Java EE 向轻量q进了一步。特别有的是,Spring 框架也开始提供对某些 Java EE 注解的支持,是否标志着两大阵营开始合?Java EE 7 预定于今q下半年发布Q目标是支持云计。最q几q来Q云计算一直被炒作Q却从来没有一个准的定义和规范,希望 Java EE 7 能够?Java 界扭转这U尴的局面?/p>
下面开始详l列?Java EE 7 的新功能前瞻Q数据来源于《Java Magazine 2012-01/02》中的《Cloud/Java EE: Looking Ahead to Java EE 7》一文。Java EE 7 是以“日期驱动”的方式开发的Q也是_在计划日期到辑։没有完成的功能都被推迟?Java EE 8?/p>
@Inject
更紧密集成?/li>
ELContext
分离析和求g下文?/li>
cc:interface
可选,Facelet 标记库的速记 URLQ与 CDI 集成QJSF lg?OSGi 支持?/li>
fileUpload
?BackButton
{新lg?/li>
ForkJoinPool
?Java SE 7 新功能“分?l合框架”的核心c,现在可能乏h问|Q但我觉得它q早会成Z。分?l合框架是一个比较特D的U程池框Ӟ专用于需要将一个Q务不断分解成子Q务(分叉Q,再不断进行汇d到最l结果(l合Q的计算q程。比起传l的U程池类 ThreadPoolExecutor
Q?code>ForkJoinPool 实现了工作窃取算法,使得I闲U程能够d分担从别的线E分解出来的子Q务,从而让所有的U程都尽可能处于饱满的工作状态,提高执行效率?/p>
ForkJoinPool
提供了三cL法来调度子Q务:
execute
pdinvoke
?invokeAll
submit
pdFuture
对象?/dd>
子Q务由 ForkJoinTask
的实例来代表。它是一个抽象类QJDK 为我们提供了两个实现Q?code>RecursiveTask ?RecursiveAction
Q分别用于需要和不需要返回计结果的子Q务?code>ForkJoinTask 提供了三个静态的 invokeAll
Ҏ来调度子dQ注意只能在 ForkJoinPool
执行计算的过E中调用它们?/p>
ForkJoinPool
?ForkJoinTask
q提供了很多让hD~ؕ的公共方法,其实它们大多数都是其内部实现去调用的Q对于应用开发h员来说意义不大?/p>
下面以统?D 盘文件个Cؓ例。这实际上是对一个文件树的遍历,我们需要递归地统计每个目录下的文件数量,最后汇总,非常适合用分?l合框架来处理:
// 处理单个目录的Q? public class CountingTask extends RecursiveTask<Integer> { private Path dir; public CountingTask(Path dir) { this.dir = dir; } @Override protected Integer compute() { int count = 0; List<CountingTask> subTasks = new ArrayList<>(); // d目录 dir 的子路径? try (DirectoryStream<Path> ds = Files.newDirectoryStream(dir)) { for (Path subPath : ds) { if (Files.isDirectory(subPath, LinkOption.NOFOLLOW_LINKS)) { // Ҏ个子目录都新Z个子d? subTasks.add(new CountingTask(subPath)); } else { // 遇到文gQ则计数器增?1? count++; } } if (!subTasks.isEmpty()) { // 在当前的 ForkJoinPool 上调度所有的子Q务? for (CountingTask subTask : invokeAll(subTasks)) { count += subTask.join(); } } } catch (IOException ex) { return 0; } return count; } } // 用一?ForkJoinPool 实例调度“MQ务”,然后敬请期待l果…? Integer count = new ForkJoinPool().invoke(new CountingTask(Paths.get("D:/")));
在我的笔记本上,l多ơ运行这D代码,耗费的时间稳定在 600 豪秒左右。普通线E池Q?code>Executors.newCachedThreadPool()Q耗时 1100 毫秒左右Q见工作窃取的优势?/p>
l束本文前,我们来围观一个最奇的结果:单线E算法(使用 Files.walkFileTree(...)
Q比q两个都快,q_耗时 550 毫秒Q这警告我们q引入多线E就能优化性能Qƈ要先经q多ơ测试才能下l论?/p>
前面已经看到Q?code>Socket cȝ getInputStream()
?getOutStream()
Ҏ分别获取套接字的输入和输出。输入流用来dq端发送过来的数据Q输出流则用来向q端发送数据?/p>
使用套接字的输入读取数据时Q当前线E会q入d状态,直到套接字收C些数据ؓ止(亦即套接字的接收~冲区有可用数据Q。该输入的 available()
Ҏ只是q回接收~冲区的可用字节数量Q不可能知道q端q要发送多字节。用输入流的时候,最好先它包装Z?BufferedInputStream
Q因取接收缓冲区导?JVM 和底层系l之间的切换Q应当尽量减切换次C提高性能?code>BufferedInputStream 的缓冲区大小最好设为套接字接收~冲区的大小?/p>
如果直接调用输入的 close()
Ҏ来关闭它Q则导致套接字被关闭。对此,Socket
cL供了一?shutdownInput()
Ҏ来禁用输入流。调用该Ҏ后,每次L作都返?EOF
Q无法再dq端发送的数据。对q个 EOF
的检,不同的输入流包装体现Z同的l果Q可能读?-1 个字节,可能d的字W串?null
Q还可能收到一?EOFException
{等。禁用输入流后,q端输出的行ؓ是^台相关的Q?/p>
用输入这U技术ƈ不常用?/p>
套接字的输出操作实际上仅仅将数据写到发送缓冲区内,当发送缓冲区填满且上ơ的发送成功后Q由底层pȝ负责发送。如果发送缓冲区的剩余空间不够,当前U程׃d。和输入类|最好将输出包装ؓ BufferedOutputStream
?/p>
如果套接字的双发都?ObjectInputStream
?ObjectOutputStream
来读?Java 对象Q则必须先创?ObjectOutputStream
Q因?ObjectInputStream
在构造的时候会试图d对象头部Q如果双发都先创?ObjectInputStream
Q则会互相等待对方的输出Q造成死锁Q?/p>
// 创徏的顺序不能颠倒! ObjectOutputStream out = new ObjectOutputStream(socket.getOutputStream()); ObjectInputStream in = new ObjectInputStream(socket.getInputStream());
cM于输入流Q关闭输出流也导致关闭套接字Q所?Socket
cdh供了一?shutdownOutput()
来禁用输出流。禁用输出流后,已写入发送缓冲区的数据会正常发送,之后的Q何写操作都会D IOException
Q且q端的输入流始终会读?EOF
。禁用输出流非常有用Q例如套接字的双发都在发送完毕数据后用输入,然后双方都会收到 EOF
Q从而知道数据已l全部交换完毕,可以安全关闭套接字。直接关闭套接字会同时关闭输入流和输出流Q且断开q接Q达不到q种效果?/p>
如果要用流q行输入和输出,只能用d模式的套接字。这里ȝ一下阻塞套接字的优~点。先看看优点Q?/p>
但在性能斚w有致命的~点Q?/p>
下一文章开始探讨用基?NIO 的套接字通道和缓冲区实现伸羃性更强的 TCP 套接字?/p>
ServerSocket
cd Socket
c都提供了多个公共构造方法。不同的构造方法不仅带的参C同,所h的意义也不一栗下面分别解析这两个cȝ实例初始化过E?/p>
ServerSocket
实例的初始化ServerSocket
cL供了四个构造器Q?/p>
public ServerSocket(int port) throws IOException
public ServerSocket(int port, int backlog) throws IOException
public ServerSocket(int port, int backlog, InetAddress bindAddr) throws IOException
public ServerSocket() throws IOException
带参构造器用来创徏已绑定的服务器套接字Q也是说构造成功后它就已经开始侦听指定的端口Q且能够调用 accept()
Ҏ来接受客Lq接。默认构造器则会创徏未绑定的服务器套接字Q构造成功后必须手动其l定C个本地地址才能用,在绑定之前可以进行一些选项配置?/p>
ȝ来说Q带参构造器提供了三个参敎ͼ
port
backlog
accept()
ҎQ,它就会被从队列中U除?code>backlog 参数q于指定队列的最大长度,默认gؓ 50Q但q个值只是一个徏议,底层pȝ可能Ҏ需要自动调整。如果队列满了,则其行ؓ是^台相关的Q微软的 WINSOCK 会拒l新的连接,其他实现则什么都不做。严格地_微Y没有遵守规范Q破坏了游戏规则…?/dd>
bindAddr
null
Q服务器套接字会在所有的本地 IP 地址Q?code>0.0.0.0 ?::0
Q上侦听。如果希望只侦听一个地址Q则可用该参数?/dd>
如果使用默认构造器Q在l定地址前,q可以做些配|。绑定操作由两个 bind
Ҏ定义Q参数类g带参构造器。配|项包括以下斚wQ都必须在绑定前配置Q:
setReuseAddress(boolean on)
Ҏ配置Q对应底层系l的 SO_REUSEADDR
套接字选项。JDK 没有定义该选项的默认倹{如果该选项?false
Q则在关?TCP q接ӞZ保证可靠性,该连接可能在关闭后的一D|_大约两分钟)内保持超时状态(通常UCؓ TIME_WAIT
状态或 2MSL
{待状态)Q这D|间里无法新建的服务器套接字l定到同一个地址。在开发阶D,服务器可能不断重启,打开攚w项会非常有用?/dd>
setReceiveBufferSize(int size)
Ҏ配置Q对应底层系l的 SO_RCVBUF
套接字选项Q单位是字节。《RFC 1323 - TCP Extensions for High Performance》将~冲区大定义ؓ 64KB。该选项只是一个徏议|底层pȝ可能Ҏ需要自行调整?/dd>
setSoTimeout(int timeout)
Ҏ配置Q对应底层系l的 SO_TIMEOUT
套接字选项Q单位是毫秒。默认gؓ 0。该选项影响 accept
Ҏ的阻塞时间长度,如果时引?SocketTimeoutException
。如果设?0Q则表示怸时?/dd>
setPerformancePreferences(int connectionTime, int latency, int bandwidth)
Ҏ配置。这三个数值分别表C短q接旉、低延迟和高带宽的相寚w要性,数D大则重要;其各自的l对值没有意义。该Ҏ的初hZ?Java 能在用非 TCP/IP 实现的套接字环境下工作得更好Q某些需要对|络q行调优的程序也可以这三个首选项作ؓ配置参数提供l用戗?/dd>
Socket
实例的初始化Socket
cL供了六个公共构造器Q已q时的除外)Q?/p>
public Socket(String host, int port) throws UnknownHostException, IOException
public Socket(InetAddress address, int port) throws IOException
public Socket(String host, int port, InetAddress localAddr, int localPort) throws IOException
public Socket(InetAddress address, int port, InetAddress localAddr, int localPort) throws IOException
public Socket()
public Socket(Proxy proxy)
前四个构造器创徏已连接的客户端套接字Q也是说构造的时候就会去q接服务器。前两个构造器需要提供服务器的地址和端口作为参敎ͼ本地地址和端口由pȝ自动分配Q后两个允许手动指定本地地址和端口,但极用。后两个构造器创徏未连接的套接字,创徏后需要调?connect
Ҏ手动q接Q连接之前可以做一些配|。最后一个构造器接受一个代表代理服务其?Proxy
对象QJDK 支持 HTTP ?SOCKSQV4 ?V5Q两U代理类型?/p>
在连接前Q客L套接字不仅像服务器套接字那样可以讄是否重用本地地址、缓冲区大小、超时值和性能首选项Q还能够配置以下各项Q都必须在连接前配置Q:
setKeepAlive(boolean on)
Ҏ配置Q对应底层系l的 SO_KEEPALIVE
套接字选项。默认gؓ false
。如果打开该选项Q则套接字会定期自动发送保持活跃的探测性消息,cM于心x。根据《RFC 1122 - Requirements for Internet Hosts》的规定Q保持活跃机制只?TCP 的一个可选功能,如果支持的话Q默认必Mؓ false
Q而且q种机制默认在成功徏立连接后Q且q箋两小时没有数据传输的情况下才会被ȀzR从另一斚w来看Q通过套接字的 I/O 操作完全可以知道q接是否q有效,所以该选项的实用hg大?/dd>
setOOBInline(boolean on)
Ҏ配置Q对应底层系l的 SO_OOBINLINE
套接字选项。默认gؓ off
。带外数据(Out-of-band DataQ也叫做紧急数据,表示数据很重要,需要用不同于发送普通数据的一个专用通道来发送。打开该选项后,可以调?sendUrgentData(int data)
Ҏ发送一个字节的紧急数据。JDK 对带外数据只提供了有限支持,紧急数据将会和普通数据一赯收到Qƈ且无法自动区分。该选项对应用开发h员意义不大?/dl>
setSoLinger(boolean on, int linger)
Ҏ配置Q对应底层系l的 SO_LINGER
套接字选项。默认ؓ false
。该选项只会影响套接字的关闭Q其中的 linger
参数表示时旉Q单位ؓU。如果打开攚w项Q如果将 linger
设ؓ 0Q则关闭套接字的时候,未发送的数据会被丢弃Q且另一端会出现q接被同位体重置的异常;如果 linger
?0Q则关闭套接字的U程被dQ直到数据全部发送或时Q超时后的行Z底层pȝ相关QJDK 无法控制。如果关闭该选项Q则套接字正常关闭,数据也会全部发送。由于底层实现的差异性,不提倡应用开发h员打开该选项?/dd>
setTcpNoDelay(boolean on)
Ҏ配置Q对应底层系l的 TCP_NODELAY
TCP 选项。默认gؓ off
。打开该选项禁?Nagle 法QTCP 包会立即发送;关闭该选项则会启用 Nagle 法Q多个较的 TCP 包会被组合成一个大包一起发送,虽然发送gq了Q但有利于避免网l拥塞。默认ؓ false
。该选项对实时性很强的E序可能有用Q但一般的E序不需要关心?/dd>
setTrafficClass(int tc)
Ҏ配置Q对应底层系l的“流量类别”套接字属性。该选项用于向网l(例如路由器)提示从该套接字发送的包需要获取哪些服务类型,Ҏ?TCP 协议栈没有媄响。IPv4 ?IPv6 分别定义了多个不同的|例如 IPv4 ?0x08
定义为最大吞吐量Q?code>0x10 定义为最gq,{等。可以用或运将多个值合qؓ一个选项。该选项用来调整性能Q需要根据实际情况设|。由于只是徏议|可能被网l忽略?/dd>
|上很多关于单例模式写法的文章,不外乎饿汉和懒汉两种形式的讨论。很多h喜欢用懒汉式Q因得它实现了gq加载,可以让系l的性能更好。但事实果真如此吗?我对此存疑?/p>
首先我们查一下饿汉和懒汉单例模式最单的写法Q这里不讨论哪种懒汉写法更好Q:
// 饿汉 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; } }
从理Z来说Q?code>HungrySingleton 的单例在该类W一ơ用的时候创建,?LazySingleton
的单例则在其 getInstance()
Ҏ被调用的时候创建。至于网上有人声U“饿汉式不管用不用都会初始化”,U属走\的时候步子迈得太大。谁的加载更q?如果你只是调用它们的 getInstance()
Ҏ来得到单例对象,则它们都是gq加载,q样懒汉式没有Q何意义,而且׃ LazySingleton
采取了同步措施,性能更低Q可以说M懒汉式的性能都低于饿汉式Q。当你用一个单例类的时候,NW一步不是调?getInstance()
么?所以在自己的代码里Q我更喜Ƣ用饿汉式?/p>
下面用一个例子来试加蝲序Q?/p>
// 饿汉 System.out.println("Before"); HungrySingleton.getInstance(); System.out.println("After"); // 懒汉 System.out.println("Before"); LazySingleton.getInstance(); System.out.println("After");
输出l果都是Q?/p>
Before Initializing... After
那么Q懒汉模式还有什么存在意义?如果pȝ使用了某些需要在启动时对c进行扫描的框架Q用饿汉式的话Q启动时间比懒汉式更长,如果使用了大量单例类Q不利于开发阶Dc在pȝ的正式运行阶D,所有的单例c迟早都要加载的Qȝ说来两者性能持^Q但是懒汉式每次都至多一个判断,所以越到后期越体现饿汉的优性?/p>
最后,推荐下《Effective Java》第二版指出的用枚Dcd实现的饿汉单例模式:
// 饿汉 public enum HungrySingleton { INSTANCE; private HungrySingleton() { } }
q种写法不但最z,q能L扩展为实例数量固定的“多例模式”?/p>