1、乐观锁和悲观锁
1.1、乐观锁
它获取数据的时候,并不担心数据被修改,每次获取数据的时候也不会加锁,只是在更新数据的时候,通过判断现有的数据是否和原数据一致来判断数据是否被其他线程操作,如果没被其他线程修改则进行数据更新,如果被其他线程修改则不进行数据更新。
1.2、悲观锁
悲观锁认为对于同一个数据的并发操作,一定是会发生修改的,哪怕没有修改,也会认为修改。因此对于同一个数据的并发操作,悲观锁采取加锁的形式。悲观地认为,不加锁的并发操作一定会出问题。
2、公平锁与非公平锁
2.1、公平锁
公平锁是指多个线程按照申请锁的顺序来获取锁。
2.2、非公平锁
非公平锁是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁。
3、独占锁和共享锁
3.1、独占锁
独占锁是指任何时候都只有一个线程能执行资源操作。
3.2、共享锁
共享锁指定是可以同时被多个线程读取,但只能被一个线程修改。比如 Java 中的ReentrantReadWriteLock 就是共享锁的实现方式,它允许一个线程进行写操作,允许多个线程读操作。
3.3、读写锁
3.3.1、读写锁介绍
- 现实中有这样一种场景:对共享资源有读和写的操作,且写操作没有读操作那么频繁。在没有写操作的时候,多个线程同时读一个资源没有任何问题,所以应该允许多个线程同时读取共享资源;但是如果一个线程想去写这些共享资源,就不应该允许其他线程对该资源进行读和写的操作了。
- 针对这种场景,JAVA 的并发包提供了读写锁 ReentrantReadWriteLock,它表示两个锁,一个是读操作相关的锁,称为共享锁;一个是写相关的锁,称为排他锁(独占锁)。
读写锁的三个重要的特性
- 公平选择性:支持非公平(默认)和公平的锁获取方式,吞吐量还是非公平优于公平。
- 重进入:读锁和写锁都支持线程重进入。
- 锁降级:遵循获取写锁、获取读锁再释放写锁的次序,写锁能够降级成为读锁。
public class ReadWriteLockDemo {
private volatile Map<String,Object> map = new HashMap<>();
ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
public static void main(String[] args) {
ReadWriteLockDemo rd = new ReadWriteLockDemo();
//写数据
for (int i = 0; i < 5; i++) {
final int num = i;
new Thread(() -> {
rd.put(Integer.toString(num),num);
},"线程" + i).start();
}
//读数据
for (int i = 0; i < 5; i++) {
final int num = i;
new Thread(() -> {
rd.get(Integer.toString(num));
},"线程" + i).start();
}
}
//写数据
public void put(String key,Object value){
try {
rwl.writeLock().lock();
System.out.println(Thread.currentThread().getName()+"正在写" + key);
// 暂停一会
TimeUnit.MICROSECONDS.sleep(300);
// 放数据
map.put(key,value);
System.out.println(Thread.currentThread().getName()+"写完了"+key);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
rwl.writeLock().unlock();
}
}
//读数据
public void get(String key){
try {
rwl.readLock().lock();
System.out.println(Thread.currentThread().getName()+"正在读" + key);
// 暂停一会
TimeUnit.MICROSECONDS.sleep(300);
// 放数据
map.get(key);
System.out.println(Thread.currentThread().getName()+"读完了"+key);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
rwl.readLock().unlock();
}
}
}
4、可重入锁
可重入锁指的是该线程获取了该锁之后,可以无限次的进入该锁锁住的代码。
5、synchronized 是哪种锁的实现?为什么?
synchronized 是悲观锁的实现,因为 synchronized 修饰的代码,每次执行时会进行加锁操作,同时只允许一个线程进行操作,所以它是悲观锁的实现。
6、synchronized 使用的是公平锁还是非公平锁?
synchronized 使用的是非公平锁,并且是不可设置的。这是因为非公平锁的吞吐量大于公平锁,并且是主流操作系统线程调度的基本选择,所以这也是synchronized 使用非公平锁原因。