一、乐观锁和悲观锁
1、乐观锁
乐观锁只是一种设计思想,并不是真的有一种锁是乐观的。
思想:每次操作共享数据之前,都认为其他线程不会修改数据,所以都不获取锁,直接操作。只在最后更新的时候会判断一下在此期间是否有其他线程更新过这个数据。其实是一种无锁状态的更新。
典型实现:数据库版本号;CAS算法。
2、悲观锁
悲观锁只是一种设计思想,并不是真的有一种锁是悲观的。
思想:每次操作共享数据之前,都认为其他线程会修改数据,所以都先获取锁,才操作。未获得锁的线程,必须阻塞等待。
典型实现:synchronized;ReentrantLock。
二、共享锁和排他锁
1、介绍
对数据的访问通常分为两种情况,读(查询)和写(新增、修改、删除)。
多个线程并发读数据,是不会出现问题的。但是,多个线程并发写数据,到底是写入哪个线程的数据呢?这就是平时所说的线程同步问题。
所以,写写/读写需要互斥访问,读读不需要互斥访问。
2、排他锁(写锁)
排他锁(X锁),又称写锁、独占锁、互斥锁:锁一次只能被一个线程所持有。如果一个线程对数据加上排他锁后,那么其他线程不能再对该数据加任何类型的锁。获得排他锁的线程即能读数据又能修改数据。
理解:一个线程获取写锁,其对数据可读,可写。其他线程只能等待,读,写都不可以。即:写写/读写需要互斥访问。
显然:synchronized 和 Lock 的实现类就是排他锁。
3、共享锁(读锁)
共享锁(S锁),又称读锁:一种只读的数据锁,可被多个线程所持有。如果一个线程对数据加上共享锁后,那么其他线程只能对数据再加共享锁,不能加排他锁。获得共享锁的线程只能读数据,不能修改数据。
理解:一个线程获取读锁,其对数据只可读,不可写。其他线程可以再获取读锁,但不可获取写锁。即:读写需要互斥访问,读读不需要互斥访问。
4、应用
问题:若对一个共享数据,加了 synchronized 排他锁(互斥锁),而对数据的访问又仅仅只是读。那么,势必会影响读的效率。
原因:一次只能被一个线程访问,未获取到锁的线程则必须等待。即:A读完,B才能读,B读完,C才能读。
解决:可以用读写锁来提高效率。在 JUC 包中,ReadWriteLock 就维护了一对相关的锁,一个用于只读操作,另一个用于写入操作。只要没有 writer,读锁可以由多个 reader 线程同时保持。读写锁相比于互斥锁并发程度更高,每次只有一个写线程,但是可以同时有多个线程并发读。
读锁,可以多个线程并发的持有。
写锁,是独占的。
源码示例:读写锁
1 public interface ReadWriteLock { 2 // 返回一个读锁(共享锁) 3 Lock readLock(); 4 5 // 返回一个写锁(排他锁) 6 Lock writeLock(); 7 }