java多线程聊天咨询程序
本聊天程序涉及三个工程,实现的功能是:implement a Online Help program for businesses such as banks which have company agents answer the customer questions.
1、client端,
一个登入界面一个聊天界面
2、agent端
一个登入界面,进入之后将当前给该agent发消息的用户列表产生出来,并在当前登录的agent之间创建一个聊天室。agent可以给用户回答问题。
1) 用户列表采用Jlist生成,同时对每个列项进行监听 ,如果有新的用户发登录过来的话就将用户列表刷新。
2)开启一个timer实时将服务器端收到的用户列表进行刷新,注意timer本身是extends线程:
public void startTimer(){
MessageTran messageTran = new MessageTran();
messageTran.setFrom(adviser);
messageTran.setMessageType(MessageType.REFRESH);
Timer timer = new Timer();
timer.schedule(new TimerTask() {
public void run() {
try {
synchronized (out){
out.writeObject(messageTran);
}
if(list != null){
// System.out.println("list");
onlineCustomers.setListData(getList(list));
onlineCustomers.repaint();
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}, 1000,2000);
}
3) 创建一个刷新线程实现对服务器发送过来的refresh信息和聊天室消息进行接收,这个线程是以内部类的格式创建在ChatClient中的,以此来访问ChatClient的Jlist和JTextArea。
class RefreshResult implements Runnable{
MessageTran obj = null;
@Override
public void run() {
try {
while (true) {
synchronized(in){
obj = (MessageTran) in.getObject();
Thread.yield();
}
switch(obj.getMessageType()){
case REFRESH:
list = (Vector<User>)obj.getObjectTran();
break;
case MESSAGEALL:
msgBd.append(obj.getFrom().getUserName()+":" +((String)obj.getObjectTran())+"\n");
break;
default:
break;
}
}
} catch (Exception e) {
}
}
}
4)同时创建另外一个线程实现服务器接收登录结果和client发送
class CAgent implements Runnable {
JTextArea msgBd = null;
private Instream in = null;
ObjectOutputStream out = null;
MessageTran obj = null;
Vector<User> listV = null;
private Lock lock = new ReentrantLock();
public CAgent(Instream in, ObjectOutputStream objOutput){
this.in = in;
this.out = objOutput;
listV = new Vector<User>();
}
public CAgent(JTextArea taMsg, Instream in) {
msgBd = taMsg;
this.in = in;
}
public void run() {
try {
while (true) {
//synchronized(in){
obj = (MessageTran) in.getObject();
//}
switch(obj.getMessageType()){
case LOGIN:
User user = (User)obj.getFrom();
listV = (Vector<User>)obj.getObjectTran();
ChatClient chatClient = new ChatClient(user, listV, in ,out);
this.wait();
break;
case MESSAGE:
msgBd.append(obj.getFrom().getUserName()+":" +((String)obj.getObjectTran())+"\n");
break;
default:
break;
}
}
} catch (Exception e) {
}
}
}
3、服务器端
1)通过线程池来管理每一个连入的线程,每当ServerSocket Accept一个连入的SOCKET的后就创建一个线程,用来管理其输入和输出。
private final class SocketTask implements Runnable {
private Socket socket;
private InputThread in;
private OutputThread out;
private OutputThreadMap map;
public SocketTask(Socket socket) {
this.socket = socket;
map = OutputThreadMap.getInstance();
}
@Override
public void run() {
out = new OutputThread(socket, map);//
// 先实例化写消息线程,(把对应用户的写线程存入map缓存器中)
in = new InputThread(socket, out, map, adviserVector, customerVector);// 再实例化读消息线程
out.setStart(true);
in.setStart(true);
in.start();
out.start();
}
}
2)、通过HashMap将用户id 和 输出线程行程一个键值对保存起来,方便通过id进行查找输出线程。通过单例模式创建OutputThreadMap.本来想利用string来做键值对的,但想了想还是算了
public class OutputThreadMap {
private HashMap<Integer, OutputThread> map;
private static OutputThreadMap instance;
// 私有构造器,防止被外面实例化改对像
private OutputThreadMap() {
map = new HashMap<Integer, OutputThread>();
}
// 单例模式像外面提供该对象
public synchronized static OutputThreadMap getInstance() {
if (instance == null) {
instance = new OutputThreadMap();
}
return instance;
}
// 添加写线程的方法
public synchronized void add(Integer id, OutputThread out) {
map.put(id, out);
}
// 移除写线程的方法
public synchronized void remove(Integer id) {
map.remove(id);
}
// 取出写线程的方法,群聊的话,可以遍历取出对应写线程
public synchronized OutputThread getById(Integer id) {
return map.get(id);
}
}
4、遇到的问题
1).java.io.StreamCorruptedException: invalid type code: AC
在同一个线程里面ObjectIputStream
原因:这是因为一个线程中定义了多个objectOutputStream
2)NullPointerException
原因:引用对象没有实例化
3)发送对象没有实现
解决:
public class MessageTran implements Serializable
4)多线程资源互斥问题:
在agent端,对于socket.getInputStream()的IO流发生访问冲突,刚开始采用的方法是在线程中直接对IO流使用同步控制块
synchronized(in){
obj = (MessageTran) in.getObject();
}
但是由于ChatClient中的Refresh线程也需要获取到in的输入,同时SingleChat开启的CAgent线程也要获取in的输入,所以虽然实现了对in 的互斥访问,但是其结果是各个线程无法及时获取到in 的锁而出现了用户消息的丢失。
所以最后修改的方法是使用java的LOCK,来实现对锁的释放。
public class Instream {
Socket socket;
ObjectInputStream ois;
private Lock lock = new ReentrantLock();
Instream(Socket socket){
this.socket = socket;
try {
ois = new ObjectInputStream(socket.getInputStream());
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public Object getObject() throws ClassNotFoundException, IOException{
Object object = null;
lock.lock();
try{
object = ois.readObject();
}finally{
lock.unlock();
}
return object;
}
}