公平锁/非公平锁/可重入锁/递归锁/自旋锁谈谈你的理解?请手写一个自旋锁
乐观锁与悲观锁是在数据库中引入的名词
悲观锁:指对数据被外界修改持悲观态度,认为数据很容易就会被其他线程修改,所以在数据被处理前先对数据进行加锁,并在整个数据处理过程中,使数据处于锁定状态。
乐观锁:它认为数据在一般情况下不会造成冲突,所以在访问记录前不会加排它锁,而是在进行数据提交更新时,才会正式对数据冲突与否进行检测
概念:
公平锁
: 是指多个线程按照申请锁的顺序来获取锁。非公平锁
: 是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁,在高并发的情况下,有可能会造成优先级反转或者饥饿现象,但优点在于吞吐量比公平锁大如何创建:
并发包中ReentrantLock的创建可以指定构造函数的Boolean类型来得到公平锁或非公平锁,默认是非公平锁
Lock lock = new ReentrantLock(true);
对于Synchronized而言,也是一种非公平锁
没有公平性需求的情况下,尽量使用非公平锁,因为公平锁会带来额外的开销
public class ReeterLockDemo { public static void main(String[] args){ Phone phone = new Phone(); new Thread(() ->{ try{ phone.sengSMS(); }catch (Exception e){ e.getStackTrace(); } },"t1").start(); new Thread(() ->{ try{ phone.get(); }catch (Exception e){ e.getStackTrace(); } },"t2").start(); try{ TimeUnit.SECONDS.sleep(1); }catch (Exception e){ e.getStackTrace(); } /** * new Thread(() ->{ * try{ * phone.get(); * }catch (Exception e){ * e.getStackTrace(); * } * },"t3").start(); */ System.out.println(); System.out.println(); System.out.println(); Thread t3 = new Thread(phone); Thread t4 = new Thread(phone); t3.start(); t4.start(); } } class Phone implements Runnable{ public synchronized void sengSMS()throws Exception{ System.out.println(Thread.currentThread().getId()+"\t invoked sendSMS()"); sengEmail(); } public synchronized void sengEmail()throws Exception{ System.out.println(Thread.currentThread().getId()+"\t ####invoked sendEmail()"); } Lock lock = new ReentrantLock(); @Override public void run() { get(); } public void get() { //只要匹配就可以多把锁 lock.lock(); lock.lock(); try{ System.out.println(Thread.currentThread().getId()+"\t invoked get()"); set(); }catch (Exception e){ e.getStackTrace(); }finally { lock.unlock(); lock.unlock(); } } public void set() { lock.lock(); try{ System.out.println(Thread.currentThread().getId()+"\t invoked set()"); }catch (Exception e){ e.getStackTrace(); }finally { lock.unlock(); } } }
是指尝试获锁的不会立即阻塞,而是采用循环的方式去尝试获取锁
由于Java中的线程和操作系统中的线程是一一对应的,所以当一个线程在获取锁失败后,会被切换到内核状态而被挂起。所以从用户态到内核状态的开销是比较大的。
自旋锁则是不马上阻塞自己,在不放弃CPU使用权的情况下,多次尝试获取锁
优缺点
//手写自旋锁 public void myLock(){ Thread thread = Thread.currentThread(); System.out.println(Thread.currentThread().getName()+"\t come in"); //开始自旋 如果是null,则更新为当前线程,否者自旋 while(!atomicReference.compareAndSet(null, thread)){ System.out.println(Thread.currentThread().getName()+"\t wait"); } } //解锁 public void myUnlock(){ Thread thread = Thread.currentThread(); atomicReference.compareAndSet(thread, null); System.out.println(Thread.currentThread().getName()+"\t invoked myUnlock"); }
改进型锁:读写锁
根据锁能被一个线程占有还是多个线程共同持有,锁可以分为共享锁和独占锁
总结
获得条件 | 排他性 | 作用 | |
---|---|---|---|
读锁 | 相应的写锁未被任何线程持有 | 对读线程是共享的,对写线程排他的 | 允许多个读线程可以同时读取共享变量,并保证读线程读取共享变量期间没有其他任何线程能够更新这些共享变量 |
写锁 | 该写锁未被其他任何线程持有并且相应的读锁为被其他任何线程持有 | 对写线程和读线程都是排他的 | 使得写线程能够以独占的方式访问共享变量 |
//资源类 class MyCache{ private volatile Map<String,Object> map = new HashMap<>(); /** * 创建一个读写锁 * 它是一个读写融为一体的锁,在使用的时候,需要转换 */ private ReentrantReadWriteLock rwLock= new ReentrantReadWriteLock(); //写操作 public void put(String key, Object value){ // 创建一个写锁 rwLock.writeLock().lock(); try{ System.out.println(Thread.currentThread().getName()+"\t 正在写入:" + key); map.put(key,value); System.out.println(Thread.currentThread().getName()+"\t 写入完成:" + value); }catch (Exception e){ e.getStackTrace(); }finally { rwLock.writeLock().unlock(); } } //读操作 public void get(String key){ //上锁 rwLock.readLock().lock(); try{ System.out.println(Thread.currentThread().getName()+"\t 读取:" + key); Object result = map.get(key); System.out.println(Thread.currentThread().getName()+"\t 读取完成:" +result ); }catch (Exception e){ e.getStackTrace(); }finally { rwLock.readLock().unlock(); } } public void clearMap(){ map.clear();; } }
减少锁的持有时间
减少锁的粒度
用读写分离锁来替换独占锁
锁分离
锁粗化
虚拟机在遇到一连串地对同一个锁不断进行请求和释放的操作的时候,便会把所有的锁操作整合对锁的一次请求,从而减少对锁请求同步的次数
如果偏向锁失败,那么虚拟机不会立即挂起线程,他还会使用一种称为轻量级锁的优化手段。
java 虚拟机在JIT编译时,通过对运行上下文的扫描,去除不可能存在共享资源竞争的锁。通过锁消除,可以节省毫无意义的请求锁时间