好像LC上大部分(就那几道)多线程题目都是考察等待通知机制。
这题要求0奇0偶的顺序打印直至n.
那就得构造屏障嘛,保证它们能够交替允许,信号量Semaphore就能很好的完成这个工作。
设一个变量为公共变量,记录当前应该打印的奇偶数,在0方法里递增。别忘了再最后唤醒它们去结束等待。
代码如下:
Semaphore
class ZeroEvenOdd {
private int n;
Semaphore zero = new Semaphore(1);
Semaphore even = new Semaphore(0);
Semaphore odd = new Semaphore(0);
public ZeroEvenOdd(int n) {
this.n = n;
}
private volatile int count =1;
// printNumber.accept(x) outputs "x", where x is an integer.
public void zero(IntConsumer printNumber) throws InterruptedException {
while(count<=n){
zero.acquire();
if(count>n){
even.release();
odd.release();
break;
}
printNumber.accept(0);
//System.out.println("\nzero:"+count);
if((count&1)==0){
even.release();
}else {
odd.release();
}
}
}
public void even(IntConsumer printNumber) throws InterruptedException {
while(count<=n){
even.acquire();
if(count>n)break;
//System.out.println("\neven:"+count);
printNumber.accept(count);
count++;
zero.release();
}
}
public void odd(IntConsumer printNumber) throws InterruptedException {
while(count<=n){
odd.acquire();
if(count>n)break;
//System.out.println("\nodd:"+count);
printNumber.accept(count);
count++;
zero.release();
}
}
}
ReentrantLock+Condition
那么还有其他方法构建屏障吗?当然可以使用等待队列。可以使用Lock也可以使用默认的synchronized,不过Lock的优势在于可以建立多个等待队列,每次只唤醒对应的,这样可以减少锁竞争。
class ZeroEvenOdd {
private int n;
private volatile boolean flag = true;
private ReentrantLock lock = new ReentrantLock();
private Condition zero = lock.newCondition();
private Condition even = lock.newCondition();
private Condition odd = lock.newCondition();
public ZeroEvenOdd(int n) {
this.n = n;
}
private volatile int count =1;
// printNumber.accept(x) outputs "x", where x is an integer.
public void zero(IntConsumer printNumber) throws InterruptedException {
while(count<=n){
lock.lock();
try {
while(!flag) {
zero.await();
}
flag = false;
if (count > n) {
even.signalAll();
odd.signalAll();
break;
}
printNumber.accept(0);
//System.out.println("\nzero:"+count);
}finally {
if((count & 1) == 0) {
even.signal();
} else {
odd.signal();
}
lock.unlock();
}
}
}
public void even(IntConsumer printNumber) throws InterruptedException {
while(count<=n){
lock.lock();
try{
while(flag||(count&1)==1){
even.await();
}
flag = true;
if(count>n)break;
//System.out.println("\neven:"+count);
printNumber.accept(count);
count++;
}finally {
zero.signal();
lock.unlock();
}
}
}
public void odd(IntConsumer printNumber) throws InterruptedException {
while(count<=n){
lock.lock();
try{
while(flag||(count&1)==0){
odd.await();
}
flag = true;
if(count>n)break;
//System.out.println("\neven:"+count);
printNumber.accept(count);
count++;
}finally {
zero.signal();
lock.unlock();
}
}
}
}
sychronized
还可以使用sychronized的moniter锁,同理,不过竞争明显多于上一种,效率会没那么高
class ZeroEvenOdd {
private int n;
private int count = 1;
private boolean isZeroPrinted = false;
public ZeroEvenOdd(int n) {
this.n = n;
}
// printNumber.accept(x) outputs "x", where x is an integer.
public void zero(IntConsumer printNumber) throws InterruptedException {
while (count < n + 1) {
synchronized (this) {
if (isZeroPrinted) {
this.wait();
} else {
printNumber.accept(0);
isZeroPrinted = true;
this.notifyAll();
}
}
}
}
public void even(IntConsumer printNumber) throws InterruptedException {
while (count < n + 1) {
synchronized (this) {
if (!isZeroPrinted || (count &1) == 1) {
this.wait();
} else {
printNumber.accept(count++);
isZeroPrinted = false;
this.notifyAll();
}
}
}
}
public void odd(IntConsumer printNumber) throws InterruptedException {
while (count < n + 1) {
synchronized (this) {
if (!isZeroPrinted || (count &1) == 0) {
this.wait();
} else {
printNumber.accept(count++);
isZeroPrinted = false;
this.notifyAll();
}
}
}
}
}
无锁,状态量比较(各方法单线程时)
如果每个方法都是单个线程执行的话,那么可以采用状态量的方式。如下所示:
class ZeroEvenOdd {
private int n;
private volatile int count = 1;
private volatile int stage = 0;
public ZeroEvenOdd(int n) {
this.n = n;
}
// printNumber.accept(x) outputs "x", where x is an integer.
public void zero(IntConsumer printNumber) throws InterruptedException {
while (count < n + 1) {
if (stage!=0) {
Thread.yield();
} else {
printNumber.accept(0);
stage = (count&1)==0?1:2;
}
}
}
public void even(IntConsumer printNumber) throws InterruptedException {
while (count < n + 1) {
if (stage!=1) {
Thread.yield();
} else {
printNumber.accept(count++);
stage = 0;
}
}
}
public void odd(IntConsumer printNumber) throws InterruptedException {
while (count < n + 1) {
if (stage!=2) {
Thread.yield();
} else {
printNumber.accept(count++);
stage = 0;
}
}
}
}
无锁,状态量比较(各方法多线程时)
如果是都是多线程的话,可以考虑带版本号的原子量。由于我们只在意有没有有改过,并不在意修改了几次,所以我们可以使用AtomicMarkableReference
class ZeroEvenOdd {
private int n;
private volatile int count = 1;
private AtomicMarkableReference<Integer> stage = new AtomicMarkableReference<>(0,false);
public ZeroEvenOdd(int n) {
this.n = n;
}
// printNumber.accept(x) outputs "x", where x is an integer.
public void zero(IntConsumer printNumber) throws InterruptedException {
while (count < n + 1) {
if (!this.stage.compareAndSet(0,0,false,true)) {
Thread.yield();
} else {
printNumber.accept(0);
if((count&1)==1){
this.stage.set(2,false);
}else {
this.stage.set(1,false);
}
}
}
}
public void even(IntConsumer printNumber) throws InterruptedException {
while (count < n + 1) {
if (!this.stage.compareAndSet(1,1,false,true)) {
Thread.yield();
} else {
printNumber.accept(count++);
this.stage.set(0,false);
}
}
}
public void odd(IntConsumer printNumber) throws InterruptedException {
while (count < n + 1) {
if (!this.stage.compareAndSet(2,2,false,true)) {
Thread.yield();
} else {
printNumber.accept(count++);
this.stage.set(0,false);
}
}
}
}
阻塞队列(任务单线程时)
使用阻塞队列当队列满时,put需要等待一个该队列的take才能继续执行。
以zero方法作为生产者,其他两个方法作为消费者。
不过这么写也有个弊端,没有竞争到那个take的名额的线程就会一直阻塞于take,又判断不了是否已经结束了。也就是说只适用于每个方法的执行线程只有一个的场景。不过这本来也不是生产者/消费者的情景。
private final int n,e,o;
private volatile int count =1;
private BlockingQueue<Integer> evens = new ArrayBlockingQueue<>(1);
private BlockingQueue<Integer> odds = new ArrayBlockingQueue<>(1);
private BlockingQueue<Integer> zeros = new ArrayBlockingQueue<>(1);
public ZeroEvenOdd(int n) {
this.n = n;
e = (n&1)==0?n:n-1;
o = e==n?n-1:n;
}
// printNumber.accept(x) outputs "x", where x is an integer.
public void zero(IntConsumer printNumber) throws InterruptedException {
while(count<n+1){
zeros.put(0);
printNumber.accept(0);
if((count&1)==1){
odds.put(count++);
}else {
evens.put(count++);
}
}
}
public void even(IntConsumer printNumber) throws InterruptedException {
while (count<e+1){
int ans = evens.take();
printNumber.accept(ans);
zeros.take();
}
}
public void odd(IntConsumer printNumber) throws InterruptedException {
while (count<o+1){
int ans = odds.take();
printNumber.accept(ans);
zeros.take();
}
}