Java中的锁介绍:
公平非公平锁、递归锁、自旋锁、读写锁(互斥锁)
公平锁:
每个线程在获取锁时会查看此锁维护的等待队列,若为空或者当前线程是等待队列中的第一个,就占有锁,否则就加入等待队列
多线程按申请锁的顺序来获取锁,顺序策略(队列先来后到)
非公平锁
上来就直接尝试占有锁,若尝试失败,则采用公平锁的方式
多线程获取锁的顺序并不是按照申请锁的顺序,高并发情况下,可能会造成优先级反转或饥饿现象
ReentrantLock默认非公平,synchronized是一种非公平锁
反转:并不是按照顺序来获取锁,允许加塞
饥饿:线程一直获取不到锁
可重入锁(递归锁),避免了死锁
ReentrantLock/synchronized默认可重入锁
同一个线程外层函数获取锁的时候,在进入内层方法会自动获取锁(在同步方法中调用同步方法会自动获取锁,因为是同一把锁)
public synchronized void method1() {
method2();
}
public synchronized void method2(){
}
Lock lock=new ReentrantLock();
public void M() {
lock.lock();
lock.lock();
try {
System.out.println();
}finally {
lock.unlock();
lock.unlock();
}
}
Lock加锁在finally中解锁防止线程抛出异常 锁可以重复加,但注意加锁几次解锁几次(若加多次,关闭次数不对应,编译通过,运行出现阻塞)
自旋锁
尝试获取锁的线程不会立即阻塞,而是采用循环的方式去获取锁
优:循环获取直到成功为止,没有类似的wait阻塞(减少线程上下文切换的消耗) 缺:循环会消耗CPU,若是一直不能获取锁,系统性能会下降
自旋锁的手动实现(CAS+自旋思想)
public class Main {
AtomicReference<Thread> atReference=new AtomicReference<>();
public void lock() {
Thread t=Thread.currentThread();
//第一个线程直接加锁,后续线程进入此循环
//缺点表现:若加锁线程执行时间过长,其余线程一直循环消耗CPU
while(!atReference.compareAndSet(null, t)) {
}
}
public void unlock() {
Thread t=Thread.currentThread();
atReference.compareAndSet(t,null);
}
读锁(共享锁)
该锁可被多个线程所持有,提高了并发性
写锁(独占锁)
该锁只能被一个线程所持有 (ReentrantLock、synchronized)
ReentrantReadWriteLock读锁是共享,写锁是独占
读写锁:读-读能共存,读-写不能共存,写-写不能共存
读写锁(互斥锁)示例:
class Cache{
private volatile Map<String,Object> cache=new HashMap<>();
private ReentrantReadWriteLock lock=new ReentrantReadWriteLock();
public boolean set(String key,Object value) {
lock.writeLock().lock();
try {
cache.put(key, value);
}finally {
lock.writeLock().unlock();
}
return true;
}
public Object get(String key){
Object value=null;
lock.readLock().lock();
try {
value=cache.get(key);
}finally {
lock.readLock().unlock();
}
return value;
}
}
总结:synchronized是重锁, 并发性低,要有读写分离的思想