synchronized与ReentrantLock的介绍、使用、适合场景及比较

来源:互联网 发布:杰视帮淘宝美工教程 编辑:程序博客网 时间:2024/05/20 13:36

 synchronized与ReentrantLock的介绍、使用、适合场景及比较

http://blog.csdn.net/mr_smile2014/article/details/52151969

JDK 5.0为开发人员开发高性能的并发应用程序提供了一些很有效的新选择,目前存在两种锁机制:synchronized和Lock,Lock接口及其

实现类是JDK5增加的内容,ReentrantLock是Lock的实现。在实际的工作中,大家对synchronized和ReentrantLock都使用的比较多,今天对这

两种锁机制进行了总结并分享给各位朋友们,希望对大家有所帮助。

一、synchronized


1).介绍


synchronized 是Java语言的关键字,可用来给对象和方法或者代码块加锁,当它锁定一个方法或者一个代码块的时候,同一时刻最多只有

一个线程执行这段代码。当两个并发线程访问同一个对象object中的这个加锁同步代码块时,一个时间内只能有一个线程得到执行。另一个线程必

须等待当前线程执行完这个代码块以后才能执行该代码块。然而,当一个线程访问object的一个加锁代码块时,另一个线程仍然可以访问该object

中的非加锁代码块。


2).使用


很多朋友都知道synchronized可以对整个方法同步,也可以对方法的部分代码块进行同步。当synchronized修饰方法的时候,synchronized

是对象级的同步,意思就是说对于某个对象里面的被synchronized修饰的多个方法和synchronized(this)的代码块,当某一个线程访问一个synch

ronized修饰的方法或执行synchronized(this)的代码块,其它线程访问该对象内的synchronized修饰的方法或执行synchronized(this)的代码块

将会处于等待状态,当之前得到锁的线程执行完方法或代码块时,其它线程才可以访问。下面通过代码的方式给大家详细介绍:


1.synchronized修饰方法,表示同步作用于当前类的对象,某一个线程访问一个synchronized修饰的方法时,其它线程访问该对象内的

synchronized修饰的方法将会处于等待状态,代码如下:

[java] view plain copy
  1. public class Ceshi{  
  2.         public  synchronized  int add(int a,int b)  
  3.         {  
  4.             return a+b;  
  5.         }  
  6.         public synchronized  int subtract(int a,int b)  
  7.         {  
  8.         return a-b;  
  9.         }  
  10.     }  


2.synchronized修饰代码块


第一种:修饰普通对象即synchronized(obj),如string实例、其它类的实例。表示当多个线程访问同一个代码块时,如果obj相同,它们将

会起到同步作用,同一时间只能允许一个线程执行,代码如下:

[java] view plain copy
  1. public class SynTest {  
  2.         public static void main(String[] argv) {  
  3.             new TestThread("12345678asdf").start();  
  4.             new TestThread("12345678asdf").start();  
  5.         }  
  6.     public void loop(String random) {  
  7.         System.out.println("thread name:" + Thread.currentThread().getName()  
  8.                 + " 传入的random:" + random);  
  9.         synchronized (random) {  
  10.             System.out.println("thread name:"  
  11.                     + Thread.currentThread().getName() + " 开始执行循环");  
  12.             for (int i = 0; i < 10; i++) {  
  13.                 System.out.println("thread name:"  
  14.                         + Thread.currentThread().getName() + " i=" + i);  
  15.             }  
  16.             System.out.println("thread name:"  
  17.                     + Thread.currentThread().getName() + " 执行循环结束");  
  18.   
  19.         }  
  20.   
  21.     }  
  22.   
  23. }  
  24. class TestThread extends Thread {  
  25.     private String name;  
  26.   
  27.     public TestThread(String name) {  
  28.         super();  
  29.         this.name = name;  
  30.     }  
  31.   
  32.     public void run() {  
  33.         SynTest syntest = new SynTest();  
  34.         syntest.loop(name);  
  35.     }  
  36.   
  37. }  

在上面代码中当第一个线程访问时,虽然线程2已经进入loop方法,但是由于它们传入的random相同,所以线程2将处于等待状态,当线程1

执行完,线程2才能执行。下图是运行结果:


第二种:修饰类的字节码即synchronized(obj)中obj为某个类的class。虽然多个线程对该类的不同的实例上操作synchronized(class)修

饰的代码块,但是同一时刻只允许一个线程访问。(即使synchronized(class)在不同的方法内,都会同步)。代码如下:

[java] view plain copy
  1. public class SynTest {  
  2.     public static void main(String[] argv) {  
  3.         SynTest syn1=new SynTest();  
  4.         SynTest syn2=new SynTest();  
  5.         new TestThread(syn1).start();  
  6.         new TestThread(syn2).start();  
  7.     }  
  8.     public void loop() {  
  9.         System.out.println("thread name:" + Thread.currentThread().getName());  
  10.         synchronized (SynTest.class) {  
  11.             System.out.println(" thread name:"  
  12.                     + Thread.currentThread().getName() + " 开始执行循环");  
  13.             for (int i = 0; i < 10; i++) {  
  14.                 System.out.println("thread name:"  
  15.                         + Thread.currentThread().getName() + " i=" + i);  
  16.             }  
  17.             System.out.println("thread name:"  
  18.                     + Thread.currentThread().getName() + " 执行循环结束");  
  19.   
  20.         }  
  21.   
  22.     }  
  23.   
  24.       
  25. }  
  26. class TestThread extends Thread {  
  27.     private SynTest syn;  
  28.   
  29.     public TestThread(SynTest syn) {  
  30.         super();  
  31.         this.syn = syn;  
  32.     }  
  33.   
  34.     public void run() {  
  35.         syn.loop();  
  36.     }  
  37.   
  38. }  

在上面代码中当第一个线程访问时,虽然线程2已经进入loop方法,虽然它们操作的不同的SynTest的实例,但是synchronized修饰的

同一个类的字节码,所以线程2将处于等待状态,当线程1执行完,线程2才能执行。下图是运行结果:



3.synchronized修饰方法和代码块结合


synchronized修饰方法是作用在类的实例上,如果代码块也是修饰到改实例的话,他们就会达到互斥的效果。代码如下:

[java] view plain copy
  1. public class SynTest {  
  2.     public static void main(String[] argv) {  
  3.         SynTest syn1=new SynTest();  
  4.         new TestThread(syn1).start();  
  5.         new TestThread1(syn1).start();  
  6.     }  
  7.     //循环方法  
  8.     public void loop() {  
  9.         System.out.println("thread name:" + Thread.currentThread().getName());  
  10.         synchronized (this) {  
  11.             System.out.println("thread name:"  
  12.                     + Thread.currentThread().getName() + " 开始执行循环");  
  13.             for (int i = 0; i < 10; i++) {  
  14.                 System.out.println("thread name:"  
  15.                         + Thread.currentThread().getName() + " i=" + i);  
  16.             }  
  17.             System.out.println("thread name:"  
  18.                     + Thread.currentThread().getName() + " 执行循环结束");  
  19.   
  20.         }  
  21.   
  22.     }  
  23.     //循环方法1  
  24.     public synchronized void loop1() {  
  25.         System.out.println("thread name:" + Thread.currentThread().getName());  
  26.   
  27.         System.out.println("thread name:" + Thread.currentThread().getName()  
  28.                 + " 开始执行循环");  
  29.         for (int i = 0; i < 10; i++) {  
  30.             System.out.println("thread name:"  
  31.                     + Thread.currentThread().getName() + " i=" + i);  
  32.         }  
  33.         System.out.println("thread name:" + Thread.currentThread().getName()  
  34.                 + " 执行循环结束");  
  35.   
  36.     }  
  37.   
  38. }  
  39. //测试线程  
  40. class TestThread extends Thread {  
  41.     private SynTest syn;  
  42.   
  43.     public TestThread(SynTest syn) {  
  44.         super();  
  45.         this.syn = syn;  
  46.     }  
  47.   
  48.     public void run() {  
  49.         syn.loop();  
  50.     }  
  51.   
  52. }  
  53. //测试线程1  
  54. class TestThread1 extends Thread {  
  55.     private SynTest syn;  
  56.   
  57.     public TestThread1(SynTest syn) {  
  58.         super();  
  59.         this.syn = syn;  
  60.     }  
  61.   
  62.     public void run() {  
  63.       
  64.         syn.loop1();  
  65.     }  
  66.   
  67. }  

从上面的代码中,我们可以看出在loop()方法中,修饰的是作用于SynTest实例的代码块(synchronized (this)),在loop1中synchronized

是修饰的该方法。当线程1访问loop方法的synchronized (this)的代码块时,线程2将会处于等待状态。下面是运行结果:


3).使用场景

在资源竞争不是很激烈的情况下,偶尔会有同步的情形下,synchronized是很合适的。原因在于,编译程序通常会尽可能的进行优化

synchronize,另外可读性非常好,不管用没用过5.0多线程包的程序员都能理解。


二、ReentrantLock


1).介绍


在java.util.concurrent.lock 中的 Lock 框架是锁定的一个抽象,它允许把锁定的实现作为 Java 类,而不是作为语言的特性来实现。这就

为 Lock 的多种实现留下了空间,各种实现可能有不同的调度算法、性能特性或者锁定语义。 ReentrantLock 类实现了 Lock ,它拥有与 synch

ronized 相同的并发性和内存语义,但是添加了类似轮询锁、定时锁等候和可中断锁等候的一些特性。此外,它还提供了在激烈争用情况下更佳

的性能。(换句话说,当许多线程都想访问共享资源时,JVM 可以花更少的时候来调度线程,把更多时间用在执行线程上。)

reentrant 锁意味着什么呢?简单来说,它有一个与锁相关的获取计数器,如果拥有锁的某个线程再次得到锁,那么获取计数器就加1,

然后锁需要被释放两次才能获得真正释放。这模仿了 synchronized 的语义;如果线程进入由线程已经拥有的监控器保护的 synchronized 块,

就允许线程继续进行,当线程退出第二个(或者后续) synchronized 块的时候,不释放锁,只有线程退出它进入的监控器保护的第一个 synchron

ized 块时,才释放锁。

上面这一段是负责ibm网站上的描述,其实ReentrantLock是一个可重入的互斥锁,重入锁是一种递归无阻塞的同步机制。ReentrantLock由

最近成功获取锁,还没有释放的线程所拥有,当锁被另一个线程拥有时,调用lock的线程可以成功获取锁。如果锁已经被当前线程拥有,当前线程会

立即返回。

ReentrantLock可以等同于synchronized使用,但是比synchronized有更强的功能、可以提供更灵活的锁机制、同时减少死锁的发生概率。


2).使用


1.简单使用,使用下面的结构形式:

[java] view plain copy
  1. ReentrantLock lock = new ReentrantLock();    
  2.     if (lock.tryLock()) {  //如果已经被lock,则立即返回false不会等待,达到忽略操作的效果     
  3.         try {    
  4.         //操作    
  5.         } finally {    
  6.         lock.unlock();    
  7.         }    
  8.     }    
我们按照上面的结构编写测试demo:

[java] view plain copy
  1. public class SynTest {  
  2.     private ReentrantLock lock = new ReentrantLock();//乐观锁  
  3.   
  4.     public static void main(String[] argv) {  
  5.         SynTest syn1 = new SynTest();  
  6.         new TestThread(syn1).start();  
  7.         new TestThread(syn1).start();  
  8.     }  
  9.   
  10.     // 循环方法  
  11.     public void loop() {  
  12.         System.out.println("thread name:" + Thread.currentThread().getName());  
  13.         lock.lock(); // 加锁  
  14.         System.out.println("thread name:" + Thread.currentThread().getName()  
  15.                 + " 开始执行循环");  
  16.         for (int i = 0; i < 10; i++) {  
  17.             System.out.println("thread name:"  
  18.                     + Thread.currentThread().getName() + " i=" + i);  
  19.         }  
  20.         System.out.println("thread name:" + Thread.currentThread().getName()  
  21.                 + " 执行循环结束");  
  22.   
  23.         lock.unlock();//执行完成释放锁  
  24.   
  25.     }  
  26. }  
  27. // 测试线程  
  28. class TestThread extends Thread {  
  29.     private SynTest syn;  
  30.   
  31.     public TestThread(SynTest syn) {  
  32.         super();  
  33.         this.syn = syn;  
  34.     }  
  35.   
  36.     public void run() {  
  37.         syn.loop();  
  38.     }  
  39. }  
执行结果:


2.实际工作中使用:

上面的简单使用,不能很好在多线程互斥的条件下提高代码的并发性和高效性,下面我们以demo的形式给大家展示如果高效的使用。

[java] view plain copy
  1. import java.util.Map;  
  2. import java.util.concurrent.ConcurrentHashMap;  
  3. import java.util.concurrent.locks.Lock;  
  4. import java.util.concurrent.locks.ReentrantLock;  
  5.   
  6. public class SynTest {  
  7.     // 放并发锁的map  
  8.     public static Map<String, Lock> currentACCount = new ConcurrentHashMap<String, Lock>();  
  9.     // 放账户金额,本来应该放在数据库的,为了测试就放入map中  
  10.     public static Map<String, Long> accountMoney = new ConcurrentHashMap<String, Long>();  
  11.   
  12.     public static void main(String[] argv) {  
  13.         new TestThread("100001",100).start();  
  14.         new TestThread("100001",10).start();  
  15.         new TestThread("100002",100).start();  
  16.         new TestThread("100002",-10).start();  
  17.     }  
  18.   
  19.     /** 
  20.      * 获取锁 
  21.      *  
  22.      * @param key 
  23.      * @return 
  24.      */  
  25.     public static synchronized Lock getKey(String key) {  
  26.         Lock obj = currentACCount.get(key);  
  27.         if (obj == null) {  
  28.             obj = new ReentrantLock();  
  29.             currentACCount.put(key, obj);  
  30.         }  
  31.         System.out.println("获得key:" + key + ":" + obj);  
  32.         return obj;  
  33.     }  
  34.   
  35.     /** 
  36.      * 修改账户金额 
  37.      *  
  38.      * @param account 
  39.      *            账户 
  40.      * @param money 
  41.      *            金额 (单位:分) 
  42.      */  
  43.     public static void updateAccountMoney(String account, Long money) {  
  44.         System.out.println("thread name:" + Thread.currentThread().getName()  
  45.                 + " 账号:" + account + ",money=" + money);  
  46.         Lock lock = getKey(account);  
  47.         //当锁已经被其它线程获取到,该线程将会处于等待状态  
  48.         lock.lock();  
  49.         System.out.println("thread name:" + Thread.currentThread().getName()  
  50.                 + " 账号:" + account + "获取到锁成功");  
  51.         long oriAmount = accountMoney.get(account)==null?0:accountMoney.get(account);  
  52.         System.out.println("thread name:" + Thread.currentThread().getName()  
  53.                 + " 账号:" + account + " 账户原金额:oriAmount=" + oriAmount);  
  54.         long amount = oriAmount + money;  
  55.         System.out.println("thread name:" + Thread.currentThread().getName()  
  56.                 + " 账号:" + account + " 更新后金额:amount=" + amount);  
  57.         accountMoney.put(account, amount);  
  58.         System.out.println("thread name:" + Thread.currentThread().getName()  
  59.                 + " 账号:" + account + "释放锁");  
  60.         lock.unlock();  
  61.     }  
  62.   
  63. }  
  64.   
  65. // 测试线程  
  66. class TestThread extends Thread {  
  67.     private String account;  
  68.     private long money;  
  69.   
  70.     public TestThread(String account, long money) {  
  71.         super();  
  72.         this.account = account;  
  73.         this.money = money;  
  74.     }  
  75.   
  76.     public void run() {  
  77.         SynTest.updateAccountMoney(account, money);  
  78.     }  
  79.   
  80. }  

从demo中我们看到,我们将实现模拟多个线程对不同的账户的账户金额进行修改,然后重新保存。其中线程1、线程2是对账户100001进行操作,

线程3、线程4对100002进行操作。它们在运行过程中只有线程1与线程2,线程3与线程4互斥,不同的账户同时操作是互不影响的,这样就能大大提高性

能,也是推荐大家使用的方式,我们看一下运行结果:

[java] view plain copy
  1. thread name:Thread-3 账号:100002,money=-10  
  2. thread name:Thread-2 账号:100002,money=100  
  3. thread name:Thread-0 账号:100001,money=100  
  4. thread name:Thread-1 账号:100001,money=10  
  5. 获得key:100002:java.util.concurrent.locks.ReentrantLock@7f5f5897[Unlocked]  
  6. 获得key:100001:java.util.concurrent.locks.ReentrantLock@4cb162d5[Unlocked]  
  7. thread name:Thread-1 账号:100001获取到锁成功  
  8. 获得key:100001:java.util.concurrent.locks.ReentrantLock@4cb162d5[Locked by thread Thread-1]  
  9. thread name:Thread-3 账号:100002获取到锁成功  
  10. thread name:Thread-3 账号:100002 账户原金额:oriAmount=0  
  11. thread name:Thread-3 账号:100002 更新后金额:amount=-10  
  12. thread name:Thread-3 账号:100002释放锁  
  13. thread name:Thread-1 账号:100001 账户原金额:oriAmount=0  
  14. thread name:Thread-1 账号:100001 更新后金额:amount=10  
  15. 获得key:100002:java.util.concurrent.locks.ReentrantLock@7f5f5897[Locked by thread Thread-3]  
  16. thread name:Thread-1 账号:100001释放锁  
  17. thread name:Thread-2 账号:100002获取到锁成功  
  18. thread name:Thread-2 账号:100002 账户原金额:oriAmount=-10  
  19. thread name:Thread-2 账号:100002 更新后金额:amount=90  
  20. thread name:Thread-0 账号:100001获取到锁成功  
  21. thread name:Thread-2 账号:100002释放锁  
  22. thread name:Thread-0 账号:100001 账户原金额:oriAmount=10  
  23. thread name:Thread-0 账号:100001 更新后金额:amount=110  
  24. thread name:Thread-0 账号:100001释放锁  

从结果中我们可以看出Thread-1在Thread-3还未释放对100002账号操作的锁时,获取到了对100001账户操作的锁,而Thread-2在Thread-3

释放锁之后才获取到对100002账户操作的锁,Thread-0在Thread-1释放锁之后才获取到对100001账户操作的锁.所以看出效率比单个的lock效率高了

很多。


3).使用场景

由于ReentrantLock但是当同步非常激烈的时候,还能维持常态。所以比较适合高并发的场景。


三、synchronized与ReentrantLock比较


在资源竞争不是很激烈的情况下,偶尔会有同步的情形下,synchronized是很合适的,因为在资源竞争不激烈的情形下,ReentrantLock性能稍

微比synchronized差点点。但是当同步非常激烈的时候,synchronized的性能一下子能下降好几十倍。而ReentrantLock确还能维持常态。


下面一段话摘自于IBM网站上的一篇文章,写的非常好,在此分享一下:


什么时候选择用 ReentrantLock 代替 synchronized?

既然如此,我们什么时候才应该使用 ReentrantLock 呢?答案非常简单 —— 在确实需要一些 synchronized 所没有的特性的时候,比如时间锁

等候、可中断锁等候、无块结构锁、多个条件变量或者轮询锁。 ReentrantLock 还具有可伸缩性的好处,应当在高度争用的情况下使用它,但是请记

住,大多数 synchronized 块几乎从来没有出现过争用,所以可以把高度争用放在一边。我建议用 synchronized 开发,直到确实证明 synchronized 不

合适,而不要仅仅是假设如果使用 ReentrantLock “性能会更好”。请记住,这些是供高级用户使用的高级工具。(而且,真正的高级用户喜欢选择能够

找到的最简单工具,直到他们认为简单的工具不适用为止。)。一如既往,首先要把事情做好,然后再考虑是不是有必要做得更快。


结束语:


Lock 框架是同步的兼容替代品,它提供了 synchronized 没有提供的许多特性,它的实现在争用下提供了更好的性能。但是,这些明显存在的

好处,还不足以成为用 ReentrantLock 代替 synchronized 的理由。相反,应当根据您是否 需要 ReentrantLock 的能力来作出选择。大多数情况下,

您不应当选择它 —— synchronized 工作得很好,可以在所有 JVM 上工作,更多的开发人员了解它,而且不太容易出错。只有在真正需要 Lock 的时

候才用它。在这些情况下,您会很高兴拥有这款工具。

文章二:

synchronized 与 Lock 的那点事

最近在做一个监控系统,该系统主要包括对数据实时分析和存储两个部分,由于并发量比较高,所以不可避免的使用到了一些并发的知识。为了实现这些要求,后台使用一个队列作为缓存,对于请求只管往缓存里写数据。同时启动一个线程监听该队列,检测到数据,立即请求调度线程,对数据进行处理。 具体的使用方案就是使用同步保证数据的正常,使用线程池提高效率。

 
同步的实现当然是采用锁了,java中使用锁的两个基本工具是 synchronized 和 Lock。
 
一直很喜欢synchronized,因为使用它很方便。比如,需要对一个方法进行同步,那么只需在方法的签名添加一个synchronized关键字。
 
// 未同步的方法
public void test() {}
// 同步的方法
pubilc synchronized void test() {}
 
synchronized 也可以用在一个代码块上,看
 
public void test() {
     synchronized(obj) {
          System.out.println("===");
     }
}
 
synchronized 用在方法和代码块上有什么区别呢?
 
synchronized 用在方法签名上(以test为例),当某个线程调用此方法时,会获取该实例的对象锁,方法未结束之前,其他线程只能去等待。当这个方法执行完时,才会释放对象锁。其他线程才有机会去抢占这把锁,去执行方法test,但是发生这一切的基础应当是所有线程使用的同一个对象实例,才能实现互斥的现象。否则synchronized关键字将失去意义。
 
但是如果该方法为类方法,即其修饰符为static,那么synchronized 意味着某个调用此方法的线程当前会拥有该类的锁,只要该线程持续在当前方法内运行,其他线程依然无法获得方法的使用权!
 
synchronized 用在代码块的使用方式:synchronized(obj){//todo code here}
 
当线程运行到该代码块内,就会拥有obj对象的对象锁,如果多个线程共享同一个Object对象,那么此时就会形成互斥!特别的,当obj == this时,表示当前调用该方法的实例对象。即
 
public void test() {
     ...
     synchronized(this) {
          // todo your code
     }
     ...
}
 
此时,其效果等同于
public synchronized void test() {
     // todo your code
}
 
 
使用synchronized代码块,可以只对需要同步的代码进行同步,这样可以大大的提高效率。
 
小结:
使用synchronized 代码块相比方法有两点优势:
1、可以只对需要同步的使用
2、与wait()/notify()/nitifyAll()一起使用时,比较方便
 
----------------------------------------------------------------------------------------------------------------------------------------------------------
 
wait() 与notify()/notifyAll()
 
这三个方法都是Object的方法,并不是线程的方法!
wait():释放占有的对象锁,线程进入等待池,释放cpu,而其他正在等待的线程即可抢占此锁,获得锁的线程即可运行程序。而sleep()不同的是,线程调用此方法后,会休眠一段时间,休眠期间,会暂时释放cpu,但并不释放对象锁。也就是说,在休眠期间,其他线程依然无法进入此代码内部。休眠结束,线程重新获得cpu,执行代码。wait()和sleep()最大的不同在于wait()会释放对象锁,而sleep()不会!
 
notify(): 该方法会唤醒因为调用对象的wait()而等待的线程,其实就是对对象锁的唤醒,从而使得wait()的线程可以有机会获取对象锁。调用notify()后,并不会立即释放锁,而是继续执行当前代码,直到synchronized中的代码全部执行完毕,才会释放对象锁。JVM则会在等待的线程中调度一个线程去获得对象锁,执行代码。需要注意的是,wait()和notify()必须在synchronized代码块中调用
 
notifyAll()则是唤醒所有等待的线程。
 
为了说明这一点,举例如下:
两个线程依次打印"A""B",总共打印10次。
 
public class Consumer implements Runnable {
 
     @Override
     public synchronized void run() {
            // TODO Auto-generated method stub
            int count = 10;
            while(count > 0) {
                 synchronized (Test. obj) {
                     
                     System. out.print( "B");
                     count --;
                     Test. obj.notify(); // 主动释放对象锁
                     
                      try {
                           Test. obj.wait();
                           
                     } catch (InterruptedException e) {
                            // TODO Auto-generated catch block
                           e.printStackTrace();
                     }
                }
                
           }
     }
}
 
public class Produce implements Runnable {
 
     @Override
     public void run() {
            // TODO Auto-generated method stub
            int count = 10;
            while(count > 0) {
                 synchronized (Test. obj) {
                     
                      //System.out.print("count = " + count);
                     System. out.print( "A");
                     count --;
                     Test. obj.notify();
                     
                      try {
                           Test. obj.wait();
                     } catch (InterruptedException e) {
                            // TODO Auto-generated catch block
                           e.printStackTrace();
                     }
                }
                
           }
 
     }
 
}
 
测试类如下:
 
public class Test {
 
     public static final Object obj = new Object();
     
     public static void main(String[] args) {
           
            new Thread( new Produce()).start();
            new Thread( new Consumer()).start();
           
     }
}
 
这里使用static obj作为锁的对象,当线程Produce启动时(假如Produce首先获得锁,则Consumer会等待),打印“A”后,会先主动释放锁,然后阻塞自己。Consumer获得对象锁,打印“B”,然后释放锁,阻塞自己,那么Produce又会获得锁,然后...一直循环下去,直到count = 0.这样,使用Synchronized和wait()以及notify()就可以达到线程同步的目的。
 
----------------------------------------------------------------------------------------------------------------------------------------------------------
 
除了wait()和notify()协作完成线程同步之外,使用Lock也可以完成同样的目的。
 
ReentrantLock 与synchronized有相同的并发性和内存语义,还包含了中断锁等候和定时锁等候,意味着线程A如果先获得了对象obj的锁,那么线程B可以在等待指定时间内依然无法获取锁,那么就会自动放弃该锁。
 
但是由于synchronized是在JVM层面实现的,因此系统可以监控锁的释放与否,而ReentrantLock使用代码实现的,系统无法自动释放锁,需要在代码中finally子句中显式释放锁lock.unlock();
 
同样的例子,使用lock 如何实现呢?
 
public class Consumer implements Runnable {
 
     private Lock lock;
     public Consumer(Lock lock) {
            this. lock = lock;
     }
     @Override
     public void run() {
            // TODO Auto-generated method stub
            int count = 10;
            while( count > 0 ) {
                 try {
                      lock.lock();
                     count --;
                     System. out.print( "B");
                } finally {
                      lock.unlock(); //主动释放锁
                      try {
                           Thread. sleep(91L);
                     } catch (InterruptedException e) {
                            // TODO Auto-generated catch block
                           e.printStackTrace();
                     }
                }
           }
 
     }
 
}
 
public class Producer implements Runnable{
 
     private Lock lock;
     public Producer(Lock lock) {
            this. lock = lock;
     }
     @Override
     public void run() {
            // TODO Auto-generated method stub
            int count = 10;
            while (count > 0) {
                 try {
                      lock.lock();
                     count --;
                     System. out.print( "A");
                } finally {
                      lock.unlock();
                      try {
                           Thread. sleep(90L);
                     } catch (InterruptedException e) {
                            // TODO Auto-generated catch block
                           e.printStackTrace();
                     }
                }
           }
     }
}
 
调用代码:
 
public class Test {
 
     public static void main(String[] args) {
           Lock lock = new ReentrantLock();
           
           Consumer consumer = new Consumer(lock);
           Producer producer = new Producer(lock);
           
            new Thread(consumer).start();
            new Thread( producer).start();
           
     }
}
 
 
使用建议:
 
在并发量比较小的情况下,使用synchronized是个不错的选择,但是在并发量比较高的情况下,其性能下降很严重,此时ReentrantLock是个不错的方案。

阅读全文
0 0
原创粉丝点击