4.3. 并发¶
4.3.1. Java 并发¶
4.3.2. 多线程¶
4.3.3. 线程安全¶
4.3.4. 一致性、事务¶
事务 ACID 特性¶
事务的隔离级别¶
未提交读:一个事务可以读取另一个未提交的数据,容易出现脏读的情况。
读提交:一个事务等另外一个事务提交之后才可以读取数据,但会出现不可重复读的情况(多次读取的数据不一致),读取过程中出现UPDATE操作,会多。(大多数数据库默认级别是RC,比如SQL Server,Oracle),读取的时候不可以修改。
可重复读: 同一个事务里确保每次读取的时候,获得的是同样的数据,但不保障原始数据被其他事务更新(幻读),Mysql InnoDB 就是这个级别。
序列化:所有事物串行处理(牺牲了效率)
-
幻读的例子非常清楚。
通过 SELECT … FOR UPDATE 解决。
-
图解脏读、不可重复读、幻读问题。
MVCC¶
-
innodb 中 MVCC 用在 Repeatable-Read 隔离级别。
MVCC 会产生幻读问题(更新时异常。)
-
通过隐藏版本列来实现 MVCC 控制,一列记录创建时间、一列记录删除时间,这里的时间
每次只操作比当前版本小(或等于)的 行。
4.3.5. 锁¶
Java中的锁和同步类¶
-
主要包括 synchronized、ReentrantLock、和 ReadWriteLock。
-
有数量控制
申请用 acquire,申请不要则阻塞;释放用 release。
-
简单的说 就是Mutex是排它的,只有一个可以获取到资源, Semaphore也具有排它性,但可以定义多个可以获取的资源的对象。
公平锁 & 非公平锁¶
公平锁的作用就是严格按照线程启动的顺序来执行的,不允许其他线程插队执行的;而非公平锁是允许插队的。
-
默认情况下 ReentrantLock 和 synchronized 都是非公平锁。ReentrantLock 可以设置成公平锁。
悲观锁¶
悲观锁如果使用不当(锁的条数过多),会引起服务大面积等待。推荐优先使用乐观锁+重试。
-
乐观锁的方式:版本号+重试方式
悲观锁:通过 select … for update 进行行锁(不可读、不可写,share 锁可读不可写)。
《Mysql查询语句使用select.. for update导致的数据库死锁分析》
mysql的innodb存储引擎实务锁虽然是锁行,但它内部是锁索引的。
锁相同数据的不同索引条件可能会引起死锁。
乐观锁 & CAS¶
-
和MySQL乐观锁方式相似,只不过是通过和原值进行比较。
ABA 问题¶
由于高并发,在CAS下,更新后可能此A非彼A。通过版本号可以解决,类似于上文Mysql 中提到的的乐观锁。
-
AtomicStampedReference 和 AtomicStampedReference。
CopyOnWrite容器¶
可以对CopyOnWrite容器进行并发的读,而不需要加锁。CopyOnWrite并发容器用于读多写少的并发场景。比如白名单,黑名单,商品类目的访问和更新场景,不适合需要数据强一致性的场景。
《JAVA中写时复制(Copy-On-Write)Map实现》
实现读写分离,读取发生在原始数据上,写入发生在副本上。
不用加锁,通过最终一致实现一致性。
RingBuffer¶
可重入锁 & 不可重入锁¶
-
通过简单代码举例说明可重入锁和不可重入锁。
可重入锁指同一个线程可以再次获得之前已经获得的锁。
可重入锁可以用户避免死锁。
Java中的可重入锁:synchronized 和 java.util.concurrent.locks.ReentrantLock
《ReenTrantLock可重入锁(和synchronized的区别)总结》
synchronized 使用方便,编译器来加锁,是非公平锁。
ReenTrantLock 使用灵活,锁的公平性可以定制。
相同加锁场景下,推荐使用 synchronized。
互斥锁 & 共享锁¶
互斥锁:同时只能有一个线程获得锁。比如,ReentrantLock 是互斥锁,ReadWriteLock 中的写锁是互斥锁。 共享锁:可以有多个线程同时或的锁。比如,Semaphore、CountDownLatch 是共享锁,ReadWriteLock 中的读锁是共享锁。
死锁¶
-
互斥、持有、不可剥夺、环形等待。
-
JConsole 可以识别死锁。
-
jstack 可以显示死锁。