跟着实例学习java多线程9-定时任务实例

定时任务是我们经常遇到的业务场景,我们有很多的功能都需要这样的技术来实现,例如:定时获取一些数据push出去,定时处理一些清理任务,定时检查某个值等。那么我们该怎么实现,在实现中又该注意一些什么?

定时任务就是另开一个线程来执行,其实也是并发的一类,大家可能不好理解,说定时不就是到时间执行一下,怎么还会产生并发,这里主要是看两个指标,一是看执行频率,二是看每次执行的时间,如果执行频率高并且执行任务又会很耗时,那么这时候就形成了并发,当然还有一种情况那就是,定时的job中调用其它服务的方法,而正常的业务逻辑中也调用那个服务的方法,那么这时在某个巧合的时间也有可能发生并发。

所以我们在写定时任务的时候也要注意这么两点:

1:如果执行频率高并且执行任务又会很耗时,要在job的执行方法上加同步处理。

2:如果job里调用其它服务方法,那么其它服务所提供的方法也要保证发布成线程安全的方法。

下面我们来说一下有几种实现定时任务的方法:

1:java提供的timer

package com.home.thread.thread10;

import java.util.Date;
import java.util.Timer;

/**
 * @author gaoxu
 * 实践出真知!
 */
public class TimerMain {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		Timer timer = new Timer();
		timer.schedule(new TimerTaskThread(), new Date(), 2000);
	}

}
package com.home.thread.thread10;

import java.util.Date;
import java.util.TimerTask;

/**
 * @author gaoxu
 * 实践出真知!
 */
public class TimerTaskThread extends TimerTask{
	//没有参数的构造函数
	public TimerTaskThread(){

	}
	//也可以写有参数的构造函数

	@Override
	public synchronized void run() {
		System.out.println(new Date().toString()+"Timer方式,我是两秒执行一次");
		//这里可以写自己要实现的定时业务

	}

}

这是一个由timer来实现的定时任务,运行结果如下:

Sat Feb 07 01:25:31 CST 2015,Timer方式,我是两秒执行一次
Sat Feb 07 01:25:33 CST 2015,Timer方式,我是两秒执行一次
Sat Feb 07 01:25:35 CST 2015,Timer方式,我是两秒执行一次
Sat Feb 07 01:25:37 CST 2015,Timer方式,我是两秒执行一次

2:由开源框架Quartz实现的定时任务

package com.home.thread.thread10;

import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SchedulerFactory;
import org.quartz.impl.StdSchedulerFactory;

import com.home.JobServiceImpl;

/**
 * @author gaoxu
 * 实践出真知!
 */
public class SchedulerMain {
	public static void main(String[] para) {

		try {
			SchedulerFactory sf = new StdSchedulerFactory();
			Scheduler scheduler = sf.getScheduler();
			JobServiceImpl jobService = new JobServiceImpl();
			jobService.setScheduler(scheduler);
			jobService.platDataCollectInterval(2);
		} catch (SchedulerException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

}

package com.home;

import org.quartz.JobBuilder;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SimpleScheduleBuilder;
import org.quartz.Trigger;
import org.quartz.TriggerBuilder;

import com.home.job.TestJob;

/**
 * @author gaoxu
 * 实践出真知!
 */
public class JobServiceImpl {

	private Scheduler scheduler;

	public void setScheduler(Scheduler scheduler) {
		this.scheduler = scheduler;
	}

	public Scheduler getScheduler() {
		return scheduler;
	}

	public void platDataCollectInterval(int interval)
			throws SchedulerException, Exception {
		System.out.println("update  PlatDataCollect cycle start");

		JobDetail jobDetail = JobBuilder.newJob(TestJob.class).withIdentity("testJob_1", "group_1").build();

		Trigger trigger = TriggerBuilder
				.newTrigger()
				.withIdentity("trigger_1", "group_1")
				.withSchedule(
						SimpleScheduleBuilder
								.repeatSecondlyForever(interval)).build();

		scheduler.scheduleJob(jobDetail, trigger);

		scheduler.start();

	}

}
package com.home.job;

import java.util.Date;

import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;

public class TestJob implements Job{

	@Override
	public void execute(JobExecutionContext arg0) throws JobExecutionException {
		 System.out.println("test job date:" + new Date().toString());  

	}

}

这里有三个类,其中job类是任务的执行者,Scheduler是一个调度器,调度器中可以创建多个Trigger,每个Trigger可以执行一个job。

log4j:WARN No appenders could be found for logger (org.quartz.impl.StdSchedulerFactory).
log4j:WARN Please initialize the log4j system properly.
log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.
update  PlatDataCollect cycle start
test job date:Sat Feb 07 01:30:31 CST 2015
test job date:Sat Feb 07 01:30:33 CST 2015
test job date:Sat Feb 07 01:30:35 CST 2015
test job date:Sat Feb 07 01:30:37 CST 2015

3:是线程来实现

package com.home.thread.thread10;

/**
 * @author gaoxu
 * 实践出真知!
 */
public class ThreadMain {
	public static void main(String[] para) {
		ThreadTask threadTask = new ThreadTask();
		threadTask.start();
	}

}
package com.home.thread.thread10;

import java.util.Date;

public class ThreadTask extends Thread{

	public void run(){
		while(true){
			try {
				Thread.sleep(2000);
				System.out.println(new Date().toString()+"线程方式,我是两秒执行一次");
				//这里可以写自己要实现的定时业务
			} catch (InterruptedException e) {

				e.printStackTrace();
			}

		}
	}
}

运行结果:

Sat Feb 07 01:33:34 CST 2015线程方式,我是两秒执行一次
Sat Feb 07 01:33:36 CST 2015线程方式,我是两秒执行一次
Sat Feb 07 01:33:38 CST 2015线程方式,我是两秒执行一次
Sat Feb 07 01:33:40 CST 2015线程方式,我是两秒执行一次
Sat Feb 07 01:33:42 CST 2015线程方式,我是两秒执行一次

还有很多方式可以实现工作任务,例如java.util.concurrent.ScheduledThreadPoolExecutor,这里就不在举例。

一定要注意定时任务的并发性,避免发生并发错误。



时间: 2024-07-30 13:52:58

跟着实例学习java多线程9-定时任务实例的相关文章

跟着实例学习java多线程-2

上一篇文章我们通过一个实例来说明了并发编程为什么要做同步处理,下面我们再来巩固一下. 对象如果拥有可变状态的变量,并且被多线程访问,那么这个时候我们要对可变状态变量的状态改变做原子操作处理. 锁机制是保证这样的操作的一个有效的方法,它可以保证变量的状态在被更新时是在一个原子操作中进行的. java提供了一种内置锁机制来支持原子性:同步代码块(Synchronized Block). 同步代码块包括两个部分:一个是作为锁的对象引用,一个是作为由这个锁保护的代码块. 让我们在来回忆上一篇文章最后的问

跟着实例学习java多线程7-对象的组合发布

我们学习线程安全与同步的知识目的就是要实现一些可复用组件或编写出更大的程序. java中类是对象抽象,那么怎么实现一个线程安全类是我们必须要知道的并正确使用的技术. 在设计线程安全类的过程中,需要包含以下三个基本元素: 找出构成对象状态的所有变量. 找出约束状态变量的不变性条件. 建立对象状态的并发访问管理策略. package com.home.thread.thread7; import com.home.thread.safe; public class Counter { /** * @

跟着实例学习java多线程6-如何正确发布线程安全的对象

我们前面所讲的一切其实都只是为了一个目标那就是能正确发布一个线程安全的对象. 一:线程封闭 这个很好理解如果一个变量是在一个线程中完成的状态改变,那么这个变量肯定是线程安全的. 我们常使用的是栈封闭和ThreadLocal类. 在java运行时内存区中有一个虚拟机栈,栈封闭说的就是这个栈,这个栈是线程私有的,它的生命周期与线程相同.虚拟机栈描述描述的是java方法执行的内存模型:每个方法被执行的时候会同时创建一个栈帧用于存储局部变量.操作数栈等.每一个方法被调用直至执行完成的过程,就对应着一个栈

跟着实例学习java多线程8-同步容器类的问题

我们知道java有很多线程安全的容器类,我们也知道如果把可变状态的管理交给这些线程安全类来管理就可以实现线程安全,但是我们还可能遇到不可想象的问题. 例如: package com.home.thread.thread8; import java.util.Vector; /** * @author gaoxu * 实践出真知! */ public class VectorQueue { private static VectorQueue instance; private static Ve

跟着实例学习java多线程5-初识volatile变量

同步机制可以保证原子操作和内存可见性,但是同步机制对变量的访问性能是我们不得不考虑的问题,java语言提供了一种弱同步机制,volatile变量. 它的原理大致是这样的,当把变量声明为volatile类型后,编译器与运行时都会注意到这个变量是共享的,因此不会将变量上的操作与其他内存操作一起重排序.volatile变量不会被缓存在寄存器或者对其他处理器不可见的地方,因此在读取volatile类型的变量总会返回最新写入的值.(参考<java并发编程实践>一书) 让我们来看一个实例: package

跟着实例学习java多线程-3

同步代码块是一种有效实现操作原子性的方法,上一章我们讲了一些同步的原子操作的基础. 现在我们回忆一下上一章的两个问题. 1:不同的synchronized的写法有什么区别,又该怎么写创建线程的代码呢? 以class实例对象作为锁的写法 写法1 package com.home.thread; /** * @author gaoxu * */ public class SafeThread { @safe public void testPrint(){ synchronized(SafeThre

跟着实例学习java多线程4-内存可见性

前三篇我们主要说了多线程访问共享可变状态时需要进行正确的同步处理,保证同一时刻只有一个线程访问相同的数据,我们使用synchronized关键字来实现原子性操作. 今天我们在来认识一下同步的另一个重要方面:内存可见性,这个概念其实很好理解,就是保证在同一个时刻,共享可变状态对访问它的线程呈现出自己最新的状态变化. 我们经常遇到的情景是这样的,一个全局变量计数器,一个线程负责更新该数值,另一些线程获取这个值,那么可见性就是获取值的线程,可以获取到更新线程更新的最新的值. 让我们先来看一个例子,在没

[JAVA 多线程] 生产者消费者实例

正好有人问,就直接将代码记录下来. 背景:有一个仓库存储货物,存在着生产者和消费者,设计一个可以并发的实现. 设计思路:设计一个仓库类,类中保存最大的容量限制和当前的count,类中包含生产和消费的方法,并且都是synchronized. 具体代码: package com.test.tiny; public class Store { private final int MAX_SIZE; //最大 private int count; // 当前 public Store(int n) {

Java多线程-实例解析

Java多线程实例 3种实现方法Java中的多线程有三种实现方式:1.继承Thread类,重写run方法.Thread本质上也是一个实现了Runnable的实例,他代表一个线程的实例,并且启动线程的唯一方法就是通过Thread类的start方法.2.实现Runnable接口,并实现该接口的run()方法.创建一个Thread对象,用实现的Runnable接口的对象作为参数实例化Thread对象,调用此对象的start方法.3.实现Callable接口,重写call方法.Callable接口与Ru