java 多线程 出现数据重复调用问题

JustDoIt_NotLast 2013-12-29 12:50:36
线程操作过程描述:
1、线程查询数据库表(table1)数据,并遍历修改记录状态(防止出现数据重复调用)。(此操作加入了同步锁)
2、调用接口,获取返回的状态。
3、把数据插入到数据库(table2)中,并删除table1中相应的数据。

贴代码:
数据操作类 messageMgrFacadeImpl
public synchronized List findPushList(HashMap searchMap) {
// TODO Auto-generated method stub
this.status = transactionManager.getTransaction(definition);
definition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
//获取全部的未发送信息
List list = this.queryForList("findPushList",
searchMap);
try {
//修改信息状态
for(int n=0;n<list.size();n++){
HashMap listMap = (HashMap)list.get(n);
searchMap.put("smsId", listMap.get("SMS_ID"));
this.update("updatePushListById",searchMap);
}
transactionManager.commit(status);
} catch (Exception e) {
transactionManager.rollback(status);
throw new RuntimeException(e);
}
return list;
}
public synchronized void insertPushLog(HashMap searchMap) {
// TODO Auto-generated method stub
this.status = transactionManager.getTransaction(definition);
definition
.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
try {
this.insert("insertPushLog", searchMap);//添加数据操作记录
this.delete("deletePushList", searchMap);//删除原表记录
transactionManager.commit(status);
} catch (Exception e) {
transactionManager.rollback(status);
throw new RuntimeException(e);
}
}

进程操作类

public class Pusher implements Runnable {
private Message message = new Message();
private HashMap<String, Object> searchMap = new HashMap<String, Object>();
private volatile boolean stop = false;
private MessageMgrFacadeImpl messageMgrFacadeImpl = null;

@Override
public void run() {
// 注入DAO
ApplicationContext context = new ClassPathXmlApplicationContext(
"applicationContext.xml");
messageMgrFacadeImpl = (MessageMgrFacadeImpl) context
.getBean("messageMgrFacade");
searchMap.put("pushTime", DateUtil.getCurrentTimeFull());
searchMap.put("maxCount", Config.getInstance().getMaxCount());
// 获取未发送信息记录
List list = messageMgrFacadeImpl.findPushList(searchMap);
if (list.size() > 0) {
for (int i = 0; i < list.size(); i++) {
HashMap listMap = (HashMap) list.get(i);
System.out.println("++++==" + i + ":" + listMap);
//.....接口操作 HashMap<String, Object> search = new HashMap<String, Object>();
search.put("smsId", smsId);
search.put("errorCode", returnResult);
search.put("errorMsg", ReturnMessage.getInstance()
.getMsgByCode(returnResult));
// 保存返回信息
messageMgrFacadeImpl.insertPushLog(search);
}
} else {
// 休息1s
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
Log4jInitialize.logger(ManagerThread.class).error(
"Pusher.run方法异常:" + e);
e.printStackTrace();
}
}
}
}

问题:
在执行过程中有部分数据出现重复的现象,由于两张表用的是同一主键标识,造成在插入操作中出现主键重复的错误(特别是在数据库表数据有新数据进入的时候)。
...全文
1343 19 打赏 收藏 转发到动态 举报
写回复
用AI写文章
19 条回复
切换为时间正序
请发表友善的回复…
发表回复
JustDoIt_NotLast 2014-01-03
  • 打赏
  • 举报
回复
引用 18 楼 ldh911 的回复:
[quote=引用 16 楼 JustDoIt_NotLast 的回复:] 要求就是不断获取table1数据进行操作后,把操作情况写入table2
你用的是Hibernate么? 两种策略: 1、事务控制下推数据库层面。 Select语句,用“For Update”关键字显式加锁,也即:Select * From 表1 Where 条件 For Update;[事务开始] 每次只Select一行或几行数据,然后立即Update它们的标志位 [事务提交];[事务开始]接下来再逐条处理(插入表2),成功或失败后更新 表1 的标志位[事务提交]。事务过程必须足够短以避免影响性能,但是又不能太短以避免丧失控制效果。 你程序的问题,代码太乱很难看清晰,但事务下推数据库层面的话,不应该存在问题,怀疑你标志位控制存在问题。 2、任务分配上单线程,执行再用多线程。 先准备一个线程池:Pool; 然后准备一个工作者:Worker,处理指定数据并将结果写入表2; 用单独的一个任务调度器:Dispatcher,负责从表1中找出需要更新的数据行,然后实例化一个Worker并将数据传递给它,然后将其丢入线程池。线程池如果待处理任务较多,调度器就休息一会儿再来。 17楼说的第二点值得关注,是否原始数据就存在重复,你所选择的条件字段是否不是主键。[/quote] 谢谢大神的帮助以及思路,结贴啦,哈哈!这几天的观察,我觉得原因应该在我把提取数据放在子线程,造成子线程间不法实现数据共享,按照原来的目的设计思路方向应该是这样的:主线程用于获取数据,把数据分配给子线程进行并行操作。有时间我再试试你的方法,哈哈!
gaofuqi 2014-01-02
  • 打赏
  • 举报
回复
引用 15 楼 JustDoIt_NotLast 的回复:
[quote=引用 13 楼 gaofuqi 的回复:] [quote=引用 12 楼 JustDoIt_NotLast 的回复:] [quote=引用 11 楼 gaofuqi 的回复:] 不是同步的问题,而是你主键生成方式的问题,主要是因为Table2用的是Table1的主键,Table1删数据之后,重新生成的主键可能会跟着之前被删除的数据的主键一样,但是Table2还有这些主键的数据,所以在插入数据的时候会出现主键重复。可以采取以下方式处理: 1.如果Table1的数据删除了,能否把Table2的中和Table1删除数据的主键一样的也删除,如果可以直接删除就好了; 2.如果Table1的数据删除了,还要保留Table2的中和Table1删除数据的主键一样的数据,那就保证Table1的主键是唯一并且是之前没有出现过的,可以使用数据库的Id自增长生成Table1的主键,或者自己定义一个生成唯一主键的方法,一般是时间戳+随机数。
Table1主键是用oracle sequence是不会重复的,我在for循环中加了输出,发现数据是一模一样的,也就是说数据被重复获取,并不是本身数据出现重复[/quote] 哦,那要看你的 List findPushList(HashMap searchMap)是怎么写的,改一下Sql语句应该就可以了解决了;如果觉得Sql不好写,就在查出来List之后,直接将List转为Set。[/quote]
引用 14 楼 ldh911 的回复:
楼主的程序好复杂。 在 run() 函数上写同步原语没意义,因为这个只是实例有效。显然你每次都是: Pusher p = new Pusher(); Thread t = new Thread(p); t.start(); 都是新的实例,非静态函数同步原语不能跨实例控制。 感觉问题还是出在数据库层面,Select的时候有没有for update?
刚刚去掉了同步锁,加了FOR UPDATE NOWAIT SKIP LOCKED,在本地测试了下,问题没出现了,希望挂上服务器也没问题,哈哈!我贴出sql语句(ibatis)

SELECT T.SMS_ID,
		    T.USER_ID,
	        T.SMS_TYPE,
	        T.SCH_ID,
	        T.SMS_TITLE,
	        T.SMS_CONTENT,
	        TO_CHAR(T.SMS_CREATE_DATE,'YYYY-MM-DD HH24:MI:SS') CREATE_DATE,
	        TO_CHAR(T.SMS_PUSH_DATE,'YYYY-MM-DD HH24:MI:SS') PUSH_DATE,
	        T.SCH_LEVEL FROM SMS_PUSH T 
	 WHERE T.SMS_ID IN(
	     SELECT * FROM(SELECT A.SMS_ID
		  FROM SMS_PUSH A
		 WHERE 1=1
		   AND A.SMS_PUSH_STATE IS NULL
		<isNotEmpty property="userType">
		   AND A.SMS_TYPE = #userType#
		</isNotEmpty>
		<isNotEmpty property="pushTime">
		   AND TO_CHAR(A.SMS_PUSH_DATE,'YYYY-MM-DD HH24:MI:SS') < #pushTime#
		</isNotEmpty>
	 	 ORDER BY A.SMS_ID)WHERE ROWNUM <= $maxCount$
 	 ) FOR UPDATE NOWAIT SKIP LOCKED
[/quote] 不要在数据库层加锁,因为这样可能会导致很多不必要的麻烦。可以按照下面的建议试一下: 1.同步的代码有问题,因为你的messageMgrFacadeImpl都不是单例的,代码中的同步没有实际意义,将messageMgrFacadeImpl写成单例模式,或者将messageMgrFacadeImpl中的方法都设置为static的。 2.SQL语句我看了一下应该是不会出现重复数据的,可能是数据库中的数据本身就有问题,执行一下selet t1.SMS_ID from table1 t1, table2 t2 where t1.SMS_ID = t2.SMS_ID; 如果查询有结果数据返回,说明是数据库中的数据本身就有问题,自己根据情况把重复的数据清理掉。还要思考一下为什么会出现数据问题,是之前的操作失误,还是SMS_ID的生成方式确实有问题。
JustDoIt_NotLast 2014-01-02
  • 打赏
  • 举报
回复
引用 14 楼 ldh911 的回复:
楼主的程序好复杂。 在 run() 函数上写同步原语没意义,因为这个只是实例有效。显然你每次都是: Pusher p = new Pusher(); Thread t = new Thread(p); t.start(); 都是新的实例,非静态函数同步原语不能跨实例控制。 感觉问题还是出在数据库层面,Select的时候有没有for update?
很不幸,刚刚挂上去又报错了,还是重复问题 大神能给个解决方案吗? 要求就是不断获取table1数据进行操作后,把操作情况写入table2
JustDoIt_NotLast 2014-01-02
  • 打赏
  • 举报
回复
引用 13 楼 gaofuqi 的回复:
[quote=引用 12 楼 JustDoIt_NotLast 的回复:] [quote=引用 11 楼 gaofuqi 的回复:] 不是同步的问题,而是你主键生成方式的问题,主要是因为Table2用的是Table1的主键,Table1删数据之后,重新生成的主键可能会跟着之前被删除的数据的主键一样,但是Table2还有这些主键的数据,所以在插入数据的时候会出现主键重复。可以采取以下方式处理: 1.如果Table1的数据删除了,能否把Table2的中和Table1删除数据的主键一样的也删除,如果可以直接删除就好了; 2.如果Table1的数据删除了,还要保留Table2的中和Table1删除数据的主键一样的数据,那就保证Table1的主键是唯一并且是之前没有出现过的,可以使用数据库的Id自增长生成Table1的主键,或者自己定义一个生成唯一主键的方法,一般是时间戳+随机数。
Table1主键是用oracle sequence是不会重复的,我在for循环中加了输出,发现数据是一模一样的,也就是说数据被重复获取,并不是本身数据出现重复[/quote] 哦,那要看你的 List findPushList(HashMap searchMap)是怎么写的,改一下Sql语句应该就可以了解决了;如果觉得Sql不好写,就在查出来List之后,直接将List转为Set。[/quote]
引用 14 楼 ldh911 的回复:
楼主的程序好复杂。 在 run() 函数上写同步原语没意义,因为这个只是实例有效。显然你每次都是: Pusher p = new Pusher(); Thread t = new Thread(p); t.start(); 都是新的实例,非静态函数同步原语不能跨实例控制。 感觉问题还是出在数据库层面,Select的时候有没有for update?
刚刚去掉了同步锁,加了FOR UPDATE NOWAIT SKIP LOCKED,在本地测试了下,问题没出现了,希望挂上服务器也没问题,哈哈!我贴出sql语句(ibatis)

SELECT T.SMS_ID,
		    T.USER_ID,
	        T.SMS_TYPE,
	        T.SCH_ID,
	        T.SMS_TITLE,
	        T.SMS_CONTENT,
	        TO_CHAR(T.SMS_CREATE_DATE,'YYYY-MM-DD HH24:MI:SS') CREATE_DATE,
	        TO_CHAR(T.SMS_PUSH_DATE,'YYYY-MM-DD HH24:MI:SS') PUSH_DATE,
	        T.SCH_LEVEL FROM SMS_PUSH T 
	 WHERE T.SMS_ID IN(
	     SELECT * FROM(SELECT A.SMS_ID
		  FROM SMS_PUSH A
		 WHERE 1=1
		   AND A.SMS_PUSH_STATE IS NULL
		<isNotEmpty property="userType">
		   AND A.SMS_TYPE = #userType#
		</isNotEmpty>
		<isNotEmpty property="pushTime">
		   AND TO_CHAR(A.SMS_PUSH_DATE,'YYYY-MM-DD HH24:MI:SS') < #pushTime#
		</isNotEmpty>
	 	 ORDER BY A.SMS_ID)WHERE ROWNUM <= $maxCount$
 	 ) FOR UPDATE NOWAIT SKIP LOCKED
MiceRice 2014-01-02
  • 打赏
  • 举报
回复
引用 16 楼 JustDoIt_NotLast 的回复:
要求就是不断获取table1数据进行操作后,把操作情况写入table2
你用的是Hibernate么? 两种策略: 1、事务控制下推数据库层面。 Select语句,用“For Update”关键字显式加锁,也即:Select * From 表1 Where 条件 For Update;[事务开始] 每次只Select一行或几行数据,然后立即Update它们的标志位 [事务提交];[事务开始]接下来再逐条处理(插入表2),成功或失败后更新 表1 的标志位[事务提交]。事务过程必须足够短以避免影响性能,但是又不能太短以避免丧失控制效果。 你程序的问题,代码太乱很难看清晰,但事务下推数据库层面的话,不应该存在问题,怀疑你标志位控制存在问题。 2、任务分配上单线程,执行再用多线程。 先准备一个线程池:Pool; 然后准备一个工作者:Worker,处理指定数据并将结果写入表2; 用单独的一个任务调度器:Dispatcher,负责从表1中找出需要更新的数据行,然后实例化一个Worker并将数据传递给它,然后将其丢入线程池。线程池如果待处理任务较多,调度器就休息一会儿再来。 17楼说的第二点值得关注,是否原始数据就存在重复,你所选择的条件字段是否不是主键。
MiceRice 2013-12-31
  • 打赏
  • 举报
回复
楼主的程序好复杂。 在 run() 函数上写同步原语没意义,因为这个只是实例有效。显然你每次都是: Pusher p = new Pusher(); Thread t = new Thread(p); t.start(); 都是新的实例,非静态函数同步原语不能跨实例控制。 感觉问题还是出在数据库层面,Select的时候有没有for update?
gaofuqi 2013-12-31
  • 打赏
  • 举报
回复
引用 12 楼 JustDoIt_NotLast 的回复:
[quote=引用 11 楼 gaofuqi 的回复:] 不是同步的问题,而是你主键生成方式的问题,主要是因为Table2用的是Table1的主键,Table1删数据之后,重新生成的主键可能会跟着之前被删除的数据的主键一样,但是Table2还有这些主键的数据,所以在插入数据的时候会出现主键重复。可以采取以下方式处理: 1.如果Table1的数据删除了,能否把Table2的中和Table1删除数据的主键一样的也删除,如果可以直接删除就好了; 2.如果Table1的数据删除了,还要保留Table2的中和Table1删除数据的主键一样的数据,那就保证Table1的主键是唯一并且是之前没有出现过的,可以使用数据库的Id自增长生成Table1的主键,或者自己定义一个生成唯一主键的方法,一般是时间戳+随机数。
Table1主键是用oracle sequence是不会重复的,我在for循环中加了输出,发现数据是一模一样的,也就是说数据被重复获取,并不是本身数据出现重复[/quote] 哦,那要看你的 List findPushList(HashMap searchMap)是怎么写的,改一下Sql语句应该就可以了解决了;如果觉得Sql不好写,就在查出来List之后,直接将List转为Set。
JustDoIt_NotLast 2013-12-31
  • 打赏
  • 举报
回复
引用 11 楼 gaofuqi 的回复:
不是同步的问题,而是你主键生成方式的问题,主要是因为Table2用的是Table1的主键,Table1删数据之后,重新生成的主键可能会跟着之前被删除的数据的主键一样,但是Table2还有这些主键的数据,所以在插入数据的时候会出现主键重复。可以采取以下方式处理: 1.如果Table1的数据删除了,能否把Table2的中和Table1删除数据的主键一样的也删除,如果可以直接删除就好了; 2.如果Table1的数据删除了,还要保留Table2的中和Table1删除数据的主键一样的数据,那就保证Table1的主键是唯一并且是之前没有出现过的,可以使用数据库的Id自增长生成Table1的主键,或者自己定义一个生成唯一主键的方法,一般是时间戳+随机数。
Table1主键是用oracle sequence是不会重复的,我在for循环中加了输出,发现数据是一模一样的,也就是说数据被重复获取,并不是本身数据出现重复
gaofuqi 2013-12-30
  • 打赏
  • 举报
回复
不是同步的问题,而是你主键生成方式的问题,主要是因为Table2用的是Table1的主键,Table1删数据之后,重新生成的主键可能会跟着之前被删除的数据的主键一样,但是Table2还有这些主键的数据,所以在插入数据的时候会出现主键重复。可以采取以下方式处理: 1.如果Table1的数据删除了,能否把Table2的中和Table1删除数据的主键一样的也删除,如果可以直接删除就好了; 2.如果Table1的数据删除了,还要保留Table2的中和Table1删除数据的主键一样的数据,那就保证Table1的主键是唯一并且是之前没有出现过的,可以使用数据库的Id自增长生成Table1的主键,或者自己定义一个生成唯一主键的方法,一般是时间戳+随机数。
tony4geek 2013-12-30
  • 打赏
  • 举报
回复
debug 看看具体原因
骑士的崛起 2013-12-30
  • 打赏
  • 举报
回复
这种问题加点日志或debug调试下就能找到原因,我估计事务使用有问题。
别闹腰不好 2013-12-30
  • 打赏
  • 举报
回复
数据库加锁,在一个线程用到数据,其他线程不能对这些数据操作,可以等或对出。 但是不针对插入数据,插入数据主键重复的错误,就在生成主键的方法加同步,就可以解决
JustDoIt_NotLast 2013-12-29
  • 打赏
  • 举报
回复
引用 6 楼 huxiweng 的回复:
insert 完之后要修改那个 对应记录的状态,这两个也需要同步。
这些都有的 但是就是出错
teemai 2013-12-29
  • 打赏
  • 举报
回复
insert 完之后要修改那个 对应记录的状态,这两个也需要同步。
JustDoIt_NotLast 2013-12-29
  • 打赏
  • 举报
回复
引用 3 楼 huxiweng 的回复:
List list = messageMgrFacadeImpl.findPushList(searchMap); 这里加上同步试试
@Override
	public void run() {
		// 注入DAO
		ApplicationContext context = new ClassPathXmlApplicationContext(
				"applicationContext.xml");
		messageMgrFacadeImpl = (MessageMgrFacadeImpl) context
				.getBean("doone-education-messageMgrFacade");
		searchMap.put("pushTime", DateUtil.getCurrentTimeFull());
		searchMap.put("maxCount", Config.getInstance().getMaxCount());
		// 获取未发送信息记录
		synchronized (this) {
			List list = messageMgrFacadeImpl.findPushList(searchMap);
			if (list.size() > 0) {
				for (int i = 0; i < list.size(); i++) {
					HashMap listMap = (HashMap) list.get(i);
					System.out.println("++++==" + i + ":" + listMap);
					//......接口操作
					HashMap<String, Object> search = new HashMap<String, Object>();
					search.put("smsId", smsId);
					search.put("errorCode", returnResult);
					search.put("errorMsg", ReturnMessage.getInstance()
							.getMsgByCode(returnResult));
					// 保存返回信息
					messageMgrFacadeImpl.insertPushLog(search);
				}
			} else {
				// 休息1s
				try {
					TimeUnit.SECONDS.sleep(1);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					Log4jInitialize.logger(ManagerThread.class).error(
							"Pusher.run方法异常:" + e);
					e.printStackTrace();
				}
			}
		}
	}
我把有关操作全放入一个锁,但是还是出现一样的问题!
JustDoIt_NotLast 2013-12-29
  • 打赏
  • 举报
回复
public synchronized List findPushList(HashMap searchMap) 
这里加不行么?
teemai 2013-12-29
  • 打赏
  • 举报
回复
List list = messageMgrFacadeImpl.findPushList(searchMap); 这里加上同步试试
JustDoIt_NotLast 2013-12-29
  • 打赏
  • 举报
回复
引用 1 楼 huxiweng 的回复:
没看到你哪里调用Pusher多线程去执行操作啊
我采用timer定时器 1分钟执行一次 这是线程管理类
public class ManagerThread extends Thread {
	private volatile boolean stop = false;
	private final LinkedList<Thread> sendThreadList = new LinkedList<Thread>();
	private static final Object sendLock = new Object();
	private int sendThreadCount = 1;

	public ManagerThread() {
		this.setDaemon(true);
	}

	public ManagerThread(int sendThreadCount) {
		this.sendThreadCount = sendThreadCount;
		this.setDaemon(true);
	}

	@Override
	public void run() {
		while (!stop) {
			try {
				synchronized (sendLock) {
					for (int i = 0; i < sendThreadList.size(); i++) {
						Thread t = sendThreadList.get(i);
						if (t.getState() == Thread.State.TERMINATED) {
							sendThreadList.remove(i);
							break;
						}
					}
					if (sendThreadList.size() < this.sendThreadCount) {
						Pusher p = new Pusher();
						Thread t = new Thread(p);
						t.start();
						sendThreadList.add(t);
					}
				}
				// 睡眠30s
				TimeUnit.SECONDS.sleep(30);

			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}
	public void myStop() {
		synchronized (sendLock) {
			for (int i = 0; i < this.sendThreadList.size(); i++) {
				Thread t = sendThreadList.get(i);
				if (t != null) {
					t.stop();
				}
			}
			this.sendThreadList.clear();
		}
		this.stop = true;

	}
}
sendThreadCount是设定的线程数
public class PushMain {
	private ManagerThread tm;
	private boolean stop = false;

	public void start()
	{
		tm = new ManagerThread(Config.getInstance().getSendThreadCount());
		tm.start();
	}

	public void stop()
	{
		if (tm != null){
			tm.myStop();
		}
		stop = true;
	}
}
task PushMain pushMain = new PushMain(); pushMain.start();
teemai 2013-12-29
  • 打赏
  • 举报
回复
没看到你哪里调用Pusher多线程去执行操作啊

67,513

社区成员

发帖
与我相关
我的任务
社区描述
J2EE只是Java企业应用。我们需要一个跨J2SE/WEB/EJB的微容器,保护我们的业务核心组件(中间件),以延续它的生命力,而不是依赖J2SE/J2EE版本。
社区管理员
  • Java EE
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧