线程轮询的有数据启动,无数据停止,节省服务器资源,线程的启动和暂停

本文介绍了一种基于缓存Map和线程轮询的离岗超时检测系统,用于实时监测工作人员离岗状态,实现超时短信提醒,确保工作纪律。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

现实场景有这样一个需求:

某工作大厅,有几百个工作人员,当有某个工作人员向离岗出去,要点击自己工位上的暂离按钮,回岗的时候点击工位上的回岗按钮,如果离岗超过20分钟还没有回岗则短信提醒该工作人员离岗超时,该回岗了!

我们需要怎么处理这个业务呢?

首先要有一个缓存Map,存放工作人员的userid和超时时间。

然后针对该Map提供数据删除和添加的方法,回岗就删除,离岗就添加。

最后再起一个线程一直轮询该Map,当前时间和Map中的超时时间做对比,如果当前时间在超时时间之后,那这个人就是超时了,然后进行短信提醒。

 

先看一下这个缓存的Map,我这里写了一个工具类:

 

/**
 * 检测用户离岗是否超时工具类
 * @author Run the ant
 * @date 2018年8月2日
 * @version 1.0
 */
@Service
public class BackJobCheckUtil {
	private final static Logger logger = LoggerFactory.getLogger(BackJobCheckUtil.class);
	
	public static final String NAMESPACE = "backjobcheck";
	public static final String DATAKEY = "datakey";
	
	@Autowired
	private BackJobCheckTask backJobCheckTask;

	@Resource(name = "cache")
	private BaseCache<HashMap<String, Date>> datecache;
	
	/**
	 * 确认是否有人员暂离超时(超时发送短信,但是只会发送一次)
	 * 
	 * @author Run the ant
	 * @data 2018年8月2日
	 */
	public void check () {
		HashMap<String, Date> data = getDataCache();
		if (data.size() == IConstant.ZERO) {
			//当查询到缓存中没有数据的时候则中断该任务
			backJobCheckTask.interrupt();
		}
		Date now = DateUtil.getCurrentDate();
		List<String> uids = new ArrayList<String>();
		for (Entry<String, Date> entry : data.entrySet()) {
			Date date = entry.getValue();
			//如果离岗时间超出限制则短信提醒
			if (now.after(date)) {
				//短信提醒业务
			}
		}
		if (uids.size() > 0) {
			for (String uid : uids) {
				data.remove(uid);
			}
			datecache.put(NAMESPACE, DATAKEY, data);
		}
	}
	
	/**
	 * 添加一条人员离岗记录
	 * @param uid
	 * @author Run the ant
	 * @data 2018年8月2日
	 */
	public synchronized void addUserOverTime (String uid) {
		Integer overmin = 20;
		Date overtime = new Date(new Date().getTime() + overmin * 60 * 1000);
		HashMap<String, Date> data = getDataCache();
		data.put(uid, overtime);
		datecache.put(NAMESPACE, DATAKEY, data);
		//唤醒
		backJobCheckTask.build();
	}
	
	/**
	 * 删除一条人员离岗记录
	 * @param uid
	 * @author Run the ant
	 * @data 2018年8月2日
	 */
	public synchronized void delUserOverTime (String uid) {
		HashMap<String, Date> data = getDataCache();
		data.remove(uid);
		datecache.put(NAMESPACE, DATAKEY, data);
	}
	
	
	/**
	 * 获取缓存中的人员超时时间数据
	 * @return
	 * @author Run the ant
	 * @data 2018年8月2日
	 */
	private HashMap<String, Date> getDataCache () {
        //针对自己的缓存修改代码,业务都是这样,从缓存取出我的Map数据
		HashMap<String, Date> data = datecache.get(NAMESPACE, DATAKEY);
		if (data == null) {
			data = new HashMap<String, Date>();
			datecache.put(NAMESPACE, DATAKEY, data);
		}
		return data;
	}
	
}

这里有BackJobCheckTask这个类,这个是线程执行的类,贴代码:

/**
 * 检测用户离岗是否超时任务
 * @author Run the ant
 * @date 2018年7月31日
 * @version 1.0
 */
@Service
public class BackJobCheckTask implements Runnable {

	@Autowired
	private BackJobCheckUtil backJobCheckUtil;
	
	private ReentrantLock lock = new ReentrantLock();
    private final static Logger logger = LoggerFactory.getLogger(BackJobCheckTask.class);
    private Thread tempThread;
    private final static String name = "检测用户离岗是否超时任务";
    
    @Override
	public void run() {
		logger.info("线程【{}】开始运行", tempThread.getName());
		try {
			while (true) {
				backJobCheckUtil.check();
				tempThread.sleep(3000);
			}
		} catch (Exception e) {
			logger.error(name + "异常",e);
		}
		logger.info("线程【{}】停止运行", tempThread.getName());
	}
    
    
    /**
     * 检测线程是否启动,未启动则启动线程
     * 
     * @author Run the ant
     * @data 2018年7月31日
     */
    private void runThread(){
		if(tempThread == null || tempThread.isAlive() == false){
			try {
				lock.lock();
				if(tempThread == null || tempThread.isAlive() == false){
					tempThread = new Thread(this,name);
					tempThread.start();
				}
			} catch (Exception e) {
				logger.error("发布者{}线程启动失败", name,e);
			}finally{
				lock.unlock();
			}
		}
	}
    
    /**
     * 线程中断
     * 
     * @author Run the ant
     * @data 2018年7月31日
     */
    public void interrupt(){
		if(tempThread != null){
			tempThread.interrupt();
		}
	}
    
    /**
     * 线程唤醒
     * 
     * @author Run the ant
     * @data 2018年7月31日
     */
    public void build(){
		runThread();
	}

}

然后还需要1个接口,来接收工作人员点击暂离和回岗的消息:

@Controller
@RequestMapping(value={"/servlet"})
public class Servlet {
	private final static Logger logger = LoggerFactory.getLogger(Servlet .class);
	
    @Autowired
	private BackJobCheckUtil backJobCheckUtil;

	/**
	 * 暂离
	 * @param request
	 * @param response
	 * @param uid  窗口人员uid
	 * @author Run the ant
	 * @data 
	 */
	@ResponseBody 
	@RequestMapping(value = "/windowpause")
	public void windowPause (HttpServletRequest request, HttpServletResponse response, String uid) {
         //接口认证等等其他业务,自行修改
         backJobCheckUtil.addUserOverTime(uid);

	}
	
	/**
	 * 回岗
	 * @param request
	 * @param response
	 * @param uid 窗口人员uid
	 * @author Run the ant
	 * @data 
	@ResponseBody 
	@RequestMapping(value = "/windownormal")
	public void windowNormal (HttpServletRequest request, HttpServletResponse response, 
			String uid) {
        //接口认证等等其他业务,自行修改
		backJobCheckUtil.delUserOverTime(uid);
	}
	

}

 

OK,这样就实现了,当没有人暂离的时候,该线程就自行销毁,如果某个时间点突然有数据加入,线程就重新启动。

<think>嗯,用户问的是Java怎么用多线程轮询读取数据并进行处理。首先我得理解清楚用户的需求。轮询通常是指定期检查某个数据源是否有新数据,然后进行处理。多线程的话,应该是为了提高效率,同时处理多个数据源或者并行处理数据。 首先,我得考虑Java中多线程的实现方式。常用的有继承Thread类、实现Runnable接口,或者使用Executor框架。Executor框架可能更合适,因为它管理线程池更方便,能有效控制资源。 然后,轮询的机制。轮询一般需要循环检查,可能需要定时执行。Java中有几种定时任务的方法,比如TimerScheduledExecutorService。后者更推荐,因为Timer是单线程的,如果有任务执行时间长,会影响后续任务,而ScheduledExecutorService可以配置多个线程。 接下来,数据读取处理的分离。可能需要一个生产者-消费者模型,一个线程负责读取数据,放入队列,另一个线程池处理数据。这样分工明确,避免阻塞。使用BlockingQueue可能比较合适,因为它线程安全,可以处理并发问题。 然后要考虑数据源是什么。用户可能是指文件、数据库、消息队列或者网络资源。不同的数据源读取方式不同,但核心是多线程轮询。假设用户的数据源需要定期查询,比如数据库,每次查询新数据。 可能的步骤是: 1. 创建线程池,使用ScheduledExecutorService来定时触发数据读取任务。 2. 数据读取任务执行查询,获取新数据,放入队列。 3. 另一个线程池从队列中取出数据,进行处理。 需要确保线程安全,队列的并发访问要处理好。比如使用LinkedBlockingQueue,这样生产者消费者可以安全操作。 另外,轮询间隔需要合理设置,太短可能消耗资源,太长可能导致延迟。用户可能需要根据具体情况调整。 还要考虑异常处理,如果数据读取或处理中出现异常,如何记录恢复。比如在任务中捕获异常,记录日志,避免整个线程崩溃。 可能还需要考虑如何优雅地关闭线程池,防止数据丢失。比如在应用关闭时,执行shutdown方法,等待任务完成。 用户可能还关心如何确保数据处理的顺序,但如果是多线程处理,顺序可能无法保证,除非使用顺序队列或者分区处理。需要根据用户需求判断是否需要有序处理。 此外,资源管理也很重要,比如数据连接多线程环境下的使用,可能需要连接池来管理。 总结下来,步骤大概是:配置线程池、定时读取数据、放入阻塞队列、多线程处理队列中的数据。代码示例的话,可以用ScheduledExecutorService定时执行读取任务,使用ExecutorService处理数据,队列作为中间媒介。 可能需要注意的点: - 线程池的大小配置,避免过多线程导致资源耗尽。 - 队列容量,防止内存溢出。 - 错误处理日志记录。 - 数据去重,避免重复处理。 现在把这些整理成结构化的回答,分步骤说明,并给出示例代码,这样用户更容易理解实施。</think>在Java中使用多线程轮询读取并处理数据,可通过以下步骤实现: --- ### **1. 核心思路** - **多线程轮询**:通过线程池定期检查数据源 - **生产者-消费者模型**:分离数据读取与处理逻辑 - **队列缓冲**:使用线程安全的阻塞队列传递数据 --- ### **2. 实现步骤** #### **2.1 创建线程池** ```java // 定时任务线程池(用于轮询) ScheduledExecutorService poller = Executors.newScheduledThreadPool(1); // 数据处理线程池 ExecutorService processor = Executors.newFixedThreadPool(4); ``` #### **2.2 定义数据队列** ```java BlockingQueue<Data> dataQueue = new LinkedBlockingQueue<>(100); ``` #### **2.3 实现轮询任务** ```java Runnable pollTask = () -> { try { List<Data> newData = queryDataSource(); // 自定义数据源查询方法 dataQueue.addAll(newData); } catch (Exception e) { e.printStackTrace(); } }; // 每500ms执行一次轮询 poller.scheduleAtFixedRate(pollTask, 0, 500, TimeUnit.MILLISECONDS); ``` #### **2.4 实现处理任务** ```java Runnable processTask = () -> { while (!Thread.currentThread().isInterrupted()) { try { Data data = dataQueue.take(); // 自动阻塞等待数据 processData(data); // 自定义处理逻辑 } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } }; // 启动4个处理线程 for (int i = 0; i < 4; i++) { processor.submit(processTask); } ``` --- ### **3. 关键优化点** 1. **队列选择**: - `LinkedBlockingQueue`:默认无界队列(注意内存风险) - `ArrayBlockingQueue`:固定容量队列(需处理满队列情况) 2. **异常处理**: ```java // 在轮询任务中添加重试机制 if (!dataQueue.offer(newData)) { // 处理队列已满的情况 } ``` 3. **优雅关闭**: ```java poller.shutdown(); processor.shutdown(); try { if (!processor.awaitTermination(10, TimeUnit.SECONDS)) { processor.shutdownNow(); } } catch (InterruptedException e) { processor.shutdownNow(); } ``` --- ### **4. 完整架构示意图** ``` +----------------+ +---------------+ +-----------------+ | 数据源 | --> | 轮询线程 | --> | 阻塞队列 | | (DB/API/文件) | | (Scheduled) | | (线程安全缓冲) | +----------------+ +---------------+ +-----------------+ | v +-------------------+ | 处理线程池 | | (并行处理数据) | +-------------------+ ``` --- ### **5. 注意事项** 1. 避免队列无限增长导致内存溢出 2. 根据硬件资源合理设置线程池大小(建议通过压测确定) 3. 需要同步逻辑时使用`ConcurrentHashMap`等并发容器 4. 重要数据需实现幂等处理,防止重复消费 这种模式适用于日志处理、实时监控、消息队列消费等场景,可根据具体需求调整轮询间隔线程池参数。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Run_the_ant

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值