开发过程中经常会遇到并发处理某共享数据时,产生不一致的情况,如何解决呢?方案是-----加锁。
一、对线程加锁
对线程加锁,就是利用Java提供的synchronized关键字。
【修饰一个代码块】
import java.lang.*;
/**
* 线程同步Demo
* @author 郑艳霞
*
*/
public class Test implements Runnable {
public void run() {
synchronized (this) {
for (int i = 0; i < 4; i++) {
try {
System.out.println("当前执行的是:"+Thread.currentThread().getName());
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
// 测试类
public static void main(String[] args) {
Test test = new Test();
Thread thread1 = new Thread(test, "线程一");
Thread thread2 = new Thread(test, "线程二");
thread1.start();
thread2.start();
}
}
运行结果为:访问synchronized同步代码块时,其他线程将被阻塞,因为执行synchronized代码块时,会锁定当前对象,只有释放对该对象的锁,下一个线程才能执行。
补充:synchronized只锁定对象,每个对象都只对应一个锁。如果在测试类中重新再声明一个对象,会证明相同的结论。(不要被单纯的运行结果欺骗哦)
【修饰一个方法】
其实在上面例子中就等同于是修饰一个方法。简单说,修饰代码块,是指将synchronized(this){//代码块},但是当这段代码块就是整个方法的时候,那就是在修饰一个方法了呗。这个时候就有了另一种写法,即:直接将synchronized关键字放在方法名前面。如下:
public synchronized void run() {
//synchronized (this) {
for (int i = 0; i < 4; i++) {
try {
System.out.println("当前执行的是:"+Thread.currentThread().getName());
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//}
运行结果当然也是一样的,就不在赘述。【修饰一个类】
跟上面一样,synchronized(this)锁住的其实就是this的范围,当this只是一个类中的一个方法中的一小段代码时,那锁的范围就是这段代码,这段代码中的对象在同一时刻只能被一个线程调用。如果this可以等同于一个方法,那锁的范围就是一个方法,这段代码中的对象在同一时刻只能被一个线程调用。如果范围是一个类,如下写法:当synchronized(test.class),那锁住的就是一个类,类中所有的对象同用一把锁。
二、对资源加锁
对资源进行加锁,就是从数据库方面进行控制。悲观锁是基于数据库锁机制使用的。
【悲观锁】(Pessimistic Lock)
悲观锁认为每次拿数据的时候都会有人来修改,认为肯定会出现影响数据完整性一致性的问题产生,所以每次拿数据的时候都会加锁。即,在整个事务处理过程中,数据都处于锁定状态。
这条sql语句锁定了t_table_id表中所有符合检索条件的记录,在这次查询事务提交之前,这些记录都无法被其他人修改。直至本次事务提交,锁释放。
【乐观锁】(Optimistic Lock)
乐观锁认为每次去拿数据都不会有人来修改,所以不加锁。但是在更新时会判断一下再此期间是否有人更新这些数据,所以用到了数据库的版本记录机制,通过version字段来判断是否是过期数据。(但是乐观锁不能解决读脏数据问题)
【比较】
相对于悲观锁,乐观锁机制采取了更加宽松的加锁机制。悲观锁大多数情况下依靠数据库的锁机制实现,来保证操作最大程度的独占性。但是如果某次事务时间特别长,还使用悲观锁的话,将会导致严重后果。乐观锁在一定程度上解决了这个问题。
【适用】
乐观锁适用于写比较少的情况,即很少发生冲突的情况,这样就可以省去锁开销,加大了系统的整个吞吐量。
悲观锁适用于更改频繁的数据表,一开始查询就加锁,直至更新操作结束才释放,但是性能有所下降。
【悲观锁的实现】
package com.bjpowernode.drp.util;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
/**
* Id生成器
* @author happy
*
*/
public class IdGenerator {
public static int generate(String tableName) {
int value = 0;
Connection conn = DbUtil.getConnection();
PreparedStatement pstmt = null;
ResultSet rs = null;
//悲观锁
String sql = "select value from t_table_id where table_name=? for update";
// 开始事务DbUtil.beginTransaction(conn);
try {
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.commitTransaction(conn);
} catch (SQLException 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
* @param tableName
* @param value
* @throws SQLException
*/
private static void modifyValueField(Connection conn, String tableName,
int value) throws SQLException {
String sql = "update t_table_id set value=? where table_name=?";
PreparedStatement pstmt = null;
try {
pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, value);
pstmt.setString(2, tableName);
pstmt.executeUpdate();
} finally {
DbUtil.close(pstmt);
}
}
public static void main(String[] args) {
int retValue = IdGenerator.generate("t_client");
System.out.println(retValue);
}
}