什么是Future?
用过Java并发包的朋友或许对Future (interface) 已经比较熟悉了,其实Future 本身是一种被广泛运用的并发设计模式,可在很大程度上简化需要数据流同步的并发应用开发。在一些领域语言(如Alice ML )中甚至直接于语法层面支持Future。
这里就以java.util.concurrent.Future 为例简单说一下Future的具体工作方式。Future对象本身可以看作是一个显式的引用,一个对异步处理结果的引用。由于其异步性质,在创建之初,它所引用的对象可能还并不可用(比如尚在运算中,网络传输中或等待中)。这时,得到Future的程序流程如果并不急于使用Future所引用的对象,那么它可以做其它任何想做的事儿,当流程进行到需要Future背后引用的对象时,可能有两种情况:
- 希望能看到这个对象可用,并完成一些相关的后续流程。如果实在不可用,也可以进入其它分支流程。
- “没有你我的人生就会失去意义,所以就算海枯石烂,我也要等到你。”(当然,如果实在没有毅力枯等下去,设一个超时也是可以理解的)
对于前一种情况,可以通过调用Future.isDone()判断引用的对象是否就绪,并采取不同的处理;而后一种情况则只需调用get()或
get(long timeout, TimeUnit unit)通过同步阻塞方式等待对象就绪。实际运行期是阻塞还是立即返回就取决于get()的调用时机和对象就绪的先后了。
简单而言,Future模式可以在连续流程中满足数据驱动的并发需求,既获得了并发执行的性能提升,又不失连续流程的简洁优雅。
在Java中,如果需要设定代码执行的最长时间,即超时,可以用Java线程池ExecutorService类配合Future接口来实现。 Future接口是Java标准API的一部分,在java.util.concurrent包中。Future接口是Java线程Future模式的实现,可以来进行异步计算。
Future模式可以这样来描述:我有一个任务,提交给了Future,Future替我完成这个任务。期间我自己可以去做任何想做的事情。一段时间之后,我就便可以从Future那儿取出结果。就相当于下了一张订货单,一段时间后可以拿着提订单来提货,这期间可以干别的任何事情。其中Future 接口就是订货单,真正处理订单的是Executor类,它根据Future接口的要求来生产产品。
Future接口提供方法来检测任务是否被执行完,等待任务执行完获得结果,也可以设置任务执行的超时时间。这个设置超时的方法就是实现Java程序执行超时的关键。
IO 是主存和外部设备拷贝数据的过程。IO 是操作系统的底层功能实现,底层通过 I/O 指令进行完成。
NIO是java new IO的简称,是JDK 1.4提供的新特性,主要包括以下几个方面:
– 为所有的原始类型提供缓存支持。
– 字符集编码解码解决方案。
– Channel :一个新的原始 I/O 抽象。
– 支持锁和内存映射文件的文件访问接口。
– 提供多路 (non-bloking) 非阻塞式的高伸缩性网络 I/O 。
Buffer Channel
Buffer 是一块连续的内存块,是NIO读写数据的中转站
Channle 数据的源头或者数据的目的地,用于向 buffer 提供数据或者读取 buffer 数据 ,buffer 对象的唯一接口
package sample; import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel; public class CopyFile {
public static void main(String[] args) throws Exception {
String infile = "C:\\copy.sql";
String outfile = "C:\\copy.txt";
// 获取源文件和目标文件的输入输出流
FileInputStream fin = new FileInputStream(infile);
FileOutputStream fout = new FileOutputStream(outfile);
// 获取输入输出通道
FileChannel fcin = fin.getChannel();
FileChannel fcout = fout.getChannel();
// 创建缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024);
while (true) {
// clear方法重设缓冲区,使它可以接受读入的数据
buffer.clear();
// 从输入通道中将数据读到缓冲区
int r = fcin.read(buffer);
// read方法返回读取的字节数,可能为零,如果该通道已到达流的末尾,则返回-1
if (r == -1) {
break;
}
// flip方法让缓冲区可以将新读入的数据写入另一个通道
buffer.flip();
// 从输出通道中将数据写入缓冲区
fcout.write(buffer);
}
}
}
在Buffer的实现过程中,主要由 position,limit,capacity 三个变量来控制读写的过程
Position 当前写入或读入单位数据数量
limit 代表可以写入或者读入的最大数据量和Buffer容量大小是相同的
capacity Buffer容量
Charset 字符编码
字符编码解码 : 字节码本身只是一些数字,放到正确的上下文中被正确被解析。向 ByteBuffer 中存放数据时需要考虑字符集的编码方式,读取 ByteBuffer 数据时也涉及对字符集解码
Java.nio.charset 提供了编码解码一套解决方案,以http为例,向百度发送请求,并正常的显示。使用到了Charset编码
package nio.readpage; import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.net.InetSocketAddress;
import java.io.IOException;
public class BaiduReader {
private Charset charset = Charset.forName("GBK");// 创建GBK字符集
private SocketChannel channel;
public void readHTMLContent() {
try {
InetSocketAddress socketAddress = new InetSocketAddress(
"www.baidu.com", 80);
//step1:打开连接
channel = SocketChannel.open(socketAddress);
//step2:发送请求,使用GBK编码
channel.write(charset.encode("GET " + "/ HTTP/1.1" + "\r\n\r\n"));
//step3:读取数据
ByteBuffer buffer = ByteBuffer.allocate(1024);// 创建1024字节的缓冲
while (channel.read(buffer) != -1) {
buffer.flip();// flip方法在读缓冲区字节操作之前调用。
System.out.println(charset.decode(buffer));
// 使用Charset.decode方法将字节转换为字符串
buffer.clear();// 清空缓冲
}
} catch (IOException e) {
System.err.println(e.toString());
} finally {
if (channel != null) {
try {
channel.close();
} catch (IOException e) {
}
}
}
}
public static void main(String[] args) {
new BaiduReader().readHTMLContent();
}
}
一个常见的网络 IO 通讯流程:
Open(打开Socket连接)->Accept(接受连接)->Read(读取请求)->Send(发送响应)->Close(关闭连接)
在Accpet还没有带来之前,会发生阻塞,程序挂起,放弃CPU资源
若数据还没有准备好,Read也会发生阻塞,程序挂起,放弃CPU资源
阻塞式网络IO的特点:多线程处理多个连接,每个线程都有自己的占空间,并占用一些CPU资源,当程序运行所需要的外部资源还没有准备好的时候,
程序会挂起,放弃CPU资源,转而去执行其他的程序。这样的结果是发生大量的上下文切换。
下面有个隐喻:
一辆从 A 开往 B 的公共汽车上,路上有很多点可能会有人下车。司机不知道哪些点会有哪些人会下车,对于需要下车的人,如何处理更好?
1. 司机过程中定时询问每个乘客是否到达目的地,若有人说到了,那么司机停车,乘客下车。 ( 类似阻塞式 )
2. 每个人告诉售票员自己的目的地,然后睡觉,司机只和售票员交互,到了某个点由售票员通知乘客下车。 ( 类似非阻塞 )
很显然,每个人要到达某个目的地可以认为是一个线程,司机可以认为是 CPU 。在阻塞式里面,每个线程需要不断的轮询,上下文切换,以达到找到目的地的结果。而在非阻塞方式里,每个乘客 ( 线程 ) 都在睡觉 ( 休眠 ) ,只在真正外部环境准备好了才唤醒,这样的唤醒肯定不会阻塞。
非阻塞的原理
1 把整个过程切换成小任务,通过任务间协作完成
2 由一个专门的线程处理所有的IO事件,并负责分发
3 事件驱动机制,事件到来的时候触发,而不是同步的去检测
4 线程通讯 多线程之间通过wait 和notify 进行数据通信,保证每次的上下文切换都是有意义的
Java中很多class都是immutable,像String,Integer等,它们通常用来作为Map的key.
那么在实现自定义的Immutable的Class的时候,应该注意哪些要点呢?
a)Class 应该定义成final,避免被继承。
b)所有的成员变量应该被定义成final。
c)不要提供可以改变类状态(成员变量)的方法。【get 方法不要把类里的成员变量让外部客服端引用,当需要访问成员变量时,返回成员变量的copy】
d)构造函数不要引用外部可变对象。如果需要引用外部可以变量,应该在构造函数里进行defensive copy。
an immutable object is an object whose state cannot be modified after it is created.
不可变对象一旦被创建就它的状态就不能被修改。
A classic example of an immutable object is an instance of the Java String
class.
不可变对象的一个经典的例子是String类的实例。
- String s = "ABC";
- s.toLowerCase();
The method toLowerCase()
will not change the data "ABC" that s
contains.
Instead, a new String object is instantiated(被实例化 ) and given the data "abc" during its construction.
A reference to this String object is returned by the toLowerCase()
method.
To make the String s
contain the data "abc", a different approach is needed.
- s = s.toLowerCase();
Now the String s
references a new String object that contains "abc". The String class's methods never affect the data that a String object contains.
For an object to be immutable, there has to be no way to change fields, mutable or not, and to access fields that are mutable.
Here is an example of a mutable object.
- import java.util.ArrayList;
- import java.util.LinkedList;
- import java.util.List;
- class Cart {
- private final List items;
- public Cart(List items) {
- this.items = items;
- }
- public List getItems() {
- return items;
- }
- public static void main(String[] args) {
- List list = new ArrayList();
- list.add("element1");
- Cart cart = new Cart(list);
- cart.getItems().add("element2");
- // 下面的代码能运行吗?为什么
- // list=new LinkedList();
- // cart.items=list;
- }
- }
An instance of this class is not immutable: one can add or remove items either by obtaining the field items
by calling getItems()
or by retaining(保留,保持 ) a reference to the List object passed when an object of this class is created.
The following change partially solves this problem. In the ImmutableCart
class, the list is immutable: you cannot add or remove items.
However, there is no guarantee that the items are also immutable.
One solution is to use the decorator pattern as a wrapper around each of the list's items to make them also immutable.
- import java.util.ArrayList;
- import java.util.Collections;
- import java.util.List;
- class ImmutableCart {
- private final List items;
- public ImmutableCart(List items) {
- this.items = Collections.unmodifiableList(new ArrayList(items));
- }
- public List getItems() {
- return items;
- }
- public static void main(String[] args) {
- List list = new ArrayList();
- list.add("element1");
- ImmutableCart cart = new ImmutableCart(list);
- cart.getItems().add("element2");
- }
- }
运行抛出异常:
Exception in thread "main" java.lang.UnsupportedOperationException
public class Immutable
{
private int data;
public Immutable(int initVal)
{
data=initVal;
}
public int read()
{
return data;
}
public Immutable quadruple()
{
return new Immutable(data*4);
}
}