问题
最近遇到了一个小问题,就是不同的线程执行不同的任务,但是这些任务是有依赖关系的。这个问题抽象出来就是:
假设有三个线程,分别打印a, b, c。我们要求最终输出的结果是:
abc,abc,abc,abc打印十次,而后输出end。
也就是最终的输出会是
abc,abc,abc,abc,abc,abc,abc,abc,abc,abc,end
注意的是,三个线程分别打印abc,也就是说主线程要打印逗号和end。
第一种解法: Join
这应该算是最正统的解决方案。Thread的join()方法可以使线程停下来等待另外一个线程执行完毕。
代码是:
private void solution1() throws Exception{
for (int i = 0; i < 10; i ++) {
printABC();
}
System.out.print("end");
}
private void printABC() throws Exception{
Thread a = new Thread(()-> {
System.out.print("a");
});
Thread b = new Thread(() -> {
try {
a.join();
System.out.print("b");
} catch (Exception e) {
}
});
Thread c = new Thread(new Runnable() {
@Override
public void run() {
try {
b.join();
} catch (Exception e) {
e.printStackTrace();
}
System.out.print("c");
}
});
a.start();
b.start();
c.start();
c.join();
System.out.print(",");
}
使用Semphore
我们的需求里面,第一个是要在输出a之后输出b,我们就可以创建一个信号量,使得输出a之后加1,而在输出b之后就减一。其余部分也是如此处理。
private Semaphore aSem = new Semaphore(1);
private Semaphore bSem = new Semaphore(1);
private Semaphore cSem = new Semaphore(1);
private void solution2() throws Exception {
aSem.acquire();
bSem.acquire();
cSem.acquire();
for (int i=0; i < 10; i++) {
this.executor.execute(() -> {
System.out.print("a");
aSem.release();
});
this.executor.execute(()-> {
try {
aSem.acquire();
} catch (Exception e) {
e.printStackTrace();
}
System.out.print("b");
bSem.release();
});
this.executor.execute( () -> {
try {
bSem.acquire();
} catch (Exception e) {
e.printStackTrace();
}
System.out.print("c");
cSem.release();
});
cSem.acquire();
System.out.print(",");
}
System.out.print("end");
}
}
这种做法其实很猥琐的。毕竟Semaphore并不是用于这种场景。
第二种解法:使用notify和wait
private final Object aMonitor = new Object();
private final Object bMonitor = new Object();
private final Object cMonitor = new Object();
private void solution2() throws Exception {
for (int i=0; i < 10; i++) {
this.executor.execute( () -> {
try {
synchronized (bMonitor) {
bMonitor.wait();
System.out.print("c");
}
// bMonitor.wait();
} catch (Exception e) {
e.printStackTrace();
}
synchronized (cMonitor) {
cMonitor.notifyAll();
}
});
this.executor.execute(()-> {
try {
synchronized (aMonitor) {
aMonitor.wait();
System.out.print("b");
}
} catch (Exception e) {
e.printStackTrace();
}
synchronized (bMonitor) {
bMonitor.notifyAll();
}
});
this.executor.execute(() -> {
synchronized (aMonitor) {
System.out.print("a");
aMonitor.notifyAll();
}
});
synchronized (cMonitor) {
cMonitor.wait();
}
System.out.print(",");
}
System.out.print("end");
}
第三种解法:使用CountDownLatch
CountDownLatch可以用于任务间的等待。比如说任务A需要等任务BCD执行完才能往下执行,就可以用CountDownLatch(3)来实现。
private void solution2() throws Exception {
for (int i=0; i < 10; i++) {
CountDownLatch aLatch = new CountDownLatch(1);
CountDownLatch bLatch = new CountDownLatch(1);
CountDownLatch cLatch = new CountDownLatch(1);
this.executor.execute( () -> {
System.out.print("a");
aLatch.countDown();
});
this.executor.execute(()-> {
try {
aLatch.await();
System.out.print("b");
} catch (Exception e) {
e.printStackTrace();
}
bLatch.countDown();
});
this.executor.execute(() -> {
try {
bLatch.await();
System.out.print("c");
} catch (Exception e) {
e.printStackTrace();
}
cLatch.countDown();
});
cLatch.await();
System.out.print(",");
}
System.out.print("end");
}
第四种解法:使用ReentrantLock和Condition
private ReentrantLock lock = new ReentrantLock();
private Condition aEnd = lock.newCondition();
private Condition bEnd = lock.newCondition();
private Condition cEnd = lock.newCondition();
private void solution2() throws Exception {
for (int i=0; i < 10; i++) {
this.executor.execute(() -> {
try {
lock.lock();
bEnd.await();
System.out.print("c");
cEnd.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
});
this.executor.execute(()-> {
try {
lock.lock();
aEnd.await();
System.out.print("b");
bEnd.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
});
this.executor.execute( () -> {
try {
lock.lock();
System.out.print("a");
aEnd.signal();
} finally {
lock.unlock();
}
});
try {
lock.lock();
cEnd.await();
System.out.print(",");
} finally {
lock.unlock();
}
}
System.out.print("end");
}
第五种解法:使用阻塞队列
在这种解法里面,核心思想就在于将问题看做是一个生产者-消费者模型。相当于线程1生产了a之后,线程2取出a,同时生产出来一个b。以此递归。
private ArrayBlockingQueue<Object> aQueue = new ArrayBlockingQueue<>(1);
private ArrayBlockingQueue<Object> bQueue = new ArrayBlockingQueue<>(1);
private ArrayBlockingQueue<Object> cQueue = new ArrayBlockingQueue<>(1);
private void solution2() throws Exception {
Object obj = new Object();
for (int i=0; i < 10; i++) {
this.executor.execute(() -> {
try {
System.out.print("a");
aQueue.put(obj);
} catch (Exception e) {
e.printStackTrace();
}
});
this.executor.execute(()-> {
try {
aQueue.take();
System.out.print("b");
bQueue.put(obj);
} catch (Exception e) {
e.printStackTrace();
}
});
this.executor.execute( () -> {
try {
bQueue.take();
System.out.print("c");
cQueue.put(obj);
} catch (Exception e) {
e.printStackTrace();
}
});
cQueue.take();
System.out.print(",");
}
System.out.print("end");
}
网友评论