Android 同步与加锁
发布日期:2021-11-12 07:57:34 浏览次数:24 分类:技术文章

本文共 6789 字,大约阅读时间需要 22 分钟。

原文地址:http://blog.csdn.net/aqi00/article/details/51200409

同步synchronized

同步方法

synchronized可用来给方法或者代码块加锁,当它修饰一个方法或者一个代码块的时候,同一时刻最多只有一个线程执行这段代码。这就意味着,当两个并发线程同时访问synchronized代码块时,两个线程只能是排队做串行处理,另一个线程要等待前一个线程执行完该代码块后,才能再次执行synchronized代码块。
使用synchronized修饰某个方法,该方法便成为一个同步方法,在同一时刻只能有一个线程执行该方法。可是,synchronized的锁机制太重量级,不但整个同步方法的代码都加锁,就连该方法用到的所有类变量也一并加锁。因此,同步方法覆盖的代码越多,加锁操作对效率的影响就越严重。

显式指纹(同步代码块)

为缩小同步方法的影响方法,我们可让synchronized只修饰某个代码块,而不必修饰整个方法,synchronized修饰后的代码块叫做同步代码块。同步代码块要先指定该代码块的密钥对象,这个对象可以是任意相关类的实例,它相当于一个指纹,每个线程执行同步代码块时都要先验证指纹,指纹相同的线程进入同一个队列依次排队,若指纹不同则进入另外的执行队列。
下面是同步方法和同步代码块的代码示例:
[java]
  1. public class TestCode {  
  2.       
  3.     public static void main(String[] args) {  
  4.         TestCode1 t1 = new TestCode1();  
  5.         TestCode2 t2 = new TestCode2();  
  6.         TestCode3 t3 = new TestCode3();  
  7.         Thread ta = new Thread(t1, "A");  
  8.         Thread tb = new Thread(t2, "B");  
  9.         Thread tc = new Thread(t3, "C");  
  10.         ta.start();  
  11.         tb.start();  
  12.         tc.start();  
  13.     }  
  14.       
  15.     private static TestCode test = new TestCode();  
  16.   
  17.     public static class TestCode1 implements Runnable {  
  18.         public synchronized void run() {  
  19.             for (int i = 0; i < 5; i++) {  
  20.                 try {  
  21.                     Thread.sleep(1000);  
  22.                 } catch (InterruptedException e) {  
  23.                     e.printStackTrace();  
  24.                 }  
  25.                 System.out.println(Thread.currentThread().getName()+" synchronized loop "+i);  
  26.             }  
  27.         }  
  28.     }  
  29.       
  30.     public static class TestCode2 implements Runnable {  
  31.         public void run() {  
  32.             synchronized (test) {  //这里如果使用this则表示新实例,就不与其他代码块搞在一起  
  33.                 for (int i = 0; i < 5; i++) {  
  34.                     try {  
  35.                         Thread.sleep(1000);  
  36.                     } catch (InterruptedException e) {  
  37.                         e.printStackTrace();  
  38.                     }  
  39.                     System.out.println(Thread.currentThread().getName()+" synchronized loop "+i);  
  40.                 }  
  41.             }  
  42.         }  
  43.     }  
  44.   
  45.     public static class TestCode3 implements Runnable {  
  46.         public void run() {  
  47.             synchronized (test) {  //这里如果使用this则表示新实例,就不与其他代码块搞在一起  
  48.                 for (int i = 0; i < 5; i++) {  
  49.                     try {  
  50.                         Thread.sleep(1000);  
  51.                     } catch (InterruptedException e) {  
  52.                         e.printStackTrace();  
  53.                     }  
  54.                     System.out.println(Thread.currentThread().getName()+" synchronized loop "+i);  
  55.                 }  
  56.             }  
  57.         }  
  58.     }  
  59.       
  60. }  
下面是该示例代码的执行结果:
[java]
  1. synchronized loop 0  
  2. synchronized loop 0  
  3. synchronized loop 1  
  4. synchronized loop 1  
  5. synchronized loop 2  
  6. synchronized loop 2  
  7. synchronized loop 3  
  8. synchronized loop 3  
  9. synchronized loop 4  
  10. synchronized loop 4  
  11. synchronized loop 0  
  12. synchronized loop 1  
  13. synchronized loop 2  
  14. synchronized loop 3  
  15. synchronized loop 4  
从输出结果可以看出,同步代码块如果加锁的是同一个对象实例,那么这两个同步代码块也被看作是互相排他的,同一时刻也只能有两个代码块的其中之一被执行,因此日志显示:线程B的同步代码块都执行完了,才开始执行线程B的同步代码块,即使两个代码块是在不同的地方。

隐式指纹

前面说到,synchronized会对同步代码内部的类变量加锁,这样一来,如果两个同步代码都使用了某个类变量,那也会产生排队等待的情况。因为某线程执行第一个同步代码时,会给类变量上锁;然后另一线程执行第二个同步代码,也准备给类变量上锁,结果发现类变量已经上锁无法再次上锁;所以后面的线程只好等待前面的线程执行完成。这种情况下,两个同步代码使用同一个类变量,我们可将其当作隐式指纹;而同步代码块事先指定密钥对象,可称作是显式指纹。
下面是隐式指纹的代码示例:
[java]
  1. public class TestSynchronized {  
  2.   
  3.     private int count = 0;  
  4.   
  5.     public synchronized void plus() {  
  6.         System.out.println("begin plus count="+count);  
  7.         count++;  
  8.         try {  
  9.             Thread.sleep(2000);  
  10.             System.out.println("end plus count="+count);  
  11.         } catch (Exception e) {  
  12.             e.printStackTrace();  
  13.         }  
  14.     }  
  15.   
  16.     public synchronized void minus() {  
  17.         System.out.println("begin minus count="+count);  
  18.         count--;  
  19.         System.out.println("end minus count="+count);  
  20.     }  
  21.   
  22.     public void print() {  
  23.         try {  
  24.             Thread.sleep(1000);  
  25.             System.out.println("print count="+count);  
  26.         } catch (Exception e) {  
  27.             e.printStackTrace();  
  28.         }  
  29.     }  
  30.   
  31.     public static void main(String[] args) throws Exception {  
  32.         final TestSynchronized ts = new TestSynchronized();  
  33.         Thread thread_plus = new Thread("thread_plus") {  
  34.             public void run() {  
  35.                 System.out.println("Thread:" + super.getName());  
  36.                 ts.plus();  
  37.             }  
  38.         };  
  39.   
  40.         final Thread thread_minus = new Thread("thread_minus") {  
  41.             public void run() {  
  42.                 System.out.println("Thread:" + super.getName());  
  43.                 //改变ts.sub和ts.print的执行顺序,看看是什么情况  
  44.                 ts.minus();  
  45.                 ts.print();  
  46.                 // ts.minus();  
  47.             }  
  48.         };  
  49.         thread_plus.start();  
  50.         Thread.sleep(1000);  
  51.         thread_minus.start();  
  52.     }  
  53.       
  54. }  
下面是该示例代码的执行结果:
[java]
  1. Thread:thread_plus  
  2. begin plus count=0  
  3. Thread:thread_minus  
  4. end plus count=1  
  5. begin minus count=1  
  6. end minus count=0  
  7. 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加锁。匿名内部类的说明参见《》。
下面是同时采用两种加锁方式的示例代码:
[java]
  1. import java.text.SimpleDateFormat;  
  2. import java.util.Date;  
  3. import java.util.concurrent.locks.ReentrantLock;  
  4.   
  5. public class TestAnonymous {  
  6.   
  7.     public static String getNowDateTime() {  
  8.         SimpleDateFormat s_format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");  
  9.         Date d_date = new Date();  
  10.         String s_date = "";  
  11.         s_date = s_format.format(d_date);  
  12.         return s_date;  
  13.     }  
  14.   
  15.     private static void runSync() {  
  16.         for (int i = 0; i < 5; i++) {  
  17.             final int pos = i;  
  18.             Thread t = new Thread() {  
  19.                 @Override  
  20.                 public synchronized void run() {  
  21.                     try {  
  22.                         Thread.sleep(1000);  
  23.                     } catch (InterruptedException e) {  
  24.                         e.printStackTrace();  
  25.                     }  
  26.                     System.out.println(getNowDateTime() + " runSync pos=" + pos);  
  27.                 }  
  28.             };  
  29.             t.start();  
  30.         }  
  31.     }  
  32.   
  33.     private final static ReentrantLock lock = new ReentrantLock();  
  34.   
  35.     private static void runLock() {  
  36.         for (int i = 0; i < 5; i++) {  
  37.             final int pos = i;  
  38.             Thread t = new Thread() {  
  39.                 @Override  
  40.                 public void run() {  
  41.                     try {  
  42.                         lock.lock();  
  43.                         Thread.sleep(1000);  
  44.                         System.out.println(getNowDateTime() + " runLock pos=" + pos);  
  45.                     } catch (InterruptedException e) {  
  46.                         e.printStackTrace();  
  47.                     } finally {  
  48.                         lock.unlock();  
  49.                     }  
  50.                 }  
  51.             };  
  52.             t.start();  
  53.         }  
  54.     }  
  55.   
  56.     public static void main(String[] args) {  
  57.         runSync();  
  58.         runLock();  
  59.     }  
  60.   
  61. }  
下面是该示例代码的执行结果:
[java]
  1. 2016-04-20 11:25:36 runSync pos=1  
  2. 2016-04-20 11:25:36 runSync pos=4  
  3. 2016-04-20 11:25:36 runSync pos=2  
  4. 2016-04-20 11:25:36 runSync pos=3  
  5. 2016-04-20 11:25:36 runSync pos=0  
  6. 2016-04-20 11:25:36 runLock pos=0  
  7. 2016-04-20 11:25:37 runLock pos=1  
  8. 2016-04-20 11:25:38 runLock pos=2  
  9. 2016-04-20 11:25:39 runLock pos=3  
  10. 2016-04-20 11:25:40 runLock pos=4  
从输出结果可以看出,在匿名内部类中,synchronized的加锁操作不符合预期结果,各线程仍是乱序执行。相比之下,Lock的加锁操作符合预期,各线程按顺序依次执行。

转载地址:https://blog.csdn.net/happy_love1990/article/details/77008073 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:Android Java的容器类
下一篇:Android 异常容错处理

发表评论

最新留言

网站不错 人气很旺了 加油
[***.192.178.218]2024年04月19日 00时56分12秒