赞
踩
在谈到"锁策略"之前.我们先要先理解线程和锁的关系,例如
你去银行办理业务,进入银行后先去取号,然后在服务区等待,过了段时间,广播加到你的号码,你走到服务窗口,由银行员工为你办理业务,并且在完成业务的过程中,员工只为你一个人服务,当你完成业务后离开,广播呼叫下一个号码.
在上述过程中,每个办理业务的人就是线程,服务窗口就是锁,服务窗口后的银行员工就是共享资源,你无法直接办理业务,需要先取号后等待的过程为阻塞,当广播叫到你的名字时,你去办理业务的提示信息为唤醒,你在窗口办理业务就是加锁,完成业务后离开为释放锁
悲观锁:
悲观锁自己去拿数据时,认为别人也去拿数据会修改数据,所以在每次拿数据时都会上锁,这样其他线程去拿数据时,会被阻挡到外面,防止数据错误.
乐观锁:
乐观锁认为在一般情况下是会发生访问冲突,所以不会上锁,只有在数据进行更新时,才会对比数据在当前线程的更新期间有没有被修改过,如果修改过,则可以尝试重新读取,选择报错,放弃修改等策略.
总结:
乐观锁和悲观锁共有各有优缺点,在面对不同的应用场景,采取合适的策略,能够大大的提高系统的效率,比如:当系统冲突次数多,适用于悲观锁,虽然由于频繁上锁,系统资源消耗大,但是能够减少冲突,提高系统的稳定性.当冲突次数少,使用乐观锁就可以减少上锁时消耗的资源,所以悲观锁阻塞事务,乐观锁回滚重试.
Synchronized 初始使用乐观锁策略. 当发现锁竞争比较频繁的时候, 就会自动切换成悲观锁策略 ,而乐观锁的在进程数据是否冲突时所采用的一个策略就是使用"版本号".
对于Synchronized属于普通的互斥锁,对于不同线程自己对同一个代码块的上锁是互斥的,而读写锁是对其进行细分,分为"读锁"和"写锁".在多线程之间,对于读操作是安全的,但是边读边写,多个线程写这是线程不安全的,使用读写锁就可以提高多个线程读的效率,还可以保证写操作的安全.
读写锁规则:
标准库的ReentrantReadWriteLock类:
Java标准库中使用ReentrantReadWriteLock 类中的readLock对象获取读锁,writeLock对象获取写锁,两个对象都提供lock/unlock来实现加锁/解锁操作.
//定义读写锁 private static final ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock(); //读锁对象 private static final ReentrantReadWriteLock.ReadLock readLock = reentrantReadWriteLock.readLock(); //写锁对象 private static final ReentrantReadWriteLock.WriteLock writeLock = reentrantReadWriteLock.writeLock(); //读操作 public static void read() { readLock.lock(); try { System.out.println(Thread.currentThread().getName() + "得到读锁,正在读取..."); Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } finally { readLock.unlock(); System.out.println(Thread.currentThread().getName()+"读取完毕,释放读锁"); } } //写操作 public static void write() { writeLock.lock(); try { System.out.println(Thread.currentThread().getName() + "得到写锁,正在写入..."); Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } finally { writeLock.unlock(); System.out.println(Thread.currentThread().getName() + "释放写锁"); } } public static void main(String[] args) { new Thread(() -> read(), "读锁 - 1").start(); new Thread(() -> read(), "读锁 - 2").start(); new Thread(() -> write(), "写锁 - 1").start(); new Thread(() -> write(), "写锁 - 2").start(); }
由显示结果及其执行次序知:读写锁支持并发读,而写操作是单独进行的.
二者涉及到用户态和内核态的转变,所以先了解锁的核心"原子性",及CPU是如何提供操作指令.
重量级锁:依赖于操作系统提供的锁
轻量级锁: 避免使用操作系统提供的锁,尽量在用户态完成任务
简而言之就是:轻量级锁任务少,锁开销小,重量级锁任务多,锁开销大.
synchronized是自适应锁,根据锁冲突情况切换,冲突不高:轻量级锁,冲突很高:重量级锁
自旋锁: 当发现锁冲突时,对于抢锁失败的线程,立即尝试重新获取锁,并且循环多次(状态不会改变).所以当其他线程释放锁时,就会第一时间获取到锁
挂起等待锁(阻塞锁): 当发现锁冲突时,会挂起等待(进入阻塞队列),所以当锁被释放时,不会第一时间获得锁.
比如去银行取钱,你有两种选择,一个是去柜台取钱,这就是阻塞锁,另一个是去取款机取钱,这就是自旋锁.在柜台取钱时你会先取号,然后开始等待被叫号,这个等待过程就是进入阻塞队列,被叫号就是被唤醒,而在取款机取钱是,没有广播提醒下一个是谁,即没有唤醒操作,你必须一直关注是否到自己取钱了.
二者都是等待获取共享资源,最大的区别是要不要放弃CPU的执行时间.
自旋锁是轻量级锁的实现:
挂起等待锁是重量级锁的实现:
在JVM中,synchronized 锁只能按照偏向锁、轻量级锁、重量级锁的顺序逐渐升级(被称为锁膨胀的过程),不允许降级,在后面会讲解,三者之间的转化过程.
假设现在有三个线程A,B,C尝试获得锁,A先尝试获取到锁,获取成功,随后B,C都去尝试获取,如果按照顺序是B,C这个顺序阻塞等待.
在线程A释放锁后,在公平锁和非公平锁的规则下,那个线程能够获得到锁是不同的
公平锁:遵守"先来后到",因为B比C先来,所以B先获取锁.
非公平锁: 不遵守"先来后到",每个锁获取锁的概率是相同的,这TM才是公平
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。