Android并发编程之如何使用ReentrantReadWriteLock替代synchronized来提高程序的效率

Android并发编程之如何使用ReentrantReadWriteLock替代synchronized来提高程序的效率

标签: android并发编程多线程
  1450人阅读  评论(1)  收藏  举报
  分类:

Java的synchronized关键字可以帮助我们解决多线程并发的问题,比如我们有一个公共资源,多个线程都来操作这个公共的资源,就会出现并发的问题,比如不同的线程对同一个数据同时进行读和写,肯定会使得每个线程最后拿到的都不是自己所希望拿到的值,为了解决这个问题,我们可以使用synchronized关键字加锁。

以前synchronized由于性能消耗太大,在Java SE 1.6对它进行了优化,使得synchronized锁现在有4种状态:无锁、偏向锁、轻量级锁、重量级锁,他们性能消耗是由低向高的,在没有竞争出现的时候,它是偏向锁,性能消耗非常小,基本就没什么消耗,当竞争来了并且竞争变大,它就会逐渐升级成重量级锁,此时的锁的性能开销很大,当一个线程获得锁后,其它的线程只能阻塞等待。

这就是关键,当线程竞争时,synchronized会升级成重量级锁,当一个线程持有锁时,其他的线程只能阻塞等待,有些时候会给我们带来不必要的性能损耗。

我们先来看一段代码

public class Storage {
    //被操作的公共数据
    private int num;

    //使用同步方法,锁对象是当前类对象
    private synchronized void write(){
        try {
            //模拟一下耗时操作
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //给num增10
        num+=10;
        System.out.println("Writer:num="+num);
    }
    //使用同步方法,锁对象是当前类对象
    private synchronized void read(){
        try {
            //模拟一下耗时操作
            TimeUnit.SECONDS.sleep(1);
            System.out.println("Reader:num="+num);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

Storage类中有一个公共变量,write()和read()方法是来操作公共变量num的,我们使用synchronized关键在修饰方法,锁对象为当前类对象,那么当执行write()方法时,不能有线程执行read()方法,同理当执行read()方法时,也不能有线程执行write()方法。

public static class Writer implements Runnable{
        private Storage storage;
        public Writer(Storage storageByLock){
            storage = storageByLock;
        }
        @Override
        public void run() {
            storage.write();
        }
    }
    public static class Reader implements Runnable{
        private Storage storage;
        public Reader(Storage storageByLock){
            storage = storageByLock;
        }
        @Override
        public void run() {
            storage.read();
        }
    }
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

这是写着和读者,他们实现了Runnable接口,在run方法里调用了Storage类的write()方法和read()方法。

public static void main(String[] args) {
        Storage storage = new Storage();
        List<Thread> threads = new ArrayList<Thread>();
        for(int i=0 ; i<5 ; i++){
            Thread t = new Thread(new Writer(storage));
            threads.add(t);
        }
        for(int i=0 ; i<5; i++){
            Thread t = new Thread(new Reader(storage));
            threads.add(t);
        }
        long startTime = System.currentTimeMillis();
        for(Thread t : threads){
            t.start();
        }
        for(Thread t : threads){
            try {
                t.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        long endTime = System.currentTimeMillis();
        System.out.println("time="+(endTime-startTime));
    }
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

这是我们的main方法,首先我们new了一个Storage对象,作为公共的资源,我们创建了5个写者和5个读者,他们分别都是一个线程,我们来运行一下,看看他们是如何执行的 
这里写图片描述 
我们看到他们是顺序执行的,当一个线程正在执行时,其他的线程是阻塞的,synchronized关键字为我们解决了并发引起的线程安全问题。

但是,我们想象这样一种情况,比如写着很少,读者很多,而读者只是想读出数据,并没有对数据进行修改,那么在读者很多的情况下,它还是得按照一个一个的顺序来执行,这样的效率会很慢,我们来模拟一下这种情况

for(int i=0 ; i<1; i++){
            Thread t = new Thread(new Writer(storage));
            threads.add(t);
        }
        for(int i=0 ; i<10; i++){
            Thread t = new Thread(new Reader(storage));
            threads.add(t);
        }
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

我们现在只需要一个写着,但是我们有10个读者,我们来看一看运行的情况 
这里写图片描述 
我们看到读者们只是读出数据,并没有改变数据,他们读的值都是一样的,最后程序运行了11秒多才结束。

所以,上面那个情况是有问题的,我们应该需要这样一种机制,就是当有写者正在写的时候,他是独占资源的,其他无论读者还是写者只能阻塞等待;当没有写者正在写的时候,读者们是可以并行读到数据的,这样当写着很少,读者很多的时候,读者们几乎可以同时完成读的操作,这样就大大提升了程序的运行效率。

Java给我们提供了ReentrantReadWriteLock可以解决上面的问题,我们将Storage类中的write()方法和read()方法使用ReentrantReadWriteLock来进行加锁

public class Storage {
    //被操作的公共数据
    private int num;
    //Lock锁
    private ReadWriteLock lock = new ReentrantReadWriteLock();

    private void write(){
        //获取到写者锁
        //当线程获取到写着锁时,其他线程不可以再获得写者锁和读者锁
        //它就相当于synchronized锁住的方法
        lock.writeLock().lock();
        try{
            TimeUnit.SECONDS.sleep(1);
            num+=10;
            System.out.println("Writer:num="+num);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally{
            //一定不要忘了在finially中释放锁
            lock.writeLock().unlock();
        }
    }

    private void read(){
        //获取读者锁
        //读者锁可以同时由很多个线程获得,因此可以增加效率
        lock.readLock().lock();
        try{
            TimeUnit.SECONDS.sleep(1);
            System.out.println("Reader:num="+num);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally{
            //一定不要忘了释放锁
            lock.readLock().unlock();
        }
    }
}
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38

这里需要解释一下:为什么读者锁可以由多个线程同时获得? 
如果当前没有写者存在,那么线程可以持有读者锁(只要有写者存在,就不能有读者存在,只有当写者释放了写者锁之后,读者才能够获得读者锁),每当一个线程持有一个读者锁后,系统就会将读者锁的数量加一,每当一个线程释放一个读者锁后,系统会将读者锁的数量减一,只有当读者锁的数量为0时,写者才能够获得写者锁,否则他会阻塞等待所有的读者都读取完毕后,才能进行写操作!

好了,其他地方的代码不变,我们还是模拟1个写者和10个读者,我们看看运行的结果如何 
这里写图片描述 
看见没,2秒就结束了!我们几乎提高了4倍的运行效率!如果在高并发的环境下,读者千千万万个,那么提高的性能就更加的明显了!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值