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

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

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 threads = new ArrayList();        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倍的运行效率!如果在高并发的环境下,读者千千万万个,那么提高的性能就更加的明显了!

更多相关文章

  1. android 语言切换过程分析
  2. Android之 UI主线程
  3. 详解 Android(安卓)的 Activity 组件
  4. 另一个更简单的Android应用程序全屏的方法
  5. Android面试必备知识点总结
  6. Android(安卓)禁止Edittext弹出系统软键盘 的几种方法
  7. Android在全屏状态下键盘覆盖输入框问题
  8. Android(安卓)SDK4.0 离线安装方法
  9. android 嵌入服务端页面二 之WebView与页面互调

随机推荐

  1. 带你一步一步深入Handler源码,拿下面试官
  2. android自动化测试CTS源码分析之一
  3. Android(安卓)时间间隔显示处理 1小时前
  4. android Activity之间跳转。
  5. Android桌面组件开发例子
  6. android中的Handler和Callback机制
  7. Android(安卓)条码扫描器 一维条形码 二
  8. Android(安卓)5.0 之SwipeRefreshLayout
  9. Android(安卓)装载器---例子
  10. 查看ANR日志