一、前言
小编在最近的项目中遇到了要对数据库中同一个字段进行操作的一个功能,少数人操作的话,还体现不出来线程的问题,当很多人同时使用,数据量变大,就会出现线程的问题。如何保持线程同步,是小编这篇博客要达到的目的。
二、引入
其实在我们生活中有很多功能使用了线程同步,小编向大家举一个例子:京东秒杀。
上面这张图就是小编在京东官网上面的京东秒杀模块,他的主要功能是在一个固定的时间点,比如16:00,发布1000件泸州酒,页面上回显示出还剩下百分之多少。这样用户会抢这些商品,很多人同时抢,就会在数据库中出现多个线程对数据库一个字段同时操作的现象,如果数据库不做到线程单一的话,数据就会变得不准确。
为了解决这个问题就引入了锁的问题,对数据库进行加锁操作,实现线程单一。
三、实现线程同步
解决共享资源的情况,必须使用线程同步,可以使用两种方案:
方法一:关键字synchronized
在java中使用synchronized关键字段对方法同步
/**
* 根据表名生成该表的序列
* @param tableName
* @return 返回生成的序列
*/
public static synchronized int generate(String tableName) {
String sql = "select value from t_table_id where table_name=? ";
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
int value = 0;
try {
conn = DbUtil.getConnection();
pstmt = conn.prepareStatement(sql);
pstmt.setString(1, tableName);
rs = pstmt.executeQuery();
if (!rs.next()) {
throw new RuntimeException();
}
value = rs.getInt("value");
value++; //自加
modifyValueField(conn, tableName, value);
}catch(Exception e) {
e.printStackTrace();
throw new RuntimeException();
}finally {
DbUtil.close(rs);
DbUtil.close(pstmt);
DbUtil.close(conn);
}
return value;
}
方法二 使用悲观锁实现线程同步
悲观锁是采用数据库机制实现的,数据被锁住之后其他用户将无法查看,只到锁释放,只有提交或回滚事务锁才会释放。
for update语句只能放到select语句中,因为查询时把数据锁住才有意义。
/**
* 根据表名生成该表的序列
* @param tableName
* @return
* @throws SQLException
* synchronized进程同步,加锁
*/
public static int generate(String tableName) throws SQLException{
//使用数据库的悲观锁for update,只能用在查询上
String sql= "select value from t_table_id where table_name=? for update";
Connection conn =null;
PreparedStatement pstmt = null;
ResultSet rs = null;
int value =0;
try {
conn=DbUtil.getConnection();
//开启事务
DbUtil.beginTransaction(conn);
pstmt=conn.prepareStatement(sql);
pstmt.setString(1, tableName);
rs = pstmt.executeQuery();
if (!rs.next()) {
throw new RuntimeException();
}
value = rs.getInt("value");
value++;
modifyValueField(conn,tableName,value);
//提交事务
DbUtil.commitTranction(conn);
}catch (Exception e) {
e.printStackTrace();
//回滚事务
DbUtil.rollbackTransaction(conn);
throw new RuntimeException();
}finally{
DbUtil.close(rs);
DbUtil.close(pstmt);
DbUtil.resetConnection(conn);// 重置Connection的状态
DbUtil.close(conn);
}
return value;
}
对事务的管理函数:
/**
* 手动提交
* @param conn
*/
public static void beginTransaction(Connection conn){
try {
if (conn!=null) {
if (conn.getAutoCommit()) {
conn.setAutoCommit(false); //手动提交
}
}
} catch (Exception e) {
}
}
/**
* 提交事务
* @param conn
*/
public static void commitTranction(Connection conn) {
try {
if (conn!=null) {
if (conn.getAutoCommit()) {
conn.commit(); //手动提交
}
}
} catch (Exception e) {
}
}
/**
* 回滚事务
* @param conn
*/
public static void rollbackTransaction(Connection conn){
try {
if (conn!=null) {
if (conn.getAutoCommit()) {
conn.rollback() ; //手动提交
}
}
} catch (Exception e) {
}
}
/**
* 重置Connection的状态
* @param conn
*/
public static void resetConnection(Connection conn){
try {
if (conn!=null) {
if (conn.getAutoCommit()) {
conn.setAutoCommit(false);
}else {
conn.setAutoCommit(true); }
}
} catch (Exception e) {
}
}
通过这样我们可以在还没有提交一个进程之前,其他的进程都要等待,只有当前的进程完成了提交,完成了事务,悲观锁解除了,才会有一个新的进程对数据进程操作,这样便完成了线程同步的效果。
四、小结
可以这么理解,在咱们的生活中,有很多的功能都已经非常的实用,非常的常见了,只要我们对其中的核心知识点做核心的研究,我们就可以实现这个功能,大家都是程序员,要深入研究还是有必要的。加油!~~