说实话,在写这个应用前,读过《Java并发编程的艺术》,但对于并发以及多线程的应用不是很多,现在写完后也算有了一定的了解,所以这篇文章就来聊聊项目中用到的一些线程框架。
如果对于流程不熟悉的还是先看下前面几篇文章吧,这里简单提一下线程应用的地方。
贴下代码吧,这样更直观。
package org.cool.qqrobot.thread;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import org.cool.qqrobot.common.Const;
/**
* 线程池(单例,将各种常用线程池封装在此类,方便统一调用)
* @author zhoukl
*
*/
public final class ThreadPool {
private static ThreadPool threadPool = new ThreadPool();
private static ExecutorService fixedThreadPool = Executors.newFixedThreadPool(Const.FIXED_THREAD_POOL_NUM);
private static ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(Const.SCHEDULED_THREAD_POOL_NUM);
private ThreadPool() {}
public static ThreadPool getInstance() {
return threadPool;
}
public ExecutorService getFixedThreadPool() {
return fixedThreadPool;
}
public ScheduledExecutorService getScheduledThreadPool() {
return scheduledThreadPool;
}
}
以上代码是定义的线程池,下面贴下具体应用。
第一处:二维码验证
服务端http请求获取到二维码后立即将二维码返回给了用户,异步线程开始轮询二维码的状态。
private void loginCheck(ProcessData processData) {
logger.info("|{}|--二维码验证中", ProcessVar.getProcessId());
ThreadPool.getInstance().getFixedThreadPool().execute(new Runnable() {
@Override
public void run() {
for (int i = ; i < Const.CYCLE_NUM; i++) {
MyHttpRequest checkRequest = new MyHttpRequest();
checkRequest.setUrl("https://ssl.ptlogin2.qq.com/ptqrlogin?webqq_type=10&remember_uin=1&login2qq=1&aid=501004106&u1=http%3A%2F%2Fw.qq.com%2Fproxy.html%3Flogin2qq%3D1%26webqq_type%3D10&ptredirect=0&pt);
MyHttpResponse checkResponse = new MyHttpResponse();
try {
checkResponse = processData.getMyHttpClient().execute(checkRequest);
} catch (Exception e) {
processData.setGetCode(false);
logger.error("二维码登录状态获取异常", e);
}
boolean firstLoginSuccess = false;
if (MyHttpResponse.S_OK == checkResponse.getStatus()) {
firstLoginSuccess = firstLogin(processData, checkResponse);
} else {
processData.setGetCode(false);
}
// 每次轮询休息一段时间,不然太频繁
threadSleep();
if (firstLoginSuccess) {
break;
}
// 轮询时间结束,用户还未扫描登录,自动标识为登录失败(不然下次不能获取二维码,刷新页面二维码不变)
if (i == Const.CYCLE_NUM - ) {
processData.setGetCode(false);
}
}
}
});
}
这里的轮询采用for循环控制,因为不需要永久轮询。部分方法未给出实现部分,但也没删是因为有涉及到过程数据(processData)的存取,之前只是提及,没有代码演示,现在贴出来可能会直观些。
第二处:用户登录信息记录
这部分没有实时同步需要,因此采用异步记录日志,线程池应用和上面一致。
// 异步记录登录用户信息
// 登录成功后,记录当前登录日志的id,当用户主动退出时,再次更新登出时间
ThreadPool.getInstance().getFixedThreadPool().execute(new Runnable() {
@Override
public void run() {
logger.info("记录用户登录信息");
robotDao.addLoginInfo(userInfo);
}
});
IO读写担心耗时就采用异步线程。
第三处:消息轮询
用户的消息收发需要一直提供服务,所以线程不间断的轮询消息。
private void pollMessageThread(ProcessData processData) {
logger.info("开始轮询消息");
// 把每一个轮询线程对象保存到map中,便于终止轮询(key:qq号,value:Future对象),同时清除登录成功的缓存信息,更新退出时间
Future<?> future = ThreadPool.getInstance().getScheduledThreadPool().scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
// 消息收发...
}
}, Const.INIT_DELAY, Const.PERIOD, TimeUnit.MICROSECONDS);
// 保存当前线程future
CacheMap.threadFuturMap.put(processData.getSelfUiu(), future);
}
这里采用定时任务,循环执行服务的调用。此处还用到了map,因为是多线程并发操作,那么HashMap和HashTable显然都不合适了(至于为什么,其它文章有详细说明,这里就不重复了),所以采用ConcurrentHashMap保存缓存数据。
本来想聊聊多线程的原理什么的,看了下相关的书籍和博客,感觉自己的领悟程度还不够深,所以不敢妄加总结,大牛实在太多了,我等小辈还是多做多学多看吧。
好了,这个年也过的差不多了,又大一岁,好好努力,别辜负了时光~