Android 同步与加锁
来源:互联网 发布:android 打开移动网络 编辑:程序博客网 时间:2024/05/22 12:41
原文地址:http://blog.csdn.net/aqi00/article/details/51200409
同步synchronized
同步方法
synchronized可用来给方法或者代码块加锁,当它修饰一个方法或者一个代码块的时候,同一时刻最多只有一个线程执行这段代码。这就意味着,当两个并发线程同时访问synchronized代码块时,两个线程只能是排队做串行处理,另一个线程要等待前一个线程执行完该代码块后,才能再次执行synchronized代码块。使用synchronized修饰某个方法,该方法便成为一个同步方法,在同一时刻只能有一个线程执行该方法。可是,synchronized的锁机制太重量级,不但整个同步方法的代码都加锁,就连该方法用到的所有类变量也一并加锁。因此,同步方法覆盖的代码越多,加锁操作对效率的影响就越严重。
显式指纹(同步代码块)
为缩小同步方法的影响方法,我们可让synchronized只修饰某个代码块,而不必修饰整个方法,synchronized修饰后的代码块叫做同步代码块。同步代码块要先指定该代码块的密钥对象,这个对象可以是任意相关类的实例,它相当于一个指纹,每个线程执行同步代码块时都要先验证指纹,指纹相同的线程进入同一个队列依次排队,若指纹不同则进入另外的执行队列。下面是同步方法和同步代码块的代码示例:
- public class TestCode {
- public static void main(String[] args) {
- TestCode1 t1 = new TestCode1();
- TestCode2 t2 = new TestCode2();
- TestCode3 t3 = new TestCode3();
- Thread ta = new Thread(t1, "A");
- Thread tb = new Thread(t2, "B");
- Thread tc = new Thread(t3, "C");
- ta.start();
- tb.start();
- tc.start();
- }
- private static TestCode test = new TestCode();
- public static class TestCode1 implements Runnable {
- public synchronized void run() {
- for (int i = 0; i < 5; i++) {
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println(Thread.currentThread().getName()+" synchronized loop "+i);
- }
- }
- }
- public static class TestCode2 implements Runnable {
- public void run() {
- synchronized (test) { //这里如果使用this则表示新实例,就不与其他代码块搞在一起
- for (int i = 0; i < 5; i++) {
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println(Thread.currentThread().getName()+" synchronized loop "+i);
- }
- }
- }
- }
- public static class TestCode3 implements Runnable {
- public void run() {
- synchronized (test) { //这里如果使用this则表示新实例,就不与其他代码块搞在一起
- for (int i = 0; i < 5; i++) {
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println(Thread.currentThread().getName()+" synchronized loop "+i);
- }
- }
- }
- }
- }
下面是该示例代码的执行结果:
- B synchronized loop 0
- A synchronized loop 0
- A synchronized loop 1
- B synchronized loop 1
- B synchronized loop 2
- A synchronized loop 2
- B synchronized loop 3
- A synchronized loop 3
- A synchronized loop 4
- B synchronized loop 4
- C synchronized loop 0
- C synchronized loop 1
- C synchronized loop 2
- C synchronized loop 3
- C synchronized loop 4
从输出结果可以看出,同步代码块如果加锁的是同一个对象实例,那么这两个同步代码块也被看作是互相排他的,同一时刻也只能有两个代码块的其中之一被执行,因此日志显示:线程B的同步代码块都执行完了,才开始执行线程B的同步代码块,即使两个代码块是在不同的地方。
隐式指纹
前面说到,synchronized会对同步代码内部的类变量加锁,这样一来,如果两个同步代码都使用了某个类变量,那也会产生排队等待的情况。因为某线程执行第一个同步代码时,会给类变量上锁;然后另一线程执行第二个同步代码,也准备给类变量上锁,结果发现类变量已经上锁无法再次上锁;所以后面的线程只好等待前面的线程执行完成。这种情况下,两个同步代码使用同一个类变量,我们可将其当作隐式指纹;而同步代码块事先指定密钥对象,可称作是显式指纹。下面是隐式指纹的代码示例:
- public class TestSynchronized {
- private int count = 0;
- public synchronized void plus() {
- System.out.println("begin plus count="+count);
- count++;
- try {
- Thread.sleep(2000);
- System.out.println("end plus count="+count);
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- public synchronized void minus() {
- System.out.println("begin minus count="+count);
- count--;
- System.out.println("end minus count="+count);
- }
- public void print() {
- try {
- Thread.sleep(1000);
- System.out.println("print count="+count);
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- public static void main(String[] args) throws Exception {
- final TestSynchronized ts = new TestSynchronized();
- Thread thread_plus = new Thread("thread_plus") {
- public void run() {
- System.out.println("Thread:" + super.getName());
- ts.plus();
- }
- };
- final Thread thread_minus = new Thread("thread_minus") {
- public void run() {
- System.out.println("Thread:" + super.getName());
- //改变ts.sub和ts.print的执行顺序,看看是什么情况
- ts.minus();
- ts.print();
- // ts.minus();
- }
- };
- thread_plus.start();
- Thread.sleep(1000);
- thread_minus.start();
- }
- }
下面是该示例代码的执行结果:
- Thread:thread_plus
- begin plus count=0
- Thread:thread_minus
- end plus count=1
- begin minus count=1
- end minus count=0
- print count=0
从输出结果可以看出,minus线程总是在plus线程结束之后才开始执行,虽然两个线程的同步方法并没有指定加锁的对象,但两个方法内部都使用了类变量count,因此这两个方法成为了拥有同一个隐式指纹的排他方法。
加锁Lock
因为synchronized是重量级的加锁,对程序效率影响大,而且容易偏离预期,所以从jdk1.5开始,java引入了Lock接口,用来实现轻量级的加锁操作。Lock接口主要有两个派生类,分别是普通的重入锁ReentrantLock,以及读写锁ReentrantReadWriteLock。重入锁ReentrantLock
ReentrantLock是不区分类型的普通锁,在lock与unlock之间的代码就是被锁保护的代码块。ReentrantLock的常用方法如下:
lock : 加锁。该操作不允许中断,重复加锁时会一直等待。
unlock : 解锁。
lockInterruptibly : 允许中断的加锁。允许在等待时由其它线程调用等待线程的Thread.interrupt方法来中断等待线程的等待而直接返回,这时不用获取锁,而会抛出一个InterruptedException
tryLock : 尝试锁。若之前未锁则加锁,并返回true;若之前已被当前线程锁,也返回true;若之前已被其他线程锁,则返回false。
getHoldCount : 获取当前线程的加锁次数。
isLocked : 判断是否加锁。
getQueueLength : 获取等待队列的长度。
读写锁ReentrantReadWriteLock
ReentrantReadWriteLock是区分了读锁和写锁的混合锁,读锁是共享锁,写锁是排它锁。ReentrantReadWriteLock的常用方法如下:
readLock : 获得读锁对象ReentrantReadWriteLock.ReadLock
writeLock : 获得写锁对象ReentrantReadWriteLock.WriteLock
isWriteLocked : 判断是否加了写锁。
getReadHoldCount : 获取加读锁的次数。
getWriteHoldCount : 获取加写锁的次数。
getQueueLength : 获取等待队列的长度。
下面是ReentrantReadWriteLock.ReadLock的常用方法:
lock : 加读锁。
lockInterruptibly : 允许中断的加锁。
tryLock : 尝试加读锁。
unlock : 解除读锁。
下面是ReentrantReadWriteLock.WriteLock的常用方法:
lock : 加写锁。
lockInterruptibly : 允许中断的加锁。
tryLock : 尝试加写锁。
unlock : 解除写锁。
匿名内部类的加锁
匿名内部类使用synchronized要小心,虽然看起来同步代码只有一个,但是匿名内部类每次使用都是创建新类并实例化,所以多次使用匿名内部类其实是调用不同的类,不同类的内部同步方法,自然是互不影响的了。这就是匿名内部类时常令人迷惑的一个地方,遇到这种情况,建议采用Lock加锁,而不要用synchronized加锁。匿名内部类的说明参见《Android开发笔记(八十六)几个特殊的类》。下面是同时采用两种加锁方式的示例代码:
- import java.text.SimpleDateFormat;
- import java.util.Date;
- import java.util.concurrent.locks.ReentrantLock;
- public class TestAnonymous {
- public static String getNowDateTime() {
- SimpleDateFormat s_format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
- Date d_date = new Date();
- String s_date = "";
- s_date = s_format.format(d_date);
- return s_date;
- }
- private static void runSync() {
- for (int i = 0; i < 5; i++) {
- final int pos = i;
- Thread t = new Thread() {
- @Override
- public synchronized void run() {
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println(getNowDateTime() + " runSync pos=" + pos);
- }
- };
- t.start();
- }
- }
- private final static ReentrantLock lock = new ReentrantLock();
- private static void runLock() {
- for (int i = 0; i < 5; i++) {
- final int pos = i;
- Thread t = new Thread() {
- @Override
- public void run() {
- try {
- lock.lock();
- Thread.sleep(1000);
- System.out.println(getNowDateTime() + " runLock pos=" + pos);
- } catch (InterruptedException e) {
- e.printStackTrace();
- } finally {
- lock.unlock();
- }
- }
- };
- t.start();
- }
- }
- public static void main(String[] args) {
- runSync();
- runLock();
- }
- }
下面是该示例代码的执行结果:
- 2016-04-20 11:25:36 runSync pos=1
- 2016-04-20 11:25:36 runSync pos=4
- 2016-04-20 11:25:36 runSync pos=2
- 2016-04-20 11:25:36 runSync pos=3
- 2016-04-20 11:25:36 runSync pos=0
- 2016-04-20 11:25:36 runLock pos=0
- 2016-04-20 11:25:37 runLock pos=1
- 2016-04-20 11:25:38 runLock pos=2
- 2016-04-20 11:25:39 runLock pos=3
- 2016-04-20 11:25:40 runLock pos=4
从输出结果可以看出,在匿名内部类中,synchronized的加锁操作不符合预期结果,各线程仍是乱序执行。相比之下,Lock的加锁操作符合预期,各线程按顺序依次执行。
阅读全文
0 0
- Android 同步与加锁
- Android开发笔记(八十八)同步与加锁
- Android开发笔记(八十八)同步与加锁
- java加锁与同步方法
- java加锁与同步方法
- Android 同步代码块,synchronized加锁
- java多线程、并发系列之 (synchronized)同步与加锁机制
- 同步,一定要加锁吗?
- 是否加锁同步备忘
- 多线程Thread同步synchronized(加锁)
- 【java】同步,一定要加锁吗?
- 【java】同步,一定要加锁吗?
- 线程同步(两个线程同步 加锁)
- java多线程三种方式区别,java多线程,线程同步方式,线程同步加锁的方法,wait与sleep区别
- 注册表加锁与解锁
- 自动加锁与解锁
- 加锁与事务MySQL
- 索引与加锁
- WPS for Linux(ubuntu)字体配置(字体缺失解决办法)
- linux设备驱动模型之device
- 原根的一些理解与题目
- 卷积神经网络CNN(8)—— Pix2Pix Application -- Aerialmap Lane Line Detection (Pix2Pix应用:航拍图车道线检测)
- ES6基础--import和export
- Android 同步与加锁
- hdu 2094 拓扑排序
- Java集合类详解
- (三)读取本地音乐文件
- [LeetCode]41. First Missing Positive
- ibatis 缓存设置 cacheModel
- 获取真实的客户端IP地址
- Android Java的容器类
- Android Studio快速定位当前文件中错误代码