一道面试题比较synchronized和读写锁
2016-06-07 11:57
288 查看
一、科普定义
这篇博文的两个主角“synchronized”和“读写锁”
1)synchronized
这个同步关键字相信大家都用得比较多,在上一篇“多个线程之间共享数据的方式”中也详细列举他的应用,在这就不多说只做几点归纳:
Java提供这个关键字,为防止资源冲突提供的内置支持。当任务执行到被synchronized保护的代码片段的时候,它检查锁是否可用,然后获取锁,执行代码,释放锁。
常用这个关键字可以修饰成员方法和代码块
2)读写锁
我们对数据的操作无非两种:“读”和“写”,试想一个这样的情景,当十个线程同时读取某个数据时,这个操作应不应该加同步。答案是没必要的。只有以下两种情况需要加同步:
这十个线程对这个公共数据既有读又有写
这十个线程对公共数据进行写操作
以上两点归结起来就一点就是有对数据进行改变的操作就需要同步
所以
java5提供了读写锁
这种锁支持多线程读操作不互斥,多线程读写互斥,多线程写写互斥。
读操作不互斥这样有助于性能的提高,这点在java5以前没有
二.用一道面试题来具体比较这两点
题目:“白板编程,实现一个缓存系统”
题目分析:
对这个缓存系统的理解:
鉴于用户和数据库中间的一个环节,我们知道用户直接访问数据库的时间是远大于直接访问内存,所以有了缓存区后用户访问数据时 这样,用户先访问缓存区当缓存区有用户需要的数据时直接拿走,当缓存区没有这样的数据,访问数据库并把访问所得的数据放在缓存区,这样当下一个需要这个数据的用户就直接访问内存即可得到。
核心代码实现:
首先用synchronized实现
Java代码
![](https://oscdn.geek-share.com/Uploads/Images/Content/201910/19/90dae5b80145589fc0a3ce17d5ebb1cc.gif)
public synchronized Object getData(String key){
Object result = map.get(key);
if(result ==null){
result = "new";//用这步代替访问数据库得数据
}
return result;
}
用读写锁实现
Java代码
![](https://oscdn.geek-share.com/Uploads/Images/Content/201910/19/90dae5b80145589fc0a3ce17d5ebb1cc.gif)
public Object getData(String key){
rw.readLock().lock();//在读前先上读锁
Object result = null;
try{
result = map.get(key);
//这个if比较关键,它避免了多余的几次对数据哭的读取
if(result==null){
//如果内存中没有所要数据
rw.readLock().unlock();
rw.writeLock().lock();
if(result==null){
try{
//我们用这个代替对数据库访问得到数据的步骤
result = "new";
}finally{
rw.writeLock().unlock();
}
rw.readLock().lock();
}
}
}finally{
rw.readLock().unlock();
}
return result;
}
代码分析:
用第一种方法处理,整个过程比较粗线条,代码比较简单单执行效率很低。这种方法的中心思想是不管你是什么操作,但凡涉及到公共资源就都给你同步。这么做可以是可以但是并不好。
第二种用读写锁处理显然是对前者的一个优化,对第二种方法做如下几点说明:
关于unlock操作,我们知道只要是上了锁就必须要解锁,但是有这么一种情况就是当你上完锁后在执行解锁操作前程序出现异常,那这个所可能就一直存在。所以针对这个问题我们一般将unlock操作放在finally代码块中,就可以保证上了的锁一定会被解。
上面的两次if判断,第一个if相信大家很好理解。但为什么要用第二个if呢?再假设一个场景,现在有十个线程来读这个数据,而这个数据又不存在与缓存区,那么这十个线程中最先到的线程将执行“rw.writeLock().lock();”而另外九个线程将被阻塞,当第一个线程读完以后缓存区实际上已经就有了这个数据,但另外九个阻塞在“rw.writeLock().lock();”如果不加这层if他们会继续访问数据库,由此可见加了这层if对整个过程影响很大。这是比较细节的一点,就这一点Java的API文档也考虑到了,它的样例代码如下:
Java代码
![](https://oscdn.geek-share.com/Uploads/Images/Content/201910/19/90dae5b80145589fc0a3ce17d5ebb1cc.gif)
class CachedData {
Object data;
volatile boolean cacheValid;
ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
void processCachedData() {
rwl.readLock().lock();
if (!cacheValid)
{
// Must release read lock before acquiring write lock
rwl.readLock().unlock();
rwl.writeLock().lock();
// Recheck state because another thread might have acquired
// write lock and changed state before we did.
if (!cacheValid)
{
data = ...
cacheValid = true;
}
// Downgrade by acquiring read lock before releasing write lock
rwl.readLock().lock();
rwl.writeLock().unlock(); // Unlock write, still hold read
}
use(data);
rwl.readLock().unlock();
}
}
学习在继续!!!Go!Go!Go!!!
这篇博文的两个主角“synchronized”和“读写锁”
1)synchronized
这个同步关键字相信大家都用得比较多,在上一篇“多个线程之间共享数据的方式”中也详细列举他的应用,在这就不多说只做几点归纳:
Java提供这个关键字,为防止资源冲突提供的内置支持。当任务执行到被synchronized保护的代码片段的时候,它检查锁是否可用,然后获取锁,执行代码,释放锁。
常用这个关键字可以修饰成员方法和代码块
2)读写锁
我们对数据的操作无非两种:“读”和“写”,试想一个这样的情景,当十个线程同时读取某个数据时,这个操作应不应该加同步。答案是没必要的。只有以下两种情况需要加同步:
这十个线程对这个公共数据既有读又有写
这十个线程对公共数据进行写操作
以上两点归结起来就一点就是有对数据进行改变的操作就需要同步
所以
java5提供了读写锁
这种锁支持多线程读操作不互斥,多线程读写互斥,多线程写写互斥。
读操作不互斥这样有助于性能的提高,这点在java5以前没有
二.用一道面试题来具体比较这两点
题目:“白板编程,实现一个缓存系统”
题目分析:
对这个缓存系统的理解:
鉴于用户和数据库中间的一个环节,我们知道用户直接访问数据库的时间是远大于直接访问内存,所以有了缓存区后用户访问数据时 这样,用户先访问缓存区当缓存区有用户需要的数据时直接拿走,当缓存区没有这样的数据,访问数据库并把访问所得的数据放在缓存区,这样当下一个需要这个数据的用户就直接访问内存即可得到。
核心代码实现:
首先用synchronized实现
Java代码
![](https://oscdn.geek-share.com/Uploads/Images/Content/201910/19/90dae5b80145589fc0a3ce17d5ebb1cc.gif)
public synchronized Object getData(String key){
Object result = map.get(key);
if(result ==null){
result = "new";//用这步代替访问数据库得数据
}
return result;
}
用读写锁实现
Java代码
![](https://oscdn.geek-share.com/Uploads/Images/Content/201910/19/90dae5b80145589fc0a3ce17d5ebb1cc.gif)
public Object getData(String key){
rw.readLock().lock();//在读前先上读锁
Object result = null;
try{
result = map.get(key);
//这个if比较关键,它避免了多余的几次对数据哭的读取
if(result==null){
//如果内存中没有所要数据
rw.readLock().unlock();
rw.writeLock().lock();
if(result==null){
try{
//我们用这个代替对数据库访问得到数据的步骤
result = "new";
}finally{
rw.writeLock().unlock();
}
rw.readLock().lock();
}
}
}finally{
rw.readLock().unlock();
}
return result;
}
代码分析:
用第一种方法处理,整个过程比较粗线条,代码比较简单单执行效率很低。这种方法的中心思想是不管你是什么操作,但凡涉及到公共资源就都给你同步。这么做可以是可以但是并不好。
第二种用读写锁处理显然是对前者的一个优化,对第二种方法做如下几点说明:
关于unlock操作,我们知道只要是上了锁就必须要解锁,但是有这么一种情况就是当你上完锁后在执行解锁操作前程序出现异常,那这个所可能就一直存在。所以针对这个问题我们一般将unlock操作放在finally代码块中,就可以保证上了的锁一定会被解。
上面的两次if判断,第一个if相信大家很好理解。但为什么要用第二个if呢?再假设一个场景,现在有十个线程来读这个数据,而这个数据又不存在与缓存区,那么这十个线程中最先到的线程将执行“rw.writeLock().lock();”而另外九个线程将被阻塞,当第一个线程读完以后缓存区实际上已经就有了这个数据,但另外九个阻塞在“rw.writeLock().lock();”如果不加这层if他们会继续访问数据库,由此可见加了这层if对整个过程影响很大。这是比较细节的一点,就这一点Java的API文档也考虑到了,它的样例代码如下:
Java代码
![](https://oscdn.geek-share.com/Uploads/Images/Content/201910/19/90dae5b80145589fc0a3ce17d5ebb1cc.gif)
class CachedData {
Object data;
volatile boolean cacheValid;
ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
void processCachedData() {
rwl.readLock().lock();
if (!cacheValid)
{
// Must release read lock before acquiring write lock
rwl.readLock().unlock();
rwl.writeLock().lock();
// Recheck state because another thread might have acquired
// write lock and changed state before we did.
if (!cacheValid)
{
data = ...
cacheValid = true;
}
// Downgrade by acquiring read lock before releasing write lock
rwl.readLock().lock();
rwl.writeLock().unlock(); // Unlock write, still hold read
}
use(data);
rwl.readLock().unlock();
}
}
学习在继续!!!Go!Go!Go!!!
相关文章推荐
- 面试笔记----------HashMap排序
- iOS求职之OC面试题
- 前端面试题
- 这样投简历,99%没面试机会!
- 中国的程序员培训是不是有问题
- 一个程序员的顿悟:这6点带来的差距真的不是一点点
- 技术面试之经验总结
- 剑指offer 面试题14 把数组所有偶数放在奇数后面
- 一个32岁入门的70后程序员给我的启示
- Java面试题(判断集合中是否有两个数的和等于某个给定整数)
- SQL数据库面试题以及答案
- 大侠程序员的江湖传奇
- 创业如何正确选择技术支持?看了不爽可以报警!
- 36氪上的这七家程序员网站你都了解吗?
- 剑指Offer----面试题28----扩展:从1到n中随意取若干数使之与某一数相等
- JAVA多线程和并发基础面试问答
- Hibernate常见面试题
- 程序员的十种级别
- 剑指Offer----面试题28----扩展:八皇后问题
- 剑指Offer----面试题28----扩展:字符的所有组合