【098】Java利用对象池配合synchronized同步块实现较高效率的线程同步
2017-11-30 23:50
573 查看
业务场景
web服务器端开发的时候,一般我们的Java代码是多线程执行的,并且是多线程地向数据库里插入数据。在插入数据的时候,我们很可能碰到这样一种情况:一个用户在一定条件下,只可能向数据库里插入一条数据。同时许多相关的业务逻辑也是以只有一条数据为基础而设计实现的。为了保证数据的完整性,我们应该确保只有一条数据。但是在实际应用中,服务器程序很可能向数据库插入了多条数据。为了给读者解释地更明白,我假设下面这样一种场景:
在线考试系统,有一个用户答案表,记录每个用户填写的每道题目的答案。假设表的结构如下:
表名: t_user_answer
主键: c_id
用户ID: c_user_id
题目ID: c_question_id
用户答案: c_user_answer
显然,保存一个用户做的某一道题目的答案,只需要一条记录就够了。也就是说,用户ID和题目ID的组合,理应是唯一的。即从业务上来说,用户ID和题目ID可以做 t_user_answer 的联合主键。当然用联合主键也是解决此问题的办法之一,但是在这篇文字中,我们主要讨论 synchronized 同步块的解决方法。
synchronized(exp){} 代码中,表达式 exp 必须返回某个对象的引用。当线程进入同步块,会给exp返回的对象加锁,当线程离开同步块,会给对象解锁。当两个线程持有同一个exp返回对象,这两个线程互斥,只有其中一个线程执行完同步块里的代码,另一个线程才能进入。如果对象已经被其它线程加锁,则在解锁之前,该线程阻塞。注意,如果两个线程的exp返回的不是同一个对象,哪怕两个对象相等,那么这两个线程也是并发的,互不影响。实际应用中,根据业务调整同步块锁的粒度,可以降低同步块对性能的影响。
演示代码
演示代码一共有三个文件:ZhangChaoLock.java LockObjectPool.java Main.java 。其中 ZhangChaoLock.java 是同步块中用于加锁的对象。
LockObjectPool.java 是加锁对象的对象池。
Main.java 包含 main 方法,用于测试。
为了演示方便,演示代码没有访问数据库。而是用创建文件来模拟并发的情况。逻辑是这样的:
程序先检查 E:/test 文件夹里面有没有文件,如果有文件,什么也不做。如果没文件,就新建一个文件,文件名是时间戳。在多线程的情况下,理想状况是 E:/test 只有一个新建文件。下面放出代码。
ZhangChaoLock.java
package zc.testSychronized; /** * 锁,用于 synchronized 关键字。作用于同步块: * synchronized(exp){ * ... * } * 中的 exp。 * @author 张超 * */ public class ZhangChaoLock { /** * 为了方便调试加的id属性 */ private int id = 0; public ZhangChaoLock() { super(); } public ZhangChaoLock(int id) { super(); this.id = id; } // 按照阿里巴巴的Java代码规范,重写equals方法和hashCode方法。 @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + id; return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; ZhangChaoLock other = (ZhangChaoLock) obj; if (id != other.id) return false; return true; } /** * 方便调试,重写toString */ @Override public String toString() { String superStr = super.toString(); return new StringBuilder().append("QstLock: ").append(this.id).append(" --- ").append(superStr).toString(); } }
LockObjectPool.java
package zc.testSychronized; /** * 锁对象的对象池 * @author 张超 * */ public final class LockObjectPool { /** * 对象池的大小。 */ private static final int POOL_SIZE; /** * 存放对象的数组。 */ private static final ZhangChaoLock[] arr; static { POOL_SIZE = 2000; arr = new ZhangChaoLock[POOL_SIZE]; for (int i = 0; i < POOL_SIZE; i++) { ZhangChaoLock lock = new ZhangChaoLock(i); arr[i] = lock; } } /** * 取绝对值 * @param a * @return 整数a的绝对值 */ private final static int absoluteValue(int a) { if (a >= 0) { return a; } else if (a == Integer.MIN_VALUE) { // 考虑边界值 int b = a + 1; return -b; }else { return -a; } } /** * 根据字符串str的哈希值,取得对应的锁。 * @param str * @return */ public final static ZhangChaoLock getLock(final String str) { int hashCode = str.hashCode(); hashCode = absoluteValue(hashCode); int pos = hashCode % POOL_SIZE; return arr[pos]; } }
Main.java
package zc.testSychronized; import java.io.File; import java.io.IOException; public class Main { /* test1和test2是测试方法。逻辑如下 1.检查 E:/test 文件夹下有没有文件。如果没有文件就新建一个文件。文件名是时间戳。 2.如果E:/test 文件夹下没有任何文件,就什么操作也不做。 3.运行程序前清空 E:/test。理想状态下,文件夹下应该只有一个文件。 test1 没有并发控制, E:/test 会出现两个文件。 test2 有并发控制, E:/test 会出现一个文件。 test3 两个相同的字符串,因为String 对象不同,所以两个线程是并发的,没有同步。启用对象池主要是为了应对字符串出现test3的情况。 实际应用中,synchronized不会对所有线程加同一把锁。 比如保存学生作业答案,每个线程都有 userId 和 questionId,把userId和questionId 拼成 一个字符串,可以得到一把锁。LockObjectPool利用哈希码尽可能根据 userId questionId 分配不同的锁。 这样,只有持有相同的 userId 和questionId 的多个线程才会互斥。 */ static void test1(String str) { File d = new File("E:/test"); File[] files = d.listFiles(); // sleep 是为了方便重现并发问题。 try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } if (files == null || files.length == 0) { File f = new File("E:/test/" + System.currentTimeMillis() + ".txt"); try { f.createNewFile(); } catch (IOException e) { e.printStackTrace(); } } else { // do nothing } } static void test2(String str) { ZhangChaoLock lock = LockObjectPool.getLock(str); System.out.println(lock); synchronized(lock) { File d = new File("E:/test"); File[] files = d.listFiles(); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } if (files == null || files.length == 0) { File f = new File("E:/test/" + System.currentTimeMillis() + ".txt"); try { f.createNewFile(); } catch (IOException e) { e.printStackTrace(); } } else { } } // end synchronized } static void test3(String str) { synchronized(str) { File d = new File("E:/test"); File[] files = d.listFiles(); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } if (files == null || files.length == 0) { File f = new File("E:/test/" + System.currentTimeMillis() + ".txt"); try { f.createNewFile(); } catch (IOException e) { e.printStackTrace(); } } else { } } // end synchronized } public static void main(String[] args) { Thread t1 = new Thread(new Runnable(){ public void run() { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } // test1("user1_id_and_question1_id"); test2("user1_id_and_question1_id"); // test3("user1_id_and_question1_id"); } }); Thread t2 = new Thread(new Runnable(){ public void run() { // test1("user1_id_and_question1_id"); test2("user1_id_and_question1_id"); // test3(new String("user1_id_and_question1_id")); } }); t1.start(); t2.start(); } }
相关文章推荐
- Java中利用synchronized关键字实现多线程同步问题 .
- java利用线程池(ExecutorService)配合Callable和Future实现执行方法超时的阻断
- Java中使用synchronized关键字实现简单同步操作示例
- Java实现利用HttpClient和配置文件实现集群模式下的文件同步分发
- java实现同步map的几种方法(lock,synchronized,rwlock,ConcurrentHashMap,hashtable,SynchronizedMap)
- java中多线程模拟(多生产,多消费,Lock实现同步锁,替代synchronized同步代码块)
- java同步关键字Synchronized 的实现原理
- 详解Java利用ExecutorService实现同步执行大量线程
- 【Java面试题】26 多线程有几种实现方法?同步有几种实现方法? 当一个线程进入一个对象的一个synchronized方法后,其它线程是否可进入此对象的其它方法?
- Java利用happen-before规则如何实现共享变量的同步操作详解
- 利用synchronized实现线程同步
- 关于网宿厦门研发中心笔试的一道PV操作题:利用java中的多线程实现生产者与消费者的同步问题
- Java之利用Freemarker模板引擎实现代码生成器,提高效率
- java-利用synchronized实现volatile的功能
- Java如何利用synchronized处理多线程的数据同步问题
- Java中synchronized关键字实现同步(二)
- java中的线程同步实现法二(Synchronized 程序段)
- Java中利用synchronized关键字实现多线程同步问题
- java两种同步机制的实现 synchronized和reentrantlock
- 【线程同步】 Java 同步块(synchronized)详细说明