行云无鸣

2010-06-26

采用Spring AOP控制线程的非正常退出

Filed under: 乱语 — 标签:, , , — hellyguo @ 16:58

项目中碰到线程的非正常退出,导致某一块功能缺失,是非常危险的。
虽然可以采用定时刷新线程状态这种方式来处理监控,但总是很累赘。
昨日受到启发,可以采用AOP来控制线程的非异常退出。
基本思路:

  1. 当run方法正常结束时,判断是否是业务或系统主动要求的退出,主动要求的,不再重启;非主动要求的,等待此次退出后重启线程;
  2. 当run方法异常结束时,判断是否有必要重启,有必要,重启;没有必要,不再重启。

方法如下:
Spring配置文件

<bean id="demoThread" parent="singleExecThread">
<property name="target">
<ref local="demoThreadBody" />
</property>
</bean>

<bean id="demoThreadBody" class="CommonSingleThread"
autowire="byName">
<property name="runTarget">
<ref local="demoThreadTarget" />
</property>
<property name="idName" value="demoThread" />
</bean>

<bean id="demoThreadTarget" class="DemoThread"
autowire="byName">
<property name="id" value="0" />
</bean>

<bean id="throwsAdvice"
class="ThrowsFromCommonSingleThreadRunAdvice"></bean>

<bean id="throwsAdvisor"
class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor">
<property name="advice">
<ref local="throwsAdvice" />
</property>
<property name="mappedName">
<value>run</value>
</property>
</bean>

<bean id="afterAdvice" class="AfterCommonSingleThreadRunAdvice"></bean>

<bean id="afterAdvisor"
class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor">
<property name="advice">
<ref local="afterAdvice" />
</property>
<property name="mappedName">
<value>run</value>
</property>
</bean>

<bean id="singleExecThread" class="org.springframework.aop.framework.ProxyFactoryBean"
abstract="true">
<property name="proxyInterfaces">
<value>ICommonSingleThread</value>
</property>
<property name="interceptorNames">
<list>
<value>throwsAdvisor</value>
<value>afterAdvisor</value>
</list>
</property>
</bean>

DemoThread

public class DemoThread implements Runnable, Constants {
private int id;

public void setId(int id) {
this.id = id;
}

public void run() {
LOGGER.info("start");
if (id % 2 == 0) {
throw new RuntimeException();
}
LOGGER.info("quit");
}

}

ICommonSingleThread

/**
* 单线程控制接口
* ICommonSingleThread Jun 26, 2010 10:39:06 AM
*
* @author helly
*
*/
public interface ICommonSingleThread extends Runnable {

/**
* 设置线程控制标志
*
* @param active
*/
public abstract void setActive(boolean active);

/**
* 获取线程控制标志
*
*/
public abstract boolean isActive();

/**
* 设置线程执行主体
*
* @param target
*/
public abstract void setRunTarget(Runnable runTarget);

/**
* spring配置文件中唯一id
*
* @param id
*/
public abstract void setIdName(String beanId);

/**
* 获取线程状态
*
* @return
*/
public abstract boolean getThreadState();

/**
* 初始化
*/
public abstract void init();

/**
* 销毁
*/
public abstract void destory();

}

CommonSingleThread

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

/**
* 通用线程线程
* CommonSingleThread Jun 23, 2010 8:57:04 PM
*
* @author helly
*
*/
public class CommonSingleThread implements Runnable, Constants,
ICommonSingleThread, ApplicationContextAware {
// Spring ApplicationContext
protected static ApplicationContext appcontext;
// 对象锁
protected Object lock = new Object();
// 线程控制标志
protected boolean active = true;
// 线程
protected Thread thread;
// 运行实例
protected Runnable runTarget;
// spring代理对象
protected Runnable proxyObject;
// spring配置文件中唯一id
protected String idName;

/**
* 设置线程控制标志
*
* @param active
*/
public void setActive(boolean active) {
synchronized (lock) {
this.active = active;
}
}

/**
* 获取线程控制标志
*
* @return
*/
public boolean isActive() {
return active;
}

/**
* 设置线程执行主体
*
* @param target
*/
public void setRunTarget(Runnable runTarget) {
this.runTarget = runTarget;
}

/**
* spring配置文件中唯一id
*
* @param id
*/
public void setIdName(String idName) {
this.idName = idName;
}

/**
* 获取线程状态
*
* @return
*/
public boolean getThreadState() {
synchronized (lock) {
if (thread == null) {
return false;
} else {
return thread.isAlive();
}
}
}

/**
* 初始化
*/
public void init() {
synchronized (lock) {
// 设置代理对象
if (proxyObject == null) {
setupProxyObject();
}
if (!getThreadState()) {
destory();
active = true;
createNewThreadAndStart();
}
}
}

/**
* 根据idName获取被spring代理过的对象
*/
private void setupProxyObject() {
proxyObject = (Runnable) appcontext.getBean(idName);
}

/**
* 创建新线程
*/
protected void createNewThreadAndStart() {
// 创建线程,Runnable主体必须用代理对象创建
// 原因:如果用内部的runTarget对象创建,无法调用到Advice
// 只有调用proxyObject对象,才能通过AOP调用Advice
thread = new Thread(proxyObject, runTarget.getClass().getSimpleName());
thread.start();
}

/**
* 线程执行体
*/
public void run() {
while (active) {
try {
runTarget();
} catch (Exception e) {
LOGGER.warn(e.getMessage(), e);
throw new RuntimeException(e);
}
break;
}
}

/**
* 执行
*/
protected void runTarget() {
runTarget.run();
}

/**
* 销毁
*/
public void destory() {
synchronized (lock) {
if (thread != null) {
active = false;
while (true) {
try {
thread.join();
active = true;
break;
} catch (InterruptedException e) {
continue;
}
}
}
}
}

/**
* 获取spirng上下文
*
* @param arg0
* @throws BeansException
*/
public void setApplicationContext(ApplicationContext context)
throws BeansException {
if (appcontext == null) {
appcontext = context;
}
}
}

AfterAdvice

import java.lang.reflect.Method;

import org.springframework.aop.AfterReturningAdvice;

/**
* CommonSingleThread正常退出善后类
* AfterCommonSingleThreadRunAdvice Jun 26, 2010 10:58:35 AM
*
* @author helly
*
*/
public class AfterCommonSingleThreadRunAdvice implements AfterReturningAdvice,
Constants {

public void afterReturning(Object retValue, Method method, Object[] args,
Object target) throws Throwable {
LOGGER.debug("进入AfterCommonSingleThreadRunAdvice");
// 获取对象
final ICommonSingleThread cst = (ICommonSingleThread) target;
// 获取线程名称
final String thName = Thread.currentThread().getName();
// 校验是否需要重新启动
// 如若active状态为true,需要重启
// 否则,不需要
if (cst.isActive()) {
LOGGER.info("需要重启" + thName);
Runnable runit = new Runnable() {
public void run() {
LOGGER.info("重启" + thName);
// 必须等待
// 原因:要预留时间给原异常线程退出。
// 如若不等待,原线程还存活,init方法无法启动新线程
try {
Thread.sleep(5000l);
} catch (InterruptedException e) {
}
// 启动新线程
cst.init();
LOGGER.info("重启" + thName + "完成");
}
};
new Thread(runit, "RestartNormalExitThreadFor:" + thName).start();
}
}

}

ThrowsAdvice

import java.lang.reflect.Method;

import org.springframework.aop.ThrowsAdvice;

/**
* CommonSingleThread异常退出善后类
* ThrowsFromCommonSingleThreadRunAdvice Jun 26, 2010 10:55:18 AM
*
* @author helly
*
*/
public class ThrowsFromCommonSingleThreadRunAdvice implements ThrowsAdvice,
Constants {

/**
* 当异常退出时,进入此方法
*
* @param method
* @param args
* @param target
* @param ex
*/
public void afterThrowing(Method method, Object[] args, Object target,
Exception ex) {
LOGGER.debug("进入ThrowsFromCommonSingleThreadRunAdvice");
// 获取对象
final ICommonSingleThread cst = (ICommonSingleThread) target;
// 获取线程名称
final String thName = Thread.currentThread().getName();
// 校验是否需要重新启动
// 如若active状态为true,需要重启
// 否则,不需要
if (cst.isActive()) {
LOGGER.info("需要重启" + thName);
Runnable runit = new Runnable() {
public void run() {
LOGGER.info("重启" + thName);
// 必须等待
// 原因:要预留时间给原异常线程退出。
// 如若不等待,原线程还存活,init方法无法启动新线程
try {
Thread.sleep(5000l);
} catch (InterruptedException e) {
}
// 启动新线程
cst.init();
LOGGER.info("重启" + thName + "完成");
}
};
new Thread(runit, "RestartExceptionExitThreadFor:" + thName).start();
}
}
}

Test

import org.testng.annotations.BeforeTest;
import org.testng.annotations.Test;

public class DemoThreadTest {
private ICommonSingleThread th;

@Test
public void test() throws Exception {
th.init();
Thread.sleep(1000l);
}

@BeforeTest
public void beforeTest() {
Log4jLoader.loadLog4j();
SpringLoader.loadSpring();
th = (ICommonSingleThread) SpringLoader.context.getBean("demoThread");
}

}

2009-08-07

Spring子事务的使用

Filed under: 未分类 — 标签:, — hellyguo @ 15:19

一、    前提
spring事务声明
如下:

do* PROPAGATION_REQUIRED
deal* PROPAGATION_REQUIRES_NEW

二、    一个类情况下,子事务的使用
定义
public interface AService{
public void doXXX();
public void dealXXX();
}

public class AServiceImpl implements AService{
public void doXXX(){
return ;
}

public void dealXXX(){
return ;
}
}

实例化
interfase AService -实现-> class AServiceImpl -实例化-> object aService -Spring事务代理-> proxy proxy$x
调用
proxy$x.doXXX()
如此调用,如果目前存在事务,则使用当前事务;如果不存在事务,则新建事务
proxy$x.dealXXX()
如此调用,如果目前存在事务,则挂起当前事务,新建事务;如果不存在事务,则新建事务
aService.doXXX()
与事务完全无关,不知事务为何
aService.dealXXX()
与事务完全无关,不知事务为何
变化1
如果类AServiceImpl的实现改为
public class AServiceImpl implements AService{
public void doXXX(){
try{
dealXXX();
}catch(Exception e){
}
return ;
}

public void dealXXX(){
return ;
}
}

请注意:
doXXX方法的执行过程中,不会因调用dealXXX方法新建子事务。因为调用的dealXXX方法是直接通过aService执行的,而不是经由spring包装代理后的proxy$x!!!

变化2
如果类AServiceImpl的实现改为
public class AServiceImpl implements AService{
public void doXXX(){
try{
Content.getInstance().getAService().dealXXX();
}catch(Exception e){
}
return ;
}

public void dealXXX(){
return ;
}
}

注:Content为一单例对象,此对象内部由spring注入了Aservice对象——proxy$x。
请注意:
doXXX方法的执行过程中,调用dealXXX方法将会新建子事务。因为调用的dealXXX方法不是直接通过aService执行的,而是经由spring包装代理后的proxy$x!!!
三、    两个 (及多个) 类情况下,子事务的使用
定义
public interface AService{
public void doXXX();
}

public interface BService{
public void dealYYY();
}

public class AServiceImpl implements AService{
public void doXXX(){
return ;
}
}

public class BServiceImpl implements BService{
public void dealYYY(){
return ;
}
}

实例化
interfase AService -实现-> class AServiceImpl -实例化-> object aService -Spring事务代理-> proxy proxy$x
interfase BService -实现-> class BServiceImpl -实例化-> object bService -Spring事务代理-> proxy proxy$y
调用
proxy$x.doXXX()
如此调用,如果目前存在事务,则使用当前事务;如果不存在事务,则新建事务
aService.doXXX()
与事务完全无关,不知事务为何
proxy$y.dealYYY()
如此调用,如果目前存在事务,则挂起当前事务,新建事务;如果不存在事务,则新建事务
bService.dealYYY()
与事务完全无关,不知事务为何
变化1
如果类AServiceImpl的实现改为
public class AServiceImpl implements AService{
public void doXXX(){
try{
BService bService = new BServiceImpl();
bService.dealXXX();
}catch(Exception e){
}
return ;
}
}

请注意:
doXXX方法的执行过程中,不会因调用BService的dealYYY方法新建子事务。因为调用的dealYYY方法是直接通过bService执行的,而不是经由spring包装代理后的proxy$y!!!

变化2
如果类AServiceImpl的实现改为
public class AServiceImpl implements AService{
private BService bService;

public void setBService(){
this.bService = bService;
}

public void doXXX(){
try{
bService.dealXXX();
}catch(Exception e){
}
return ;
}
}

请注意:
doXXX方法的执行过程中,调用bService的dealYYY方法将会新建子事务。因为调用的dealYYY方法不是直接通过bService执行的,而是经由spring包装代理后的proxy$y!!!

2008-11-06

系统运行中报:Resource temporarily unavailable

Filed under: 未分类 — 标签:, , , — hellyguo @ 16:43

2008-11-06 12:49:50 [ERROR] [org.springframework.transaction.CannotCreateTransactionException][Could not open JDBC Connection for
transaction; nested exception is java.sql.SQLException: Io 异常: Connection refused(DESCRIPTION=(TMP=)(VSNNUM=153094144)(ERR=12500)
(ERROR_STACK=(ERROR=(CODE=12500)(EMFI=4))(ERROR=(CODE=12540)(EMFI=4))(ERROR=(CODE=12560)(EMFI=4))(ERROR=(CODE=510)(EMFI=4))(ERROR=(B
UF=’IBM/AIX RISC System/6000 Error: 11: Resource temporarily unavailable’))))DSRA0010E: SQL 状态 = null,错误代码 = 17,002DSRA0010E:
SQL 状态 = null,错误代码 = 17,002]

初步判断是系统资源不足,查看系统:

#ulimit -a
time(seconds) unlimited
file(blocks) 2000
data(kbytes) 131072
stack(kbytes) 32768
memory(kbytes) 32768
coredump(blocks) 2097151
nofiles(descriptors) 2000

果然,打开文件数有限制
修改之

#ulimit -a
time(seconds) unlimited
file(blocks) unlimited
data(kbytes) 131072
stack(kbytes) 32768
memory(kbytes) 32768
coredump(blocks) 2097151
nofiles(descriptors) 2000

2008-06-02

一次DB2死锁解决

Filed under: 未分类 — 标签:, , , — hellyguo @ 15:47


<prop key="remove*">PROPAGATION_REQUIRED,-Exception</prop>
<prop key="cancel*">PROPAGATION_REQUIRED,-Exception</prop>
<prop key="process*">PROPAGATION_REQUIRED,-Exception</prop>
<prop key="insert*">PROPAGATION_REQUIRED,-Exception</prop>
<prop key="delete*">PROPAGATION_REQUIRED,-Exception</prop>
<prop key="update*">PROPAGATION_REQUIRED,-Exception</prop>
<prop key="do*">PROPAGATION_REQUIRED,-Exception</prop>
<prop key="deal*">PROPAGATION_REQUIRES_NEW,-Exception</prop>
<prop key="get*">PROPAGATION_REQUIRED,readOnly</prop>
<prop key="*">PROPAGATION_REQUIRED, readOnly</prop>

这是在Spring中的事务配置

在某个业务环节上,需要在一个大事务(称为A,方法名非deal起头)中循环执行多个子事务(称为a,方法名用deal起头)
这些子事务,都是在一个查询获得的List中取出数据处理
这时,从数据库层面上,由于DB2的锁机制,A获得了一个锁,保证数据不被篡改。当a事务要求修改数据,就被锁限制,只能等待。
而从应用程序层面上,由于顺序执行的关系,A必须等待a的执行完毕后,才能进入下一步操作,也只能等待。
这样,就造成了两个层面上的死锁。

解决方法:
将A中的查询事务也抽取为小事务(称为b,方法名用deal起头),先执行b,获得数据List。再按查询得到的List,循环执行a,问题得到解决。

2008-05-25

Spring中配置null值

Filed under: 未分类 — 标签:, , — hellyguo @ 08:14

项目延续的问题,在Spring配置文件中出现了交叉引用的情况。不得已,得将一个对象的引用置为null。

查了查Reference,得到我想要的结果:
<property name="test">
<null/>
</property>

%d 博主赞过: