0 0

一个多线程查询数据库的奇怪问题0

表中有50W笔数据,表结构很简单,主键id(32位随机码),外加一个name字段,都是varchar2类型,想取出全部数据并封装到JavaBean中,但是一次查询耗时太长,想使用多线程的方法取出这些数据,但是用下面的多线程代码运行后发现,预期能取出全部50W笔,但是实际只取出499992笔,少了8笔,不知道是什么原因。

思路是先创建容量为50W的公共List,把这个List当类成员变量传递到线程对象里,然后创建100个线程,每个线程单独创建数据库连接执行查询,然后把各自查出来的结果集封装到JavaBean然后填充到公共list中,代码如下:

package jdbc;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;

public class Test2 {
	
	public static void main(String[] args) {
		Test2 t2 = new Test2();
		List<A> list_db = t2.getAll();
		
	}
	
	public List<A> getAll(){
		//Connection conn = null;
		List<A> list_db = new ArrayList<A>(500000);
		try{
			long start = System.currentTimeMillis();
			//Class.forName(DRIVER_CLASSNAME);
			//conn = DriverManager.getConnection(URL, USER, PASSWORD);
			
			QueryThread qt = null;
			for(int i = 0; i < 100; i++){
				qt = new QueryThread();
				//qt.setConn(conn);
				qt.setList_db(list_db);
				qt.settName("thread" + i);
				qt.setStart((i*5000+1));
				qt.setEnd((i+1) * 5000);
				Thread dao = new Thread(qt);
				dao.start();
			}
			while(true){
				if(list_db.size() == 500000){
					break;
				}
				System.out.println("..." + list_db.size());
				Thread.sleep(1000);
				
			}
			long end = System.currentTimeMillis();
			System.out.println("封装完毕!耗时:" + (end - start)/1000 + "秒!");
			System.out.println("查询完成,结果集:" + list_db.size());
		}catch(Exception e){
			e.printStackTrace();
		}finally{
//			if(conn != null){
//				try {
//					conn.close();
//				} catch (SQLException e) {
//					e.printStackTrace();
//				}
//			}
		}
		return list_db;
	}
}


package jdbc;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;

public class QueryThread extends Thread{
	public static String DRIVER_CLASSNAME = "x";
	public static String URL = "x";
	public static String USER = "x";
	public static String PASSWORD = "x";
	
	private List<A> list_db;
	
	public void setList_db(List<A> list_db) {
		this.list_db = list_db;
	}

	private int start;
	private int end;
	
	public void setStart(int start) {
		this.start = start;
	}

	public void setEnd(int end) {
		this.end = end;
	}

	private String tName;
	
	public void settName(String tName) {
		this.tName = tName;
	}

	@Override
	public void run(){
		Connection conn = null;
		PreparedStatement stmt = null;
		try{
			System.out.println("线程[" + this.tName + "]开始执行!");
			Class.forName(DRIVER_CLASSNAME);
			conn = DriverManager.getConnection(URL, USER, PASSWORD);
			stmt = conn.prepareStatement("select t2.* from (select t1.*,rownum rn from (select * from testa t order by t.taid) t1 where rownum <= ?) t2 where t2.rn >= ? ");
			stmt.setInt(1, end);
			stmt.setInt(2, start);
			ResultSet rs = stmt.executeQuery();
			A a = null;
			int count = 0;
			while(rs.next()){
				a = new A();
				a.settId(rs.getString(1));
				a.setaName(rs.getString(2));
				this.list_db.add(a);
				count++;
				//System.out.println("线程[" + this.tName + "] 遍历中结果集:" + this.list_db.size());
			}
			System.out.println("线程[" + this.tName + "]执行结束! 结果集:" + count);
		}catch(Exception e){
			System.out.println("线程[" + this.tName + "]插入失败!" + e.getMessage());
			e.printStackTrace();
		}finally{
			if(stmt != null){
				try {
					stmt.close();
				} catch (SQLException e2) {
					System.out.println("线程[" + this.tName + "]关闭stmt 失败!" + e2.getMessage());
					e2.printStackTrace();
				}
			}
			if(conn != null){
				try {
					conn.close();
				} catch (SQLException e) {
					e.printStackTrace();
				}
			}
		}
	}
}


问题补充:100个线程每个线程查询5000笔,用分页和动态传参的方式实现
2014年3月21日 16:00

8个答案 按时间排序 按投票排序

0 0

因为ArrayList线程并不安全,要线程安全的话你可以用ConcurrentLinkedQueue。不过话说回来,50w的数据,一般是不需要拆库分表的,磁盘IO你单线程扫就行了,多线程反而可能更慢。

2014年3月24日 22:50
0 0

如果的查的机子只有一个物理硬盘,用不用多线程查数据,基本一样速度 ,还有可能多线程会变慢.

如果数据在变成javabean这个过程时间比较长,可以用多线程,如果不是多核CPU,还是用单线程吧。

而且你这代码有线程安全问题,多线程访问ArrayList出问题,要用CopyOnWriteArrayList
最好用并发包的线程池看下ExecutorService.invokeAll 这个方法,可以返回每个线程的结果。

楼主,好像还不是很清楚什么情况要注意线程安全,还是了解下先

2014年3月24日 14:54
0 0

System.out.println(Runtime.getRuntime().availableProcessors());

2014年3月23日 12:37
0 0

System.out.println(Runtime.getRuntime().availableProcessors());看看几何的cup就就启动几个线程

2014年3月23日 12:37
0 0

1.加个排序字段试试
2.另外线程数要根据 CPU 线程数来定的,多了CPU应付不过来,还要不断切换线程反而慢
3.数据库连接数也要设定好

2014年3月22日 09:07
0 0

1. 100个线程, 是不是太多, 不是线程多就查询快, io是有限的!

2. ArrayList不是线程安全的, 当然数目不对了. 但是也不要用CopyOnWriteArrayList, 因为这个只适合大部分读, 很少量的写.
建议你直接使用Vector, 或者在add的时候, 加锁好了.

2014年3月21日 18:56
0 0

1、ArrayList不是线程安全的,你多线程添加时可能会有问题的。

2、建议你将ArrayList换做java.util.concurrent 下的线程安全的类
例如CopyOnWriteArrayList
http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/CopyOnWriteArrayList.html

3你的机器CPU线程数是多少,最好启动(cpu核心线程数-1)的线程数,我觉得起太多线程反而慢,你可以测试下,结果共享出来,大家看看

2014年3月21日 18:28
0 0

因为没看到具体操作  不知道具体原因
建议你这样试试
   代码的主要逻辑不变 然后变成10个线程 读取100条数据 看看是否也少了 确定一下是不是你写的哪部分逻辑有问题

2014年3月21日 17:13

相关推荐

    QT中sqlite多线程操作4个注意问题

    在多线程环境下使用`QsqlQuery`变量时,有时会遇到奇怪的问题,即使按照上述方法进行了处理,程序运行一段时间后仍可能出现数据库死锁或者异常关闭的情况。此时,可以尝试通过限制`QsqlQuery`变量的作用域来改善这一...

    數據庫程式:尚未完成的<>

    本项目名为“数据库程式:尚未完成的”,它是一个用于实现通用查询功能的数据库工具。然而,项目开发者遇到了一个棘手的问题:一旦出现未返回结果的查询,应用就无法继续进行其他查询操作,这显然不符合正常的功能...

    基于nodejs 的博客园爬虫项目(javascript)

    如果深入做下去,你会发现要面对不同的网页要求,比如有认证的,不同文件格式、编码处理,各种奇怪的url合规化处理、重复抓取问题、cookies 跟随问题、多线程多进程抓取、多节点抓取、抓取调度、资源压缩等一系列...

    Java问题定位技术.pdf

    4.关于并发和多线程 5.幽灵代码 6.常见的Java泥潭 7.JVM 8.关于字符集与编码 9.常用分析工具 10.Java最佳实践 11.关于数据库 12.工程实践 13.常见的案例 附录 A JProfiler内存泄漏精确定位 B SUN JDK自带故障定位 C...

    Jedis API中文使用文档.-比较详细

    为了避免这些问题,可以使用 JedisPool,JedisPool 是一个线程安全的网络连接池。可以用 JedisPool 创建一些可靠 Jedis 实例,可以从池中拿到 Jedis 的实例。 Jedis 的基本操作 Jedis 提供了多种基本操作,包括...

    二十三种设计模式【PDF版】

    你曾经多少次有过这种感觉—你已经解决过了一个问题但就是不能确切知道是在什么地 方或怎么解决的?如果你能记起以前问题的细节和怎么解决它的,你就可以复用以前的经验而不需要重新发现它。然而,我们 并没有很好...

    超级有影响力霸气的Java面试题大全文档

    与cgi的区别在于servlet处于服务器进程中,它通过多线程方式运行其service方法,一个实例可以服务于多个请求,并且其实例一般不会销毁,而CGI对每个请求都产生新的进程,服务完成后就销毁,所以效率上低于servlet。...

    java联想(中文)

    1.11.4 一个独立的领域:应用程序 1.12 分析和设计 1.12.1 不要迷失 1.12.2 阶段0:拟出一个计划 1.12.3 阶段1:要制作什么? 1.12.4 阶段2:开始构建? 1.12.5 阶段3:正式创建 1.12.6 阶段4:校订 1.12.7 计划的...

    微软订阅邮件

    - **如何创建一个动态的Tree控件**(P.23) - 创建动态Tree控件可以通过动态添加和删除节点以及监听相关的控件事件实现。 - **如何重载MRU文件**(P.25) - 重载MRU文件可以通过自定义`CMRUFile`类或通过覆盖相应...

    类似QQ的聊天程序局域网聊天工具

    5. **多线程编程**:在聊天软件中,通常需要同时处理接收和发送消息,这就需要用到多线程技术,以确保用户界面的响应性和系统的稳定性。 6. **网络编程**:理解网络编程的基础知识,如套接字(Socket)编程,是实现...

    Thinking in Java 中文第四版+习题答案

    1.11.4 一个独立的领域:应用程序 1.12 分析和设计 1.12.1 不要迷失 1.12.2 阶段0:拟出一个计划 1.12.3 阶段1:要制作什么? 1.12.4 阶段2:开始构建? 1.12.5 阶段3:正式创建 1.12.6 阶段4:校订 1.12.7 计划的...

    Think in Java(中文版)chm格式

    1.11.4 一个独立的领域:应用程序 1.12 分析和设计 1.12.1 不要迷失 1.12.2 阶段0:拟出一个计划 1.12.3 阶段1:要制作什么? 1.12.4 阶段2:开始构建? 1.12.5 阶段3:正式创建 1.12.6 阶段4:校订 1.12.7 ...

    JAVA_Thinking in Java

    1.11.4 一个独立的领域:应用程序 1.12 分析和设计 1.12.1 不要迷失 1.12.2 阶段0:拟出一个计划 1.12.3 阶段1:要制作什么? 1.12.4 阶段2:开始构建? 1.12.5 阶段3:正式创建 1.12.6 阶段4:校订 1.12.7 计划的...

    Java初学者入门教学

    1.11.4 一个独立的领域:应用程序 1.12 分析和设计 1.12.1 不要迷失 1.12.2 阶段0:拟出一个计划 1.12.3 阶段1:要制作什么? 1.12.4 阶段2:开始构建? 1.12.5 阶段3:正式创建 1.12.6 阶段4:校订 1.12.7 计划的...

    ThinkInJava

    1.11.4 一个独立的领域:应用程序 1.12 分析和设计 1.12.1 不要迷失 1.12.2 阶段0:拟出一个计划 1.12.3 阶段1:要制作什么? 1.12.4 阶段2:开始构建? 1.12.5 阶段3:正式创建 1.12.6 阶段4:校订 1.12.7 计划的...

    java 编程入门思考

    1.11.4 一个独立的领域:应用程序 1.12 分析和设计 1.12.1 不要迷失 1.12.2 阶段0:拟出一个计划 1.12.3 阶段1:要制作什么? 1.12.4 阶段2:开始构建? 1.12.5 阶段3:正式创建 1.12.6 阶段4:校订 1.12.7 计划的...

    thinkinjava

    1.11.4 一个独立的领域:应用程序 1.12 分析和设计 1.12.1 不要迷失 1.12.2 阶段0:拟出一个计划 1.12.3 阶段1:要制作什么? 1.12.4 阶段2:开始构建? 1.12.5 阶段3:正式创建 1.12.6 阶段4:校订 1.12.7 计划的...

    Thinking in Java简体中文(全)

    1.11.4 一个独立的领域:应用程序 1.12 分析和设计 1.12.1 不要迷失 1.12.2 阶段0:拟出一个计划 1.12.3 阶段1:要制作什么? 1.12.4 阶段2:开始构建? 1.12.5 阶段3:正式创建 1.12.6 阶段4:校订 1.12.7 计划的...

Global site tag (gtag.js) - Google Analytics