通常所说的concurrent包基本有3个package组成
java.util.concurrent:提供大部分关于并发的接口和类,如BlockingQueue,Callable,ConcurrentHashMap,ExecutorService, Semaphore等
java.util.concurrent.atomic:提供所有原子操作的类, 如AtomicInteger, AtomicLong等;
java.util.concurrent.locks:提供锁相关的类, 如Lock, ReentrantLock, ReadWriteLock, Condition等;
concurrent包的优点:
1. 首先,功能非常丰富,诸如线程池(ThreadPoolExecutor),CountDownLatch等并发编程中需要的类已经有现成的实现,不需要自己去实现一套; 毕竟jdk1.4对多线程编程的主要支持几乎就只有Thread, Runnable,synchronized等
2. concurrent包里面的一些操作是基于硬件级别的CAS(compare and swap),就是在cpu级别提供了原子操作,简单的说就可以提供无阻塞、无锁定的算法; 而现代cpu大部分都是支持这样的算法的;
CountDownLatch通常的使用场景是,某个主线程接到一个任务,起了n个子线程去完成,但是主线程需要等待这n个子线程都完成任务了以后才开始执行某个操作;即防止主线程在前面线程全部结束之前结束
例如:
import java.util.List;
import java.util.concurrent.CountDownLatch;
public class RunnableDemo implements Runnable{
//每个线程都会有一个自己的副本,所以不会出现脏数据
//可以用来控制单例对象
/*private static ThreadLocal<Integer> num = new ThreadLocal<Integer>();
public void run() {
num.set(0);
for (int i = 0; i < 3; i++) {
num.set(num.get()+1);
System.out.println(Thread.currentThread().getName()+"*********"+num.get());
}
}*/
//private static int a = 0;
private List<Object> list;
private CountDownLatch countDownLatch;
public void run() {
for(int i = 0;i<100;i++){
list.add(new Object());
}
countDownLatch.countDown();
}
public RunnableDemo(List<Object> list,CountDownLatch countDownLatch){
this.list = list;
this.countDownLatch = countDownLatch;
}
}
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Vector;
import java.util.concurrent.CountDownLatch;
public class Test {
public static void main(String[] args) throws InterruptedException {
//size = 100;500个线程进入list.add方法,先判断size,这时size=100; 然后执行add方法,在size++之前500个线程都执行add方法, 发生下标越界
List<Object> list = new ArrayList<Object>();//非线程安全,在list达到满了,并扩容之前就往里添加会导致异常
//List<Object> list = new Vector<Object>();//
int threadCount = 10000;
CountDownLatch countDownLatch = new CountDownLatch(threadCount);
//启动1000个线程,每个线程往list放100个元素
for (int i = 0; i <threadCount; i++) {
Thread thread = new Thread(new RunnableDemo(list, countDownLatch));
thread.start();
}
//防止主线程在前面线程全部结束之前结束
try {
countDownLatch.await();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(list.size());//在执行add方法的时候有可能size++被多个线程同步执行,导致数据不一定正确---2个线程同时执行size++,结果可能为1
}
}
**********************************************************************************************************************
或如下:
package concurrent;
import java.util.concurrent.CountDownLatch;
public class CountDown {
public static void main(String[] args) {
int count = 10;
final CountDownLatch l = new CountDownLatch(count);
for (int i = 0; i < count; i++) {
final int index = i;
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.currentThread().sleep(20*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread " + index + " has finished...");
l.countDown();
}
}).start();
}
try {
l.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("all threads have finished.");
}
}
线程安全并且无阻塞的Atomic类
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicLong;
public class AtomicLongDemo {
public static void main(String[] args) {
final int loopCount = 10000;
int threadCount = 10;
final NonSafeSeq seql = new NonSafeSeq();
final SafeSeq safeSeql = new SafeSeq();
final CountDownLatch l = new CountDownLatch(threadCount);
for (int i = 0; i < threadCount; i++) {
final int index = i;
new Thread(new Runnable(){
public void run() {
for (int j = 0; j < loopCount; j++) {
seql.inc();
safeSeql.inc();
}
System.out.println("Finished:"+index);
l.countDown();
}
}).start();
}
try {
l.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("both have finished...");
System.out.println("NonSafeSeq--"+seql.get());
System.out.println("SafeSeql--"+safeSeql.get());
}
}
class NonSafeSeq {
private long count = 0;
public void inc(){
count++;
}
public long get(){
return count;
}
}
class SafeSeq{
private AtomicLong count = new AtomicLong();
public void inc(){
count.incrementAndGet();//当前值+1
}
public Long get(){
return count.longValue();
}
}
运行代码,可以得到类似这样的结果
finished : 1
finished : 0
finished : 3
finished : 2
finished : 5
finished : 4
finished : 6
finished : 8
finished : 9
finished : 7
both have finished....
NonSafeSeq:91723
SafeSeq with atomic: 100000
可以看到,10个线程,每个线程运行了10,000次,理论上应该有100,000次增加,使用了普通的long是非线程安全的,而使用了AtomicLong是线程安全的;
注意,这个例子也说明,虽然long本身的单个设置是原子的,要么成功要么不成功,但是诸如count++这样的操作就不是线程安全的;因为这包括了读取和写入两步操作;(先读取count的值,再对其进行++写操作)
可以代替synchronized关键字的ReentrantLock
synchronized关键字,本质上该关键字是一个对象锁,可以加在不同的instance上或者class上,从使用的角度则分别可以加在非静态方法,静态方法,以及直接synchronized(MyObject)这样的用法;
concurrent包提供了一个可以替代synchronized关键字的ReentrantLock, 简单的说你可以new一个ReentrantLock, 然后通过lock.lock和lock.unlock来获取锁和释放锁;注意必须将unlock放在finally块里面,reentrantlock的好处
1. 是更好的性能,
2. 提供同一个lock对象上不同condition的信号通知
3. 还提供lockInterruptibly这样支持响应中断的加锁过程,意思是说你试图去加锁,但是当前锁被其他线程hold住,然后你这个线程可以被中断;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockDemo {
public static void main(String[] args) {
final int loopcount = 10000;
int threadcount = 10;
final SafeSeqWithLock seq = new SafeSeqWithLock();
final CountDownLatch l = new CountDownLatch(threadcount);
for (int i = 0; i < threadcount; ++i) {
final int index = i;
new Thread(new Runnable() {
public void run() {
for (int j = 0; j < loopcount; j++) {
seq.inc();
}
System.out.println("finished: "+index );
l.countDown();
}
}).start();
}
try {
l.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("both have finished....");
System.out.println("SafeSeqWithLock:" + seq.get());
}
}
class SafeSeqWithLock{
private long count = 0;
private ReentrantLock relock = new ReentrantLock();
public void inc(){
System.out.println(Thread.currentThread().getName());//3333333444433334444说明当前锁被其他线程hold住,线程3和线程4进不去
relock.lock();
try {
count++;
} finally {
relock.unlock();
}
}
public long get(){
return count;
}
}
synchronized和Lock性能对比图
读写锁ReadWriteLock
意思是说读锁可以有很多个锁同时上锁,只要当前没有写锁;
写锁是排他的,上了写锁,其他线程既不能上读锁,也不能上写锁;同样,需要上写锁的前提是既没有读锁,也没有写锁;
两个写锁不能同时获得无需说明,下面一段程序说明下上了读锁以后,其他线程需要上写锁也无法获得
import java.util.Date;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReadWriteLockDemo {
public static void main(String[] args) {
ReentrantReadWriteLock rwlock = new ReentrantReadWriteLock();
final Lock rlock = rwlock.readLock();
final Lock wlock = rwlock.writeLock();
final CountDownLatch l = new CountDownLatch(2);
//start r thread
new Thread(new Runnable(){
public void run() {
System.out.println(new Date() + "now to get rlock");
rlock.lock();
try {
Thread.currentThread().sleep(2*1000);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(new Date()+"now to unlock rlock");
rlock.unlock();
l.countDown();
}
}).start();
//start w thread
new Thread(new Runnable(){
public void run() {
System.out.println(new Date() + "now to get wlock");
wlock.lock();
System.out.println(new Date() + "now to unlock wlock");
wlock.unlock();
l.countDown();
}
}).start();
try {
l.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(new Date() + " finished");
}
}
这代码在我机器上打印的结果是, 也就是试图获得写锁的线程只有当另外一个线程将读锁释放了以后才可以获得
Tue Feb 28 23:18:13 CST 2012now to get rlock
Tue Feb 28 23:18:13 CST 2012now to get wlock
Tue Feb 28 23:18:33 CST 2012now to unlock rlock
Tue Feb 28 23:18:33 CST 2012now to unlock wlock
Tue Feb 28 23:18:33 CST 2012finished
ReadWriteLock的实现是ReentrantReadWriteLock,
有趣的是,在一个线程中,读锁不能直接升级为写锁,但是写锁可以降级为读锁;
这意思是,如果你已经有了读锁,再去试图获得写锁,将会无法获得, 一直堵住了;
但是如果你有了写锁,再去试图获得读锁,没问题;
下面是一段降级的代码,
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class RWLock_downgrade {
public static void main(String[] args) {
ReentrantReadWriteLock rwlock = new ReentrantReadWriteLock();
Lock rlock = rwlock.readLock();
Lock wlock = rwlock.writeLock();
/*//本段可以正常打印出来
System.out.println("now to get wlock");
wlock.lock();
System.out.println("now to get rlock");
rlock.lock();
System.out.println("now to unlock wlock");
wlock.unlock();
System.out.println("now to unlock rlock");
rlock.unlock();
System.out.println("finished");*/
System.out.println("now to get rlock");
rlock.lock();
System.out.println("now to get wlock");
wlock.lock();
System.out.println("now to unlock wlock");
wlock.unlock();
System.out.println("now to unlock rlock");
rlock.unlock();
System.out.println("finished");
}
}
用在一个lock上的多个Condition
concurrent系列的前一篇说到说一说java的concurrent包7–thread和runnable,现在继续,今天介绍下Condtion这个接口,可以用在一个lock上的多个不同的情况;
在jdk的线程同步代码中,无论的synchronized关键字,或者是lock上的await/signal等,都只能在一个锁上做同步通知;
假设有3个线程,要对一个资源做同步,一般只能有一个锁来做同步通知操作,那么通知的时候无法做到精确的通知3个线程中的某一个的;
因为你调用了wait()/notify()的时候,具体的调度是jvm决定的;
但是有的时候的确需要需要对一个锁做多种不同情况的精确通知, 比如一个缓存,满了和空了是两种不同的情况,可以分别通知取数据的线程和放数据的线程;
Condition的基本使用如下:
* Condition是个接口,基本的方法就是await()和signal()方法;
* Condition依赖于Lock接口,生成一个Condition的基本代码是lock.newCondition()
* 调用Condition的await()和signal()方法,都必须在lock保护之内,就是说必须在lock.lock()和lock.unlock之间才可以
* 和Object.wait()方法一样,每次调用Condition的await()方法的时候,当前线程就自动释放了对当前锁的拥有权
当然,Condition其实是个接口,上面说的这几点,在实现Condition的时候可以自由控制一点;但是jdk的javadoc说了,如果有啥特别的实现,必须要清楚的说明的;
下一节我会结合具体的代码来介绍下Condition的使用;
Condition的代码例子BoundedBuffer
javadoc里面对Condition有一个绝佳的例子,BoundedBuffer类,就是一个线程安全的有界限的缓存;非常巧妙的利用了Condition,根据来通知不同的线程做不同的事情;
下面先看下具体代码:
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class BoundedBuffer {
final Lock lock = new ReentrantLock();
final Condition notFull = lock.newCondition();
final Condition notEmpty = lock.newCondition();
final Object[] items = new Object[100];
int putptr,takeptr,count;
public void put(Object x) throws InterruptedException{
lock.lock();
try {
while(count == items.length)notFull.await();
items[putptr] = x;
if(++putptr == items.length)putptr = 0;
++count;
notEmpty.signal();
} finally {
lock.unlock();
}
}
public Object take() throws InterruptedException{
lock.lock();
try {
while (count == 0)
notEmpty.await();
Object x = items[takeptr];
if(++takeptr == items.length)takeptr = 0;
--count;
notFull.signal();
return x;
} finally {
lock.unlock();
}
}
}
代码意思不复杂,一个有界的buffer,里面是个数组,可以往里面放数据和取数据;
由于该buffer被多个线程共享,所以每次放和取操作的时候都用一个lock保护起来;
每次取数据(take)的时候,
a. 如果当前个数是0(用一个count计数), 那么就调用notEmpty.await等待,锁就释放了;
b. 取数据的索引专门有一个,每次向前一步; 如果到头了就从0开始循环使用
c.如果有数据,那就取一个数据,将count减1,同时调用notfull.signal(),
每次放数据(put)的时候
a.如果count和length相等,也就是满了,那就调用notFull.await等待,释放了锁; 等待有一些take()调用完成之后才会进入
b. 放数据也有一个索引putptr, 放入数据; 如果到头了也从0开始循环使用
c. 调用notempty.signal(); 如果有线程在take()的时候await住了,那么就会被通知到,可以继续进行操作
Condition和BoundedBuffer的测试代码
前面一篇说了Condition和BoundedBuffer的基本代码,下面写一个简单的程序测试下这个BoundedBuffer;
这段程序的目的是测试先put()后take()的操作,
1. 我将BoundedBuffer的大小设置成5,同时在每次进入notFull和notEmpty的await()的时候打印一下表示当前线程正在等待;
2. 先开启10个线程做put()操作,预计有5个线程可以完成,另外5个会进入等待
3. 主线程sleep10秒中,然后启动10个线程做take()操作;
这个时候,首先第一个take()必然成功完成,在这之前等待的5个put()线程都不会被唤醒, 接下来的事情就不好说了;
剩下的5个put()线程和9个take()线程中的任何一个都可能会被jvm调度;
比如可能出现
a. 开始take()的时候,有5个连续的take()线程完成操作; 然后又进入put()和take()交替的情况
b. 第一个take()之后,立刻会有一个put()线程被notFull().signal()唤醒; 然后继续有take()和put()交替的情况;
其中take()线程也可能进入notEmpty.await()操作;
但是任何时候,未完成的take()线程始终>=未完成的put()线程, 这个也是很自然的;
import java.util.Date;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class BoundedBufferTest {
public void testPutTake(){
final BoundedBufferDemo bb = new BoundedBufferDemo();
int count = 10;
final CountDownLatch c = new CountDownLatch(count*2);
System.out.println(new Date() + " now try to call put for " + count );
for (int i = 0; i < count; ++i) {
final int index = i;
try {
Thread t = new Thread(new Runnable(){
public void run() {
try{
bb.put(index);
System.out.println(new Date() + " put finished: " + index);
}catch (Exception e) {
e.printStackTrace();
}
c.countDown();
}
});
t.start();
} catch (Exception e) {
e.printStackTrace();
}
}
try{
System.out.println(new Date() + " main thread is going to sleep for 10 seconds");
Thread.sleep(10*1000);
}catch (Exception e) {
e.printStackTrace();
}
System.out.println(new Date() + " now try to take for count: " + count);
for (int i = 0; i < count; ++i) {
Thread t = new Thread(new Runnable(){
public void run() {
try {
Object o = bb.take();
System.out.println(new Date() + " take get: " + o);
} catch (Exception e) {
e.printStackTrace();
}
c.countDown();
}
});
t.start();
}
try {
System.out.println(new Date() + ": main thread is to wait for all threads");
c.await();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(new Date() + " all threads finished");
}
}
class BoundedBuffer {
final Lock lock = new ReentrantLock();
final Condition notFull = lock.newCondition();
final Condition notEmpty = lock.newCondition();
final Object[] items = new Object[5];
int putptr, takeptr, count;
public void put(Object x) throws InterruptedException {
lock.lock();
try {
while (count == items.length){
System.out.println(new Date() + " put is to wait....");
notFull.await();
}
items[putptr] = x;
if (++putptr == items.length) putptr = 0;
++count;
notEmpty.signal();
}finally {
lock.unlock();
}
}
public Object take() throws InterruptedException {
lock.lock();
try {
while (count == 0){
System.out.println(new Date() + " take is going to wait..");
notEmpty.await();
}
Object x = items[takeptr];
if (++takeptr == items.length) takeptr = 0;
--count;
notFull.signal();
return x;
} finally {
lock.unlock();
}
}
}
在几次不同的执行中,始终可以观察到任何时候,未完成的take()线程数>= 未完成的put()线程; 在未完成的线程数相等的情况下,即使jvm首先调度到了take()线程,也会进入notEmpty.await()释放锁,进入等待
Condition和BoundedBuffer的测试代码2
前面一篇说了一个Condition和BoundedBuffer的测试代码例子,前面测试的是先put()再take()的操作,这篇说一下先take()再put()的操作;
当然,必须先要说明的是,这篇和前面这篇在打印日志的时候其实是有错误的,这个错误在前面一篇并不明显,不会导致明显的问题;
但是同样的原因导致现在这个先take()再put()的操作会出现明显的错误,看上去会显得不可思议;
具体情况留到下一篇详细说明,这里先上测试目的,测试代码和运行结果;
同时说明多线程编程需要非常谨慎,否则极易出错
测试目的:
1. 我将BoundedBuffer的大小设置成5,同时在每次进入notFull和notEmpty的await()的时候打印一下表示当前线程正在等待;
2. 先开启10个线程做take()操作,由于开始BoundedBuffer里面没有东西,所以10个线程全部调用await进入等待
3. 主线程sleep10秒中,然后启动10个线程做put()操作;
在第一个put()完成之后,接下来应该会有部分put()线程和take()线程先后完成;
理论上,
a. 任何一个元素的put()都会发生在take()之前;
b. 如果X表示某个操作成功的次数,在X(put)-X(take)<5的时候,put线程不会进入等待状态
import java.util.Date;
import java.util.concurrent.CountDownLatch;
public class BoundedBufferTest2 {
public static void main(String[] args) {
final BoundedBuffer bb = new BoundedBuffer();
int count = 10;
final CountDownLatch c = new CountDownLatch(count*2);//20个线程计数
System.out.println(new Date() + "first try to call take for count:" + count);
//10个线程调用take
for (int i = 0; i < count; i++) {
final int index = i;
Thread t = new Thread(new Runnable(){
public void run() {
try {
Thread.currentThread().setName("take"+index);
Object o = bb.take();
System.out.println(new Date() + " " + " take get: " + o );
} catch (Exception e) {
e.printStackTrace();
}
c.countDown();
}
});
t.start();
}
try {
System.out.println(new Date() + "main thread is going to sleep for 10 seconds");
Thread.sleep(10*1000);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(new Date() + " now try to call putb for " + count);
//10个线程调用put
for (int i = 0; i < count; i++) {
final int index = i;
try {
Thread t = new Thread(new Runnable(){
public void run() {
Thread.currentThread().setName("put"+index);
try {
bb.put(index);
System.out.println(new Date() + " " +" put finished: "+index);
} catch (Exception e) {
e.printStackTrace();
}
c.countDown();
}
});
t.start();
} catch (Exception e) {
e.printStackTrace();
}
}
try {
System.out.println(new Date() + ":main thread is to wait for all threads");
c.await();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(new Date() + " all threads finished");
}
}
class BoundedBuffer {
final Lock lock = new ReentrantLock();
final Condition notFull = lock.newCondition();
final Condition notEmpty = lock.newCondition();
final Object[] items = new Object[5];
int putptr, takeptr, count;
public void put(Object x) throws InterruptedException {
lock.lock();
try {
while (count == items.length){//如果count和items长度相等,即items放满了数据就将本线程进入等待,执行别的线程
// System.out.println(new Date() + " put is to wait....");
System.out.println(new Date() + " " + Thread.currentThread().getName() + " put is to wait....: " + System.currentTimeMillis());
notFull.await();
}
items[putptr] = x;//items[0] = 放入的对象,依次放入items[1],items[2]等等
if (++putptr == items.length) putptr = 0;//如果放满了,就让从items[0]开始放
++count;//用count计数用来判断循环
notEmpty.signal();//唤醒一个等待的线程
}finally {
lock.unlock();
}
}
public Object take() throws InterruptedException {
lock.lock();
try {
while (count == 0){ //如果count为0,即items没有数据了,不能从items里面取数据了,就将本线程进入等待,执行别的线程
// System.out.println(new Date() + " take is going to wait..");
System.out.println(new Date() + " " + Thread.currentThread().getName() + " take is going to wait.. " + System.currentTimeMillis());
notEmpty.await();
}
Object x = items[takeptr]; //取出items中的对象,
if (++takeptr == items.length) takeptr = 0; //如果取到items的最后一个元素了就从0开始取
--count; //用count计数用来判断循环
notFull.signal(); //唤醒另一个等待线程
return x; //返回取到的数据
} finally {
lock.unlock();
}
}
}
结果如下:
Wed Apr 04 10:07:07 CST 2018first try to call take for count:10
Wed Apr 04 10:07:07 CST 2018main thread is going to sleep for 10 seconds//主线程等到调度先执行了
Wed Apr 04 10:07:07 CST 2018 take3 take is going to wait.. 1522807627472//执行了take线程
Wed Apr 04 10:07:07 CST 2018 take2 take is going to wait.. 1522807627472
Wed Apr 04 10:07:07 CST 2018 take5 take is going to wait.. 1522807627473
Wed Apr 04 10:07:07 CST 2018 take0 take is going to wait.. 1522807627473
Wed Apr 04 10:07:07 CST 2018 take4 take is going to wait.. 1522807627473
Wed Apr 04 10:07:07 CST 2018 take6 take is going to wait.. 1522807627473
Wed Apr 04 10:07:07 CST 2018 take1 take is going to wait.. 1522807627473
Wed Apr 04 10:07:07 CST 2018 take8 take is going to wait.. 1522807627473
Wed Apr 04 10:07:07 CST 2018 take7 take is going to wait.. 1522807627473
Wed Apr 04 10:07:07 CST 2018 take9 take is going to wait.. 1522807627473
Wed Apr 04 10:07:17 CST 2018now try to call putb for 10 //准备启动put线程
Wed Apr 04 10:07:17 CST 2018:main thread is to wait for all threads//主线程等到调度先执行了
Wed Apr 04 10:07:17 CST 2018 put finished: 2
Wed Apr 04 10:07:17 CST 2018 take get: 2
Wed Apr 04 10:07:17 CST 2018 put finished: 9
Wed Apr 04 10:07:17 CST 2018 put5 put is to wait....: 1522807637474
Wed Apr 04 10:07:17 CST 2018 put finished: 3
Wed Apr 04 10:07:17 CST 2018 put finished: 0
Wed Apr 04 10:07:17 CST 2018 take get: 9
Wed Apr 04 10:07:17 CST 2018 take get: 8
Wed Apr 04 10:07:17 CST 2018 take get: 1
Wed Apr 04 10:07:17 CST 2018 put finished: 7
Wed Apr 04 10:07:17 CST 2018 take get: 4
Wed Apr 04 10:07:17 CST 2018 take get: 5
Wed Apr 04 10:07:17 CST 2018 put finished: 6
Wed Apr 04 10:07:17 CST 2018 take get: 0
Wed Apr 04 10:07:17 CST 2018 put finished: 8
Wed Apr 04 10:07:17 CST 2018 put finished: 4
Wed Apr 04 10:07:17 CST 2018 take get: 3
Wed Apr 04 10:07:17 CST 2018 put finished: 1
Wed Apr 04 10:07:17 CST 2018 take get: 7
Wed Apr 04 10:07:17 CST 2018 put finished: 5
Wed Apr 04 10:07:17 CST 2018 take get: 6
Wed Apr 04 10:07:17 CST 2018 all threads finished
注意到红色部分:
第一个加为红色是因为按照打印结果,put()只完成了2次,就开始有put()进入等待了,而BoundedBuffer的大小是5,理论上应该没有满的!
蓝色是因为元素8竟然先被take,然后再被put! 显然程序有地方出错了!