题:创建一个固定容量的同步容器,拥有put和get以及getCount方法,支持2个生产者线程和10个消费者线程阻塞调用
使用synchronized
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 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
| public class Question01<T> {
private final int MAX = 10; private LinkedList<T> list = new LinkedList<>(); private int count = 0;
public synchronized void put(T t) { while (list.size() == MAX) { try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } list.add(t); ++count; this.notifyAll(); }
public synchronized T get() { while (0 == list.size()) { try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } T t = list.removeFirst(); --count; this.notifyAll(); return t; }
public synchronized int getCount() { return count; }
public static void main(String[] args) { Question01<String> question01 = new Question01<>();
for (int i = 0; i < 10; i++) { new Thread(() -> { for (int j = 0; j < 5; j++) { System.out.println(question01.get()); } }, "c" + i).start(); }
try { TimeUnit.SECONDS.sleep(1L); } catch (InterruptedException e) { e.printStackTrace(); }
for (int i = 0; i < 2; i++) { new Thread(() -> { for (int j = 0; j < 25; j++) { question01.put(Thread.currentThread().getName() + " " + j); } }, "p" + i).start(); }
}
}
|
这里有两个需要注意的地方,一个是判断的时候是使用的while循环判断,另一个唤醒线程使用的是notifyAll而不是notify。
原因:1. 这里之所以不能使用 if 判断的原因是,如果有多个生产者线程同时进入等待状态,此时消费者线程消费了数据,使用notifyAll唤醒所有的生产者线程,从等待的地方开始继续往下执行。假设此时 list 的size已经等于MAX - 1了,但是仍然有两个或者多个生产者线程处于等待状态,那么当其中的任何一个获得锁执行完之后,此时的list的长度已经达到最大值,那么当其他的线程获得锁,继续往下执行的时候,如果此时的判断是if (list.size() == MAX) 那么线程不会再次判断,而是直接执行list.add(t); 往list中添加数据,那么此时肯定会超过最大值了。而如果使用的是while的话,那么从wait的地方继续执行后面的代码的时候,还会再次进行判断,这样就不会超出最大容量了。所以,当使用wait的使用,99.9%的情况都是与while一同使用的。
原因:2. 如果使用的是notify的话,假如现在是生产者调用了notify,那么它不能保证它唤醒的一定是消费者线程,也有可能是生产者线程,如果唤醒的是生产者线程,那么又会进入到wait状态。
使用ReentrantLock的方式
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 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
| public class Question02<T> {
private final int MAX = 10; private LinkedList<T> list = new LinkedList<>(); private int count = 0;
private Lock lock = new ReentrantLock(); private Condition producer = lock.newCondition(); private Condition consumer = lock.newCondition();
public void put(T t) { try { lock.lock(); while (list.size() == MAX) { producer.await(); } list.add(t); count++; consumer.signalAll(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } }
public synchronized T get() { T t = null; try { lock.lock(); while (list.size() == 0) { consumer.await(); } t = list.removeFirst(); count--; producer.signalAll(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } return t; }
public synchronized int getCount() { return count; }
public static void main(String[] args) { Question02<String> question01 = new Question02<>();
for (int i = 0; i < 10; i++) { new Thread(() -> { for (int j = 0; j < 5; j++) { System.out.println(question01.get()); } }, "c" + i).start(); }
try { TimeUnit.SECONDS.sleep(1L); } catch (InterruptedException e) { e.printStackTrace(); }
for (int i = 0; i < 2; i++) { new Thread(() -> { for (int j = 0; j < 25; j++) { question01.put(Thread.currentThread().getName() + " " + j); } }, "p" + i).start(); }
}
}
|
使用ReentrantLock的方式,可以精确指定哪些线程被唤醒,这样的效率明显是比synchronized效率要高的。