在使用Lock之前,我们都使用Object 的wait和notify实现同步的。举例来说,一个producer和consumer,consumer发现没有东西了,等待,produer生成东西了,唤醒。
| 线程consumer | 线程producer |
| synchronize(obj){ obj.wait();//没有东西了,等待 } | synchronize(obj){ obj.notify();//有东西了,唤醒 } |
有了lock后,世道变了,现在是:
| lock.lock(); condition.await(); lock.unlock(); | lock.lock(); condition.signal(); lock.unlock(); |
为了突出区别,省略了若干细节。区别有三点:
- 1. lock不再用synchronize把同步代码包装起来;
- 2. 阻塞需要另外一个对象condition;
- 3. 同步和唤醒的对象是condition而不是lock,对应的方法是await和signal,而不是wait和notify。
为什么需要使用condition呢?简单一句话,lock更灵活。以前的方式只能有一个等待队列,在实际应用时可能需要多个,比如读和写。为了这个灵活性,lock将同步互斥控制和等待队列分离开来,互斥保证在某个时刻只有一个线程访问临界区(lock自己完成),等待队列负责保存被阻塞的线程(condition完成)。
通过查看ReentrantLock的源代码发现,condition其实是等待队列的一个管理者,condition确保阻塞的对象按顺序被唤醒。
在Lock的实现中,LockSupport被用来实现线程状态的改变,后续将更进一步研究LockSupport的实现机制。
[Java]
纯文本查看
复制代码
001 002 003 004 005 006 007 008 009 010 011 012 013 014 015 016 017 018 019 020 021 022 023 024 025 026 027 028 029 030 031 032 033 034 035 036 037 038 039 040 041 042 043 044 045 046 047 048 049 050 051 052 053 054 055 056 057 058 059 060 061 062 063 064 065 066 067 068 069 070 071 072 073 074 075 076 077 078 079 080 081 082 083 084 085 086 087 088 089 090 091 092 093 094 095 096 097 098 099 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 | <font style="color:rgb(77, 77, 77)"><font face="""><font style="font-size:16px"> package com.thread; import java.util.LinkedList; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * 使用Lock来实现生产者和消费者问题 * * * */ public class ProducerConsumer { public static void main(String[] args) { Basket b = new Basket(); Product p = new Product(b); Consumer c = new Consumer(b); Consumer c1 = new Consumer(b); new Thread(p).start(); new Thread(c).start(); new Thread(c1).start(); } } //馒头 class ManTou{ int id; public ManTou(int id) { this.id = id; } @Override public String toString() { return "ManTou"+id; } } //装馒头的篮子 class Basket{ int max = 6; LinkedList<ManTou> manTous = new LinkedList<ManTou>(); Lock lock = new ReentrantLock(); //锁对象 Condition full = lock.newCondition(); //用来监控篮子是否满的Condition实例 Condition empty = lock.newCondition(); //用来监控篮子是否空的Condition实例 //往篮子里面放馒头 public void push(ManTou m){ lock.lock(); try { while(max == manTous.size()){ System.out.println("篮子是满的,待会儿再生产..."); full.await(); } manTous.add(m); empty.signal(); } catch (InterruptedException e) { e.printStackTrace(); }finally{ lock.unlock(); } } //往篮子里面取馒头 public ManTou pop(){ ManTou m = null; lock.lock(); try { while(manTous.size() == 0){ System.out.println("篮子是空的,待会儿再吃..."); empty.await(); } m = manTous.removeFirst(); full.signal(); } catch (InterruptedException e) { e.printStackTrace(); }finally{ lock.unlock(); return m; } } } //生产者 class Product implements Runnable{ Basket basket; public Product(Basket basket) { this.basket = basket; } public void run() { for (int i = 0; i < 40; i++) { ManTou m = new ManTou(i); basket.push(m); System.out.println("生产了"+m); try { Thread.sleep((int)(Math.random()*2000)); } catch (InterruptedException e) { e.printStackTrace();在使用Lock之前,我们都使用Object 的wait和notify实现同步的。举例来说,一个producer和consumer,consumer发现没有东西了,等待,produer生成东西了,唤醒。
有了lock后,世道变了,现在是:
为了突出区别,省略了若干细节。区别有三点:
为什么需要使用condition呢?简单一句话,lock更灵活。以前的方式只能有一个等待队列,在实际应用时可能需要多个,比如读和写。为了这个灵活性,lock将同步互斥控制和等待队列分离开来,互斥保证在某个时刻只有一个线程访问临界区(lock自己完成),等待队列负责保存被阻塞的线程(condition完成)。 通过查看ReentrantLock的源代码发现,condition其实是等待队列的一个管理者,condition确保阻塞的对象按顺序被唤醒。 在Lock的实现中,LockSupport被用来实现线程状态的改变,后续将更进一步研究LockSupport的实现机制。 [Java] 纯文本查看 复制代码
} } } } //消费者 class Consumer implements Runnable{ Basket basket; public Consumer(Basket basket) { this.basket = basket; } public void run() { for (int i = 0; i < 20; i++) { try { Thread.sleep((int)(Math.random()*2000)); } catch (InterruptedException e) { e.printStackTrace(); } ManTou m = basket.pop(); System.out.println("消费了"+m); } }</font></font></font> |