开发过程中经常会遇到并发处理某共享数据时,产生不一致的情况,如何解决呢?方案是-----加锁。

一、对线程加锁

      对线程加锁,就是利用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();
	}
}


    运行结果为:





java 线程插队案例 java线程冲突_Java



      访问synchronized同步代码块时,其他线程将被阻塞,因为执行synchronized代码块时,会锁定当前对象,只有释放对该对象的锁,下一个线程才能执行。


      补充:synchronized只锁定对象,每个对象都只对应一个锁。如果在测试类中重新再声明一个对象,会证明相同的结论。(不要被单纯的运行结果欺骗哦)



java 线程插队案例 java线程冲突_java 线程插队案例_02



【修饰一个方法】


      其实在上面例子中就等同于是修饰一个方法。简单说,修饰代码块,是指将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)

      悲观锁认为每次拿数据的时候都会有人来修改,认为肯定会出现影响数据完整性一致性的问题产生,所以每次拿数据的时候都会加锁。即,在整个事务处理过程中,数据都处于锁定状态。

java 线程插队案例 java线程冲突_synchronized_03


这条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);
	}
}