数据库事务必须同时满足4个特性:原子性(Atomic),一致性(Consistency),隔离性(Isolation),持久性(Durabiliy),简称ACID.
原子性:表示组成一个事务的多个数据库操作是一个不可分割的原子单元,要么全部执行成功,整个事务提交;要么回滚,回到初始状态.
一致性:事务操作成功后,数据库所处的状态和它的业务规则是一致的,即数据不会被破坏.
隔离性:在并发数据操作时,每一个事务本身拥有各自的数据空间,事务之间不会产生彼此干扰.准确的说,并非要求做到完全无干扰,数据库本身规定了多种事务隔离级别,
不同的隔离级别对应不同的干扰程度,隔离级别越高,数据一致性越好,但并发性越弱.
持久性:一旦事务提交成功后,事务中所有的数据操作必须被持久化到数据库中,即使在提交事务后,数据库崩溃,在数据库重启时,也必须要保证能够通过某种机制恢复数据.
数据并发的问题
1:脏读:A事务读取B事务尚未提交的更改数据.
2:不可重复读:A事务读取了B事务已经提交的更改数据.
3:幻象读:A事务读取了B事务提交的新增数据,这时A事务将出现幻想读的问题.
4:第一类丢失更新:A事务撤销时,把已经提交的B事务的更新数据覆盖了.
5:第二类丢失更新:A事务覆盖B事务已经提交的数据.造成B事务所做操作丢失.
数据库锁机制/事务隔离级别
按锁定的对象的不同,一般可以分为表锁定和行锁定.前者对整张表进行锁定,后者只对所要操作的表中的行记录进行锁定.从并发事务锁定的关系上看,可以分为共享锁定和独占锁定.
共享锁定会防止独占锁定,但允许其他的共享锁定.而独占锁定即防止其他的独占锁定也防止其他的共享锁定.为了更改数据,数据库必须在更改的行上施加行独占锁定,INSERT,UPDATE,DELETE和SELECT FOR UPDATE语句都会隐式采用必要的行独占锁定.
尽管数据库为用户提供了锁的DML操作方式,但直接使用锁管理是非常麻烦的,因此数据库为用户提供了自动锁机制.只要用户指定了会话的事务隔离级别,数据库就会分析事务中的SQL语句,然后自动为事务操作的数据资源添加合适的锁.
ANSI/ISO SQL92标准定义了4个等级的事务隔离级别.
隔离级别 | 脏读 | 不可重复读 | 幻象读 | 第一类丢失更新 | 第二类丢失更新 |
READ UNCOMMITED | 允许 | 允许 | 允许 | 不允许 | 允许 |
READ COMMITED | 不允许 | 允许 | 允许 | 不允许 | 允许 |
REPEATABLE READ | 不允许 | 不允许 | 允许 | 不允许 | 不允许 |
SERIALIZABLE | 不允许 | 不允许 | 不允许 | 不允许 | 不允许 |
ThreadLocal
针对并发中对象非线程安全的问题有两种处理方式:
1:传统的同步方式,即对对象的访问采用synchronized进行线程同步,但线程同步会降低并发性,影响系统性能.(时间换空间)
2:采用空间换时间的方式,给每个对象添加当前线程的副本,即采用ThreadLocal方式.
什么是ThreadLocal?通过分析源码:
public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } return setInitialValue(); }
ThreadLocalMap getMap(Thread t) { return t.threadLocals; }
public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); }
void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); }
以上列出了一些重要的源码:
通过源码我们可以知道:ThreadLocal在其内部维护了一个内部类ThreadLocalMap,而其构造函数为
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue)
可见ThreadLocalMap的key为当前线程的对象本身.
所以jdk1.8版本的ThreadLocal的核心就是在其内部维护了一个以当前线程的对象本身为key,来存储一个value的容器.
ThreadLocal在spring中发挥着重要的作用,在管理request作用域的Bean,事务管理,任务调度,AOP等模块中都出现了它的身影.
如创建一个线程安全的Connection类
package com.springboot.transaction; import java.sql.Connection; import org.springframework.jdbc.datasource.DriverManagerDataSource; public class ConnUtil { private static ThreadLocal<Connection> connThreadLocal = new ThreadLocal<Connection>(); private static final String DRIVER_CLASS = ""; private static final String URL = ""; private static final String USER_NAME = ""; private static final String PASS_WORD = ""; public static Connection getConnection() { try { if(connThreadLocal.get() == null) { // Connection conn = DriverManager.getConnection("", "", ""); DriverManagerDataSource ds = new DriverManagerDataSource(); ds.setDriverClassName(DRIVER_CLASS); ds.setUrl(URL); ds.setUsername(USER_NAME); ds.setPassword(PASS_WORD); Connection conn = ds.getConnection(); connThreadLocal.set(conn); return conn; } } catch (Exception e) { e.printStackTrace(); } return connThreadLocal.get(); } }