网站设计的摘要,网页源代码视频下载链接,营销型网站展示,小说网站怎么做原创有一些关于锁的面试题#xff1a;
你知道 Java 里面有哪些锁#xff1f;读写锁的饥饿问题是什么#xff1f;有没有比读写锁更快的锁#xff1f;StampedLock知道嘛#xff1f;#xff08;邮戳锁/票据锁#xff09;ReentrantReadWriteLock 有锁降级机制#xff1f;
Ree…有一些关于锁的面试题
你知道 Java 里面有哪些锁读写锁的饥饿问题是什么有没有比读写锁更快的锁StampedLock知道嘛邮戳锁/票据锁ReentrantReadWriteLock 有锁降级机制
ReentrantReadWriteLock
与 ReentrantLock 相比ReentrantLock 实现了 Lock 接口ReentrantReadWriteLock 实现了 ReadWriteLock 接口。对于ReentrantLock 它的 读读也是一个线程访问浪费资源。ReentrantReadWriteLock 可以实现读读共享
读写锁定义为一个资源能被多个读线程访问或者被一个写线程访问但是不能同时存在读写线程读写互斥读读共享
class MyRescource { // 资源类模拟缓存MapString, String map new HashMap();// ReentrantLock 等价于 SynchronizedLock lock new ReentrantLock();// ReentrantReadWritLock 读读共享ReadWriteLock readWriteLock new ReentrantReadWriteLock();public void write(String key, String value) {lock.lock();try{System.out.println(Thread.currentThread().getName()\t正在写入。。。。);map.put(key,value);try {TimeUnit.MILLISECONDS.sleep(1000);System.out.println(Thread.currentThread().getName()\t完成写入。。。。);}catch (InterruptedException e){e.printStackTrace();}}finally {lock.unlock();}}public void readWriteWrite(String key, String value) {readWriteLock.writeLock().lock();try{System.out.println(Thread.currentThread().getName()\t正在写入。。。。);map.put(key,value);try {TimeUnit.MILLISECONDS.sleep(1000);System.out.println(Thread.currentThread().getName()\t完成写入。。。。);}catch (InterruptedException e){e.printStackTrace();}}finally {readWriteLock.writeLock().unlock();}}public void read(String key) {lock.lock();try{System.out.println(Thread.currentThread().getName()\t正在读入。。。。);String s map.get(key);try {// 1. 暂停500毫秒// 2. 暂停2000毫秒显式读锁没有完成之前写锁无法获取锁TimeUnit.MILLISECONDS.sleep(2000);System.out.println(Thread.currentThread().getName()\t完成读入。。。。\ts);}catch (InterruptedException e){e.printStackTrace();}}finally {lock.unlock();}}public void readWriteRead(String key) {readWriteLock.readLock().lock();try{System.out.println(Thread.currentThread().getName()\t正在读入。。。。);String s map.get(key);try {// 1. 暂停500毫秒// 2. 暂停2000毫秒显式读锁没有完成之前写锁无法获取锁TimeUnit.MILLISECONDS.sleep(2000);System.out.println(Thread.currentThread().getName()\t完成读入。。。。\ts);}catch (InterruptedException e){e.printStackTrace();}}finally {readWriteLock.readLock().unlock();}}
}public class ReentrantReadWriteLockDemo {public static void main(String[] args) {MyRescource myRescource new MyRescource();for (int i 0; i 10; i) {int finalI i;new Thread(()-{// myRescource.write(keyString.valueOf(finalI),valueString.valueOf(finalI));myRescource.readWriteWrite(keyString.valueOf(finalI),valueString.valueOf(finalI));},String.valueOf(i)).start();}for (int i 0; i 10; i) {int finalI i;new Thread(()-{// myRescource.read(keyString.valueOf(finalI));myRescource.readWriteRead(keyString.valueOf(finalI));},String.valueOf(i)).start();}try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}// 显式读锁没有完成之前写锁无法获取锁for (int i 0; i 3; i) {int finalI i;new Thread(()-{// myRescource.write(keyString.valueOf(finalI),valueString.valueOf(finalI));myRescource.readWriteWrite(keyString.valueOf(finalI),valueString.valueOf(finalI));},新读写锁String.valueOf(i)).start();}}
}
锁降级
ReentrantReadWriteLock锁降级将写入锁降级为读锁(类似Linux文件读写权限理解就像写权限要高于读权限一样)锁的严苛程度变强叫做升级反之叫做降级。
重进入该锁支持重进入以读写线程为例:读线程在获取了读锁之后能够再次获取读锁。而写线程在获取了写锁之后能够再次获取写锁同时也可以获取读锁。
锁降级遵循获取写锁、获取读锁再释放写锁的次序写锁能够降级成为读锁
写锁的降级降级成为了读锁
如果同一个线程持有了写锁在没有释放写锁的情况下它还可以继续获得读锁。这就是写锁的降级降级成为了读锁。规则惯例先获取写锁然后获取读锁再释放写锁的次序。如果释放了写锁那么就完全转换为读锁。
保证数据的一致性
/**
所降级遵循了获取写锁在获取读锁再释放写锁的次序写锁能够降级为读锁。因为写的优先级比读高
如果一个线程占有了写锁在不释放写锁的情况下它还能占有读锁即写锁降级为读锁
读没有完成时候写锁无法获得锁只有在读锁读完才可以
*/
public void lockLevelDown(){ReentrantReadWriteLock readWriteLock new ReentrantReadWriteLock();ReentrantReadWriteLock.ReadLock readLock readWriteLock.readLock();ReentrantReadWriteLock.WriteLock writeLock readWriteLock.writeLock();writeLock.lock();System.out.println(写入);readLock.lock();System.out.println(读入);writeLock.unlock();readLock.unlock();readLock.lock();System.out.println(读入);writeLock.lock();System.out.println(写入);readLock.unlock();writeLock.unlock();
}
/**
写入
读入
读入
*/分析StampedLock会发现它改进之处在于:
读的过程中也允许获取写锁介入(相当牛B读和写两个操作也让你“共享”(注意引亏))这样会守致戎们实的数掂趴可能个一以所以需要额外的方法来判断读的过程中是否有写入这是一种乐观的读锁。
显然乐观锁的并发效率更高但一旦有小概率的写入导致读取的数据不一致需要能检测出来再读一遍就行。
为什么要锁降级
锁降级的必要性1
锁降级中读锁的获取是否必要呢答案是必要的。主要是为了保证数据的可见性如果当前线程不获取读锁而是直接释放写锁 假设此刻另一个线程记作线程T获取了写锁并修改了数据那么当前线程无法感知线程T的数据更新。如果当前线程获取读锁即遵循锁降级的步骤则线程T将会被阻塞直到当前线程使用数据并释放读锁之后线程T才能获取写锁进行数据更新。
部分人读完上述话可能有些疑惑针对上面黑体字那句话为什么无法感知线程T的数据更新我当前线程再次获取读锁的时候不是可以察觉到数据在主存中的变化吗 我参考了一些资料对该 “”数据可见性“” 有了另一种理解理解是 当前线程为了保证数据的可见性这是指线程自己更改了数据自己应该要察觉到数据的变化如果没有读锁更改完数据之后线程T获取到了写锁并更改了数据则当前线程读到的数据是线程T更改的并不是自己更改的当前线程并不知道是线程T修改了自己要读的原来自己改的数据所以可能导致当前线程在执行后续代码的时候结果出错这时就导致了数据的不可见即当前线程并无法察觉到自己修改的值
锁降级的必要性2
为了提高程序执行性能可能存在一个事务线程不希望自己的操作被别的线程中断而这个事务操作可能分成多部分操作更新不同的数据或表甚至非常耗时。如果长时间用写锁独占显然对于某些高响应的应用是不允许的所以在完成部分写操作后退而使用读锁降级来允许响应其他进程的读操作。只有当全部事务完成后才真正释放锁。
StampedLock
邮戳锁、版本锁是比读写锁更快的锁。
StampedLock是JDK1.8中新增的一个读写锁也是对JDK1.5中读写锁ReentrantReadWriteLock的优化。
stamp戳记long 类型代表了锁的状态。当stamp返回零时表示线程获取锁失败。并且当释放锁或者转换锁的时候都要传入最初获取的stamp值。
锁饥饿问题ReentrantReadWriteLock实现了读写分离但是一旦读操作比较多的时候想要获取写锁就变得比较困难了假如当前1000个线程999个读1个写有可能999个读取线程长时间抢到了锁那1个写线程就悲剧了因为当前有可能会一直存在读锁而无法获得写锁根本没机会写。
如何缓解锁饥饿问题
使用“公平”策略可以一定程度上缓解这个问题 new ReentrantReadWriteLock(true)但是“公平策略是以牺牲系统吞吐量为代价
StampedLock 类的乐观读锁对于短的制度代码段使用乐观模式通常可以减少争用并提高吞吐量
ReentrantReadWriteLock
允许多个线程同时读但是只允许一个线程写在线程获取到写锁的时候其他写操作和读操作都会处于阻塞状态
读锁和写锁也是互斥的所以在读的时候是不允许写的读写锁比传统的synchronized速度要快很多
原因就是在于ReentrantReadWriteLock支持读并发读读可以共享
StampedLock横空出世
ReentrantReadWriteLock的读锁被占用的时候其他线程尝试获取写锁的时候会被阻塞。
但是StampedLock采取乐观获取锁后其他线程尝试获取写锁时不会被阻塞这其实是对读锁的优化
所以在获取乐观读锁后还需要对结果进行校验。
StampedLock的特点
所有获得锁的方法都返回一个邮戳StampStamp为零表示获取失败其余都表示成功
所有释放锁的方法都需要一个邮戳Stamp这个Stamp必须是和成功获取锁时得到的Stamp一致
StampedLock 是不可重入的危险如果一个线程已经持有了写锁再去获取写锁的话就会造成死锁
StampedLock 有三种访问模式
Reading读模式悲观功能和ReentrantReadWriteLock 的读锁类似Writeing写模式功能和ReentrantReadWriteLock 的写锁类似Optimistic reading乐观读模式无锁机制类似于数据库中的乐观锁支持读写并发很乐观认为读取时没人修改假如被修改再实现升级为悲观读模式——读的过程中也允许获取写锁介入
邮戳读写锁的传统版本
public class StampedLockDemo {static int number 37;static StampedLock stampedLock new StampedLock();public void write(){long stamp stampedLock.writeLock();System.out.println(Thread.currentThread().getName()\t 写线程准备修改。);try{number number 13;} finally {stampedLock.unlockWrite(stamp);}System.out.println(Thread.currentThread().getName()\t 写线程结束修改。);}public void read(){long stamp stampedLock.readLock();System.out.println(Thread.currentThread().getName()\t 悲观读线程准备修改。..);try{for (int i 0; i 4; i) {TimeUnit.SECONDS.sleep(1);System.out.println(读线程正在读取中);}int result number;System.out.println(Thread.currentThread().getName()\t 悲观读线程结束修改。result result);System.out.println(写线程没有修改成功读锁时候写锁无法介入传统读写互斥);} catch (InterruptedException e) {e.printStackTrace();} finally {stampedLock.unlockRead(stamp);}}public static void main(String[] args) {StampedLockDemo demo new StampedLockDemo();new Thread(()-{demo.read();},读线程).start();try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}new Thread(()-{demo.write();},写线程).start();}}
/**
读线程 悲观读线程准备修改。..
读线程正在读取中
读线程正在读取中
读线程正在读取中
读线程正在读取中
读线程 悲观读线程结束修改。result 37
写线程没有修改成功读锁时候写锁无法介入传统读写互斥
写线程 写线程准备修改。
写线程 写线程结束修改。
*/邮戳锁乐观版本
public class StampedLockDemo {static int number 37;static StampedLock stampedLock new StampedLock();public void write(){long stamp stampedLock.writeLock();System.out.println(Thread.currentThread().getName()\t 写线程准备修改。);try{number number 13;} finally {stampedLock.unlockWrite(stamp);}System.out.println(Thread.currentThread().getName()\t 写线程结束修改。);}public void read(){long stamp stampedLock.readLock();System.out.println(Thread.currentThread().getName()\t 悲观读线程准备修改。..);try{for (int i 0; i 4; i) {TimeUnit.SECONDS.sleep(1);System.out.println(读线程正在读取中);}int result number;System.out.println(Thread.currentThread().getName()\t 悲观读线程结束修改。result result);System.out.println(写线程没有修改成功读锁时候写锁无法介入传统读写互斥);} catch (InterruptedException e) {e.printStackTrace();} finally {stampedLock.unlockRead(stamp);}}public void tryOptimisticRead(){long stamp stampedLock.tryOptimisticRead();int result number;System.out.println(4秒前stampedLock.validate方法值 true无修改false有修改\tstampedLock.validate(stamp));for (int i 0; i 4; i) {try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()\t正在读取i秒后stampedLock.validate方法值\tstampedLock.validate(stamp));}if (!stampedLock.validate(stamp)){System.out.println(有人修改------有写操作);stamp stampedLock.readLock();try{System.out.println(从乐观读 升级为 悲观读);result number;System.out.println(从新悲观读后resultresult);}finally {stampedLock.unlockRead(stamp);}}System.out.println(Thread.currentThread().getName()\t最后的值是result);}public static void main(String[] args) {StampedLockDemo demo new StampedLockDemo();new Thread(()-{System.out.println(Thread.currentThread().getName()进入);demo.tryOptimisticRead();},乐观读线程).start();try {TimeUnit.SECONDS.sleep(2);} catch (InterruptedException e) {e.printStackTrace();}new Thread(()-{System.out.println(Thread.currentThread().getName()进入);demo.write();},写线程).start();}
}
/**
乐观读线程进入
4秒前stampedLock.validate方法值 true无修改false有修改 true
乐观读线程 正在读取0秒后stampedLock.validate方法值 true
写线程进入
写线程 写线程准备修改。
写线程 写线程结束修改。
乐观读线程 正在读取1秒后stampedLock.validate方法值 false
乐观读线程 正在读取2秒后stampedLock.validate方法值 false
乐观读线程 正在读取3秒后stampedLock.validate方法值 false
有人修改------有写操作
从乐观读 升级为 悲观读
从新悲观读后result50
乐观读线程 最后的值是50
*/邮戳锁的缺点
StampedLock 不支持重入没有 Re 开头StampedLock 的悲观读锁和写作都不支持条件变量使用StampedLock一定不要调用中断操作