线程安全问题
ArrayList<Integer> list = new ArrayList<>();
Runnable r = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
list.add(10);
}
System.out.println("-------------");
}
};
new Thread(r).start();
new Thread(r).start();
Thread.sleep(1000);//等待线程执行完
System.out.println(list.size());
此段代码我们的需求是使用两个线程,同时给集合添加元素,在运行结束后打印集合的长度。已知业务中要求线程向集合中添加10000次数据,最后的理想效果应是集合的长度变为20000,但是实际运行结果<20000。
原因就是,集合中的add操作会对集合长度进行size++操作。
size++不是原子性(不可分离)操作。
步骤:
- 先取size原本的值
- 将这个值+1
- 将+1后的值存入size
这就导致了这一情况的发生,比如一个线程在进行size++操作时,尚未将+1后的值存入size,另一个线程来临,取到的size值仍是之前的值,最后导致他们存入size的值是一样的。
下面模拟一个这样的过程(首次添加数据):
线程1线程2t1①取值 —— 0
t2
②0++ —— 1t3①取值 —— 0t4②0++ —— 1t5③赋值1t6③赋值1
因此,我们学习了一个很关键的东西:锁。将一个不能被分离的多个操作放入同一个锁中,比如这题,将操作放入锁中,未完成size++操作前,使得其他线程无法进行此操作(获取此锁对象),在保证完成size++操作后再释放锁对象。就可以解决这种原子性引起的线程安全问题。
过节给女朋友发红包的问题
广大男性朋友们为避免在开心的日子发生不愉快的问题,总会触发“520”红包技能。
在Boys发红包时,钱包数据库会进行update操作,将余额数量-520。对应仙女们的钱包数据库也会会进行update操作,将余额数量+520。
这本是很愉快的一件事,但是不巧发完红包Boy的手机没有了流量,网络断开,余额已经扣除,只剩下可怜的三块五,但是仙女并没有收到。Boy开心的以为自己大难不死必有后福,没想到晚上依旧睡了沙发。
可是实际上并不会或者很少发生这种情况,Boy的钱变少了而仙女的钱没有变多,那这些钱总不能不翼而飞了吧。于是便有了事务。
什么是事务
事务(Transaction),就是将一组SQL语句放在同一批次内去执行,如果一个SQL语句出错,则该批次内的所有SQL都将被取消执行。
可以解决并发、一致性问题。
特点
一个事务中如果有一个数据库操作失败,那么整个事务的所有数据库操作都会失败,数据库数据就会回滚到该事务开始之前的状态。
** 注意:MySQL数据库中仅InnoDB和BDB类型的数据库表支持事务。**
事务的ACID原则(四大特征)
原子性
事务是数据库的逻辑工作单位,意味着数据库中的事务执行是作为原子粒度。即不可再分,整个语句要么执行,要么不执行行。每个事务作为一个整体,不可分离。
一致性
即在事务开始之前和事务结束以后,数据库的完整性约束没有被破坏。
事务执行的结果必须是使数据库从一个一致性状态变到另一个一致性状态。因此当数据库只包含成功事务提交的结果时,就说数据库处于一致性状态。如果数据库系统 运行中发生故障,有些事务尚未完成就被迫中断,这些未完成事务对数据库所做的修改有一部分已写入物理数据库,这时数据库就处于一种不正确的状态,或者说是不一致的状态。
只有在开始之前与结束之后才能保持一致性,在执行期间不能保持。
当一个事务在执行中,另一个用户访问查询,查到的是事务开始前的值。当事务结束后,查询到的值都是一样的。
隔离性
事务的执行是互不干扰的,一个事务不可能看到其他事务运行时,中间某一时刻的数据。
并发或者顺序从操作不影响其他事务的进行。
持久性
意味着在事务完成以后,该事务所对数据库所作的更改便持久的保存在数据库之中,并不会被回滚。也称永久性,一个事务一旦提交,它对数据库中的数据的改变就应该是永久性的。接下来的其它操作或故障不应该对其执行结果有任何影响。
事务的成功或者失败是永久的。
Mysql实现事务
关键语句
- set autocommit
** **使用该语句来改变自动提交模式,等于0时关闭自动提交模式,等于1时开启自动提交模式。默认为1,使用事务时为0。
**start transaction **
开始一个事务,标记事务的起始点。
commit
提交一个事务给数据库
rollback
将事务回滚,数据回到本次事务的初始状态。 **注意:commit和rollback是互斥的。**
步骤
- 关闭Mysql事务自动提交
- 开启事务
- 一组sql语句,实现业务
- 关闭事务
- 开启Mysql事务自动提交
关闭Mysql事务自动提交
set autocommit = 0; 默认值为1,即默认自动提交,先前做sql语句业务时没有进行设置,则可看做每一句sql语句是一个事务,执行前自动开启事务,执行完毕自动提交。
开启事务
start transaction; 此语句之后,知道碰到关闭事务语句前的所有sql语句都是一个事务。
关闭事务
commit; 提交事务,结束事务。 rollback; 回滚到本事务开始之前的状态,结束事务。
** 两种关闭事务的方式是互斥的。**
开启自动提交
set autocommit = 1; 如果不进行此操作可能会影响其他sql语句的执行。其他操作无法直接使用,因为没有开启事务与提交。
Mysql的四种隔离级别
SQL标准定义了4类隔离级别,包括了一些具体规则,用来限定事务内外的哪些改变是可见的,哪些是不可见的。低级别的隔离级一般支持更高的并发处理,并拥有更低的系统开销。
Read Uncommitted(读取未提交内容)
在该隔离级别,所有事务都可以看到其他未提交事务的执行结果。本隔离级别很少用于实际应用,因为它的性能也不比其他级别好多少。
** 存在问题:读取未提交的数据,也被称之为脏读(Dirty Read)。**
某个事务已更新一份数据,另一个事务在此时读取了同一份数据,由于某些原因,前一个RollBack了操作,则后一个事务所读取的数据就会是不正确的。
Read Committed(读取提交内容**** 也叫做不可重复读****)
** ** 这是大多数数据库系统的默认隔离级别(但不是MySQL默认的)。它满足了隔离的简单定义:一个事务只能看见已经提交事务所做的改变。这种隔离级别也支持所谓的不可重复读(Nonrepeatable Read),因为同一事务的其他实例在该实例处理其间可能会有新的commit,所以同一select可能返回不同结果。
** 存在问题:每次看到的数据可能不同。**
一个事务读某条数据读两遍,读到的是不一样的数据,也就是说,一个事务在进行中读取到了其他事务对旧数据的修改结果。即看到的永远都是最新的数据。
Repeatable Read(可重读)
这是MySQL的默认事务隔离级别,它确保同一事务的多个实例在并发读取数据时,会看到同样的数据行。
** 存在问题:幻读 (Phantom Read)。**
简单的说,幻读指当用户读取某一范围的数据行时,另一个事务又在该范围内插入了新行,当用户再读取该范围的数据行时,会发现有新的“幻影” 行。InnoDB和Falcon存储引擎通过多版本并发控制(MVCC,Multiversion Concurrency Control)机制解决了该问题。
一个事务中,读取到了其他事务新增的数据,仿佛出现了幻象。(幻读与不可重复读类似,不可重复读是读到了其他事务update/delete的结果,幻读是读到了其他事务insert的结果)
但是只要此事务不结束,每次查询看到的都是此事务开始前的值(事务的隔离性) 。
Serializable(可串行化)
这是最高的隔离级别,它通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。简言之,它是在每个读的数据行上加上共享锁。
存在问题:可能导致大量的超时现象和锁竞争。
使得所有的事务都变成顺序结构,除了正在执行的事务,其他事务都在等待,并行量增大,降低速度。
**因此,没有完美的锁或者隔离级别,只能找到满足需求的最优解。**
查看隔离级别
select @@global.transaction_isolation,@@transaction_isolation;
版权归原作者 鸡兄长高了 所有, 如有侵权,请联系我们删除。