《Java源码分析》:ReadWriteLock(第一部分)

《Java源码分析》:ReadWriteLock(第一部分)

对象的方法中一旦加入synchronized关键字修饰,则任何时刻都只能有一个线程能够访问synchronized修饰的方法,例如:在Hashtable类中就是所有方法都使用了synchronized关键字修饰,这虽然解决了线程安全的问题,但是也降低了程序的并发性能(吞吐量)。在Hashtable中,即使我们使用get方法也只能只有一个线程能够访问,而本质上get方法并不修改内容,即使多个线程一起访问get方法也不存在线程不安全。基于此,就有了ReadWriteLock类,ReadWriteLock解决了这个问题.

对于ReadWriteLock,当写操作时,其它线程无法读取或写入数据,而当读操作时,其它线程无法写数据,但却可以读取数据。

ReentrantLock 实现了标准的互斥操作,也就是一次只能有一个线程持有锁,也即所谓独占锁的概念。显然这个特点在一定程度上面减低了吞吐量,实际上独占锁是一种保守的锁策略,在这种情况下任何“读/读”,“写/读”,“写/写”操作都不能同时发生。但是同样需要强调的一个概念是,锁是有一定的开销的,当并发比较大的时候,锁的开销就比较客观了。所以如果可能的话就尽量少用锁,非要用锁的话就尝试看能否改造为读写锁。

读写锁(ReadWriteLock):分为读锁(ReadLock)和写锁(WriteLock),多个读锁不互斥,读锁与写锁互斥,这是由jvm自己控制的,你只要上好相应的锁即可。如果你的代码只读数据,可以很多线程同时读,但不能同时写,那就上读锁;如果你的代码修改数据,只能有一个人在写,且不能同时读取,那就上写锁。总之,读数据的时候上读锁,写数据的时候上写锁!

ReadWriteLock是一个接口,而ReentrantReadWriteLock是其的具体的实现类。

ReentrantReadWriteLock会使用两把锁来解决问题,一个读锁,一个写锁

线程进入读锁的前提条件

1、没有其他线程的写锁,
2、没有写请求或者有写请求,但调用线程和持有锁的线程是同一个

线程进入写锁的前提条件

1、没有其他线程的读锁
2、没有其他线程的写锁

说到ReentrantReadWriteLock,首先要做的是与ReentrantLock划清界限。它和后者都是单独的实现,彼此之间没有继承或实现的关系。

然后就是总结这个锁机制的特性了:

(a).重入方面其内部的WriteLock可以获取ReadLock,但是反过来ReadLock想要获得WriteLock则永远都不要想。

(b).WriteLock可以降级为ReadLock,顺序是:先获得WriteLock再获得ReadLock,然后释放WriteLock,这时候线程将保持Readlock的持有。反过来ReadLock想要升级为WriteLock则不可能,为什么?参看(a),呵呵.

(c).ReadLock可以被多个线程持有并且在作用时排斥任何的WriteLock,而WriteLock则是完全的互斥。这一特性最为重要,因为对于高读取频率而相对较低写入的数据结构,使用此类锁同步机制则可以提高并发量。

(d).不管是ReadLock还是WriteLock都支持Interrupt,语义与ReentrantLock一致。

(e).WriteLock支持Condition并且与ReentrantLock语义一致,而ReadLock则不能使用Condition,否则抛出UnsupportedOperationException异常。

(f):此锁最多支持 65535 个递归写入锁和 65535 个读取锁。试图超出这些限制将导致锁方法抛出 Error。

上面说的都是ReadWriteLock的理论知识。在分析ReentrantReadWriteLock的内部实现之前,我们先看ReentrantReadWriteLock一个例子。

    public class ReadWriteLockDemo {
        static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        public static void main(String[] args) {
            int threadNum = 2;
            ReadWriteLockDemo demo = new ReadWriteLockDemo();
            RWMap rwMap = demo.new RWMap();
            //开启threadNum个读线程
            for(int i=0;i<threadNum;i++){
                new Thread(){
                    @Override
                    public void run() {
                        int j = 0;
                        while(j++<2){
                            rwMap.get(j);
                        }

                    }

                }.start();;

            }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //threadNum个写线程
            for(int i=0;i<threadNum;i++){
                new Thread(){
                    @Override
                    public void run() {
                        int j = 0;
                        while(j++<2){
                            rwMap.put(j,j+"");
                        }

                    }

                }.start();;

            }


        }

        class RWMap{

            private Map<Integer,String> map; 
            private ReadWriteLock rwLock ;
            private Lock readLock ;
            private Lock writeLock ;
            public RWMap(){
                map = new HashMap<Integer,String>();
                rwLock = new ReentrantReadWriteLock();
                readLock = rwLock.readLock();
                writeLock = rwLock.writeLock();
                initMap();
            }
            //初始化map
            private void initMap() {
                int len = 10;
                for(int i= 0;i<len;i++){
                    map.put(i, i+"");
                }
            }
            //get方法
            public String get(int key){
                readLock.lock();
                System.out.println(sdf.format(new Date())+"  "+Thread.currentThread().getName()+"正在读map中key="+key+"的数据。。。");
                try{
                    String value = map.get(key);
                    try {
                        Thread.sleep(1000);//一定时间间隔
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(sdf.format(new Date())+"  "+Thread.currentThread().getName()+"读的数据内容为:"+value);
                    return value;
                }finally{
                    readLock.unlock();
                }
            }

            public void put(int key,String value){
                writeLock.lock();
                try{
                    System.out.println(sdf.format(new Date())+"  "+Thread.currentThread().getName()+"正在将键值对(key,value)=("+key+","+value+")写入Map中");
                    map.put(key, value);
                    try {
                        Thread.sleep(1000);//一定时间间隔
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(sdf.format(new Date())+"  "+Thread.currentThread().getName()+"写数据结束");
                }finally{
                    writeLock.unlock();
                }
            }
        }

    }

运行结果:

2016-08-03 17:34:06  Thread-1正在读map中key=1的数据。。。
2016-08-03 17:34:06  Thread-0正在读map中key=1的数据。。。
2016-08-03 17:34:07  Thread-1读的数据内容为:1
2016-08-03 17:34:07  Thread-1正在读map中key=2的数据。。。
2016-08-03 17:34:07  Thread-0读的数据内容为:1
2016-08-03 17:34:08  Thread-1读的数据内容为:2
2016-08-03 17:34:08  Thread-3正在将键值对(key,value)=(1,1)写入Map中
2016-08-03 17:34:09  Thread-3写数据结束
2016-08-03 17:34:09  Thread-2正在将键值对(key,value)=(1,1)写入Map中
2016-08-03 17:34:10  Thread-2写数据结束
2016-08-03 17:34:10  Thread-2正在将键值对(key,value)=(2,2)写入Map中
2016-08-03 17:34:11  Thread-2写数据结束
2016-08-03 17:34:11  Thread-0正在读map中key=2的数据。。。
2016-08-03 17:34:12  Thread-0读的数据内容为:2
2016-08-03 17:34:12  Thread-3正在将键值对(key,value)=(2,2)写入Map中
2016-08-03 17:34:13  Thread-3写数据结束

从运行结果可以看出两点:

1、读锁是可以被多线程拥有的,即允许多个线程同时读取数据。

2、写锁是互斥的,写锁只能被一个线程拥有。当写入数据时,其它线程是不能够读数据和写入数据的。

下篇博文就来看下ReentrantReadWriteLock类的具体实现。

小结

当多线程并发时,如果读操作远远要大于写操作,我们就可以使用ReadWriteLock来进行加锁控制实现线程安全。

如果读操作和写操作差不多,则使用ReadWriteLock就没有多大优势。

参考资料

1、http://zk1878.iteye.com/blog/1005160

2、http://www.cnblogs.com/liuling/archive/2013/08/21/2013-8-21-03.html

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值