在编写简单的聊天室时,需要线程间的通信。
服务器与客户端之间是通过线程连接到一块的,他们都需要最基础的通信层,我是用Communication类来表示。
Communication需要完成最基本的通信信道的建立。客户端与服务器除了最基本的通信层之外还需要客户端会话层(ClientConversation)与服务器会话层(ServerConversation),服务器层(Server)与客户端层(Client)并不能直接进行通信。
完成Communication类时需要用到线程,要实现Runnable接口,则需要完成相关的Run() 方法。在Communication类构造方法中执行以下代码:
try {
lock = new Object();
this.socket = socket;
this.dis = new DataInputStream(socket.getInputStream());
this.dos = new DataOutputStream(socket.getOutputStream());
synchronized (lock) {
this.goon = true;
new Thread(this, "侦听对端消息线程").start(); // 开启新的线程
/* 如果开启线程后还有其他代码要处理,则,它们一定在子线程真正运行之前执行,应该将代码放在wait() 方法与new Thread 之间。*/
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} catch (IOException e) {
e.printStackTrace();
}
new Thread().start() 之后就开启一个新的线程。
@Override
public void run() {
String message = "";
synchronized (lock) {
lock.notify();
}
while (goon) {
try {
message = dis.readUTF();
dealNetMessage(new NetMessage(message)); // 处理对端消息 抽象方法,在Conversation层实现
} catch (IOException e) {
if (goon == true) {
goon = false;
peerAbnormalDrop(); // 处理对端异常掉线 抽象方法,在Conversation层实现
}
}
}
close(); // 关闭线程
}
当运行到new Thread().start(),新线程开启,但是为了防止start() 之后还有其他代码还未执行,但是新开启的线程已经执行完,所以给相关方法加锁,加锁后,其他线程只能在就绪态等待,就避免了上述情况,等锁打开时新的线程与其他线程一块竞争CPU,此时用wait()、notify()对相关代码进行操控。
wait()方法,在阻塞自身线程前,必然开lock锁;当该线程被其它线程notify()后,将再次进入lock锁块,而进一步对lock上锁。
要注意:线程被wait()之后只能通过notify()来唤醒。并且它们只能在相应的synchronized(){}语句块内执行。
之前在编写程序时,很少考虑到用锁的问题,也没有考虑到线程并发的问题,教主用了几乎两个小时的时间讲了关于锁和wait()、notify(),才发现线程安全问题真的很重要,我现在对锁的理解还不够深刻,写出来的博文还是很肤浅。