Android并发编程之如何使用ReentrantReadWriteLock替代synchronized来提高程序的效率
Android并发编程之如何使用ReentrantReadWriteLock替代synchronized来提高程序的效率
标签: android并发编程多线程 2015-12-16 14:46 1450人阅读 评论(1) 收藏 举报 分类: Android并发编程(4)版权声明:欢迎转载,转载请注明出处http://blog.csdn.net/nugongahou110
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倍的运行效率!如果在高并发的环境下,读者千千万万个,那么提高的性能就更加的明显了!
更多相关文章
- android 语言切换过程分析
- Android之 UI主线程
- 详解 Android(安卓)的 Activity 组件
- 另一个更简单的Android应用程序全屏的方法
- Android面试必备知识点总结
- Android(安卓)禁止Edittext弹出系统软键盘 的几种方法
- Android在全屏状态下键盘覆盖输入框问题
- Android(安卓)SDK4.0 离线安装方法
- android 嵌入服务端页面二 之WebView与页面互调