NetFrameWork是CsFrameWork的进阶,两者之间最大的区别是原来的csFrameWork比较low,用的是长连接,运行的效率比较低,整体线程安全性低等。
其中,安全性低是因为CsFrameWork编写时没有考虑过多线程的情况,当出现多个客户端同时连接等高并发现象时,就会崩溃!
先来看一下之前的会话过程。
public abstract class Communication implements Runnable{
private Socket socket;
private DataInputStream dis;
private DataOutputStream dos;
private volatile boolean goon;
public Communication(Socket socket) {
try {
this.socket = socket;
dis = new DataInputStream(socket.getInputStream());
dos = new DataOutputStream(socket.getOutputStream());
goon = true;
new Thread(this, "会话层").start();
} catch (IOException e) {
e.printStackTrace();
}
}
abstract void dealMessage(NetMessage mess);
@Override
public void run() {
String mess = "";
while (goon) {
try {
mess = dis.readUTF();
dealMessage(new NetMessage(mess));
} catch (IOException e) {
if (goon == true) {
// 对端异常掉线
peerAbnormalDrop();
}
goon = false;
}
}
close();
}
}
上述的代码,当多线程同时运行的时候,会存在很大的问题。因为我们的Communication类只关心会话,所以,当客户端连接好服务器之后,会初始化Communication类,初始化的时候,会调用构造方法,但是,由于存在线程的并发运行,我们不能保证new Thread(this, “会话层”).start();这条语句执行完之后,run();方法会立刻执行,相反,有可能start之后,时间片段到了,但是dis/dos通信信道已经建立,将开始TCP/IP传输,客户端就可以通过dos.writeUTF();方法向服务器端请求一系列数据,然而,服务器端dis.readUTF();之后,就会立马进行dos.writeUTF();方法,这时,由于客户端的run()方法时间片段还未到,传递过来的消息暂时还无法到达客户端,所以资源很浪费,效率很低,严重的还可能出现系统崩溃!
针对上述的问题,我们要对多线程进行处理。
public abstract class Communication implements Runnable {
private Socket socket;
private DataInputStream dis;
private DataOutputStream dos;
private volatile boolean goon;
private Object lock;
Communication(Socket socket) {
try {
this.lock = new Object();
// 这个lock对于Communication的不同实例化对象是不同的;
this.socket = socket;
this.dis = new DataInputStream(socket.getInputStream());
this.dos = new DataOutputStream(socket.getOutputStream());
this.goon = true;
// 同步锁对象是lock,那么意味着:不同的Communication对象
// 的lock锁是不同的,不能进行“互锁”。
// 因此,服务器端连接的多个客户端所实例化的Communication对象
// 及其线程,不会因为自己的lock而被迫串行下面的代码!
// 这个“门锁”只对能识别lock的线程起作用!
synchronized (lock) {
new Thread(this, "会话层").start();
try {
lock.wait();
} catch (InterruptedException e) {
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
public abstract void peerAbnormalDrop();
protected abstract void dealNetMessage(NetMessage message);
@Override
public void run() {
String message = null;
synchronized (lock) {
lock.notify();
}
// 这个run()(即,线程),因为与产生它的线程,即其父线程共同持有lock
// 对象锁,因此,上面的synchronized(lock)会对这个线程起作用!
while (goon) {
try {
message = dis.readUTF();
dealNetMessage(new NetMessage(message));
} catch (IOException e) {
if (goon == true) {
goon = false;
peerAbnormalDrop();
}
}
}
close();
}
}
上述代码,通过加锁,把执行顺序进行了控制,当我们刚要开始执行new Thread(this, “会话层”).start();时,假如时间片段到了,轮到别的线程执行时,会先检查锁的状态,发现锁是锁着的,会立刻阻塞自身,进而继续执行这个锁里面的代码,只有当这个锁完全被打开时,才会唤醒刚才进行阻塞自身的线程让他们继续多线程运行。现在假如我们已经执行完了new Thread(this, “会话层”).start();并且时间片段到了,因而,run()方法也应该被执行,但是执行到synchronized时,检查锁的状态是关闭的,只能继续将自身阻塞,等待刚才的锁里面的内容继续执行。直到执行到wait()的时候,会将自身阻塞,打开lock锁,同时会唤醒run()方法这个线程到就绪态,一旦到了运行态,会立刻检查到锁被打开,然后立马给自身上锁,然后直到执行完notify方法,这时,wait()被唤醒,与run方法都进入了就绪态,这时,就开始继续多线程并发执行了。
因为,客户端只有在Communication类的构造方法完全执行完之后,才会去调用其他的与服务器端相关的操作。这样的加锁,保证了在Communication构造方法全部执行完之前,run()方法可以被执行起来。因此,这样就不会出现,服务器的消息回来了以后,dis.readUTF();方法还没法执行这一局面。这也是多线程编程的一个很好的应用!