在现实生活中,不少场景都须要ID生成器,好比说电商平台的订单号生成、银行的叫号系统等。针对不用的业务需求,ID生成策略也不同,好比电商平台的订单号能够由时间序列组成,银行的叫号系统则是天然数自增序列。对于自增序列的ID生成器,在多并发环境下,为保证严格的自增,经常能够经过锁来保证。html
设想一下,若是咱们想在应用层面本身实现一个自增序列的ID生成器(其实本质上咱们须要实现的是一个getNextValue方法),怎么作?对不少人来讲,可能首先想到的是直接用i++这样语法层面的语句,可是必需要对方法加锁才行,由于i++不是一个原子操做。还有另一个办法,就是利用java的AtomicInteger类,AtomicInteger的实现不是基于锁,而是基于CAS(Compare and Swap),在某些场景下,效率要比加锁的方式高,参考(漫画:什么是 CAS 机制?)。java
上面介绍的语言层面的支持更多的是一些理论层面的东西,经常适用于单机系统,若是要应用到实际的软件系统中,还须要考虑不少其余方面,好比说自增序列的持久化、分布式系统中如何生成自增序列。sql
在分布式系统中,如何实现ID生成器,有不少办法,有兴趣的童鞋能够自行网上搜索。下面主要分析JPA的ID生成器是如何依赖于数据库的锁实现的。数据库
其实不少分布式场景下的需求和功能,都仍是依赖于数据库的基本功能来实现,以前写的一篇文章(liquibase和flyway中分布式锁实现的区别?)就介绍了在flyway中如何利用数据库的排他锁实现分布式锁。然而,大量依赖数据库也可能致使数据库成为一个单点性能瓶颈,这时候每每就须要考虑一些方案来减轻这个瓶颈,好比说分库分表(如今流行的微服务架构就是一个High-level的分库分表的实践)。架构
JPA的@GeneratedValue和@TableGenerator两个Annotation能够直接用来生成自增序列,而且会把当前的序列存在数据库中,JPA如今流行的两个provider(eclipselink和hibernate)在实现上,有殊途同归之处,都是依赖的数据库的排他锁。并发
那么eclipselink是如何实现的呢?就像上面提到的,本质上就是实现了一个getNextValue方法,只是这里加的锁是数据的排他锁,而不是语言层面的锁,以下图所示。eclipse
这里数据库排他锁工做的基本原理是:在一个事务中,当update一条记录时,会在当前记录上加一个排他锁(或者整个表上),只有事务结束(commit或者rollback)以后,才会释放这个锁;这时其余阻塞的事务就继续执行。参考以下代码:分布式
Connection c = null; try { Class.forName("org.postgresql.Driver"); c = DriverManager.getConnection("jdbc:postgresql://localhost:5432/postgres","postgres", "postgres"); } catch (Exception e) { e.printStackTrace(); System.err.println(e.getClass().getName()+": "+e.getMessage()); System.exit(0); } c.setAutoCommit(false); String sql1 = "update sequence set seq_count = 35 where id_generation_category='t1'"; PreparedStatement preparedStatement1 = c.prepareStatement(sql1); preparedStatement1.executeUpdate(); String sql2 = "select * from sequence where id_generation_category='t1'"; ResultSet rs = preparedStatement2.executeQuery(); while(rs.next()) { int seq_count = rs.getInt("seq_count"); System.out.println("seq_count: " + seq_count); } try{ c.commit(); }catch (SQLException e){ c.rollback(); } finally { preparedStatement1.close(); preparedStatement2.close(); c.close(); }
Hibernate的实现相似,具体能够参考文章(https://dzone.com/articles/hibernate-identity-sequence),能够看到Hibernate采用的是select for update语句显示加排他锁的方式,和前面写的一篇文章(liquibase和flyway中分布式锁实现的区别?)flyway加锁的方式同样。ide
上面提到,实现自增序列也能够不用加锁,java语言层面提供的AtomicInteger类就是采用不加锁的方法,而是采用的CAS(Compare and Swap)。那么在分布式环境下,ID生成器是否是也能够采用CAS呢?这篇文章(浅谈CAS在分布式ID生成方案上的应用 | 架构师之路)就简单介绍了如何采用CAS实现分布式ID生成器。微服务
关于各类锁概念的解释,推荐两篇文章:(Java 中15种锁的介绍:公平锁,可重入锁,独享锁,互斥锁,乐观锁,分段锁,自旋锁等等),(Java中的锁原理、锁优化、CAS、AQS详解!)