• 2021-03-05 15:56:57

例题：Leetcode1116. Print Zero Even Odd

# 无锁并发

class ZeroEvenOdd {
private int n;
// volatile变量用来控制各个方法的输出顺序
private volatile int state;
public ZeroEvenOdd(int n) {
this.state = 0;
this.n = n;
}

/**
* state作用：当state = 0，表示轮到zero()方法使用cpu；
*           当state = 1，表示轮到odd()方法使用cpu；
*           当state = 2，表示轮到odd()方法使用cpu；
* 也有可能不是它。
*/
// printNumber.accept(x) outputs "x", where x is an integer.
public void zero(IntConsumer printNumber) throws InterruptedException {
for(int i = 1; i <= n; i++){
while (state != 0){
}
printNumber.accept(0);
if(i % 2 == 1){
state = 1;
} else {
state = 2;
}
}
}

// 输出偶数
public void even(IntConsumer printNumber) throws InterruptedException {
for(int i = 2; i <= n; i += 2){
while (state != 2){
}
printNumber.accept(i);
state = 0;
}
}

// 输出奇数
public void odd(IntConsumer printNumber) throws InterruptedException {
for(int i = 1; i <= n; i += 2){
while (state != 1){
}
printNumber.accept(i);
state = 0;
}
}
}


# 信号量

首先，我们应该知道，信号量Semaphore的release()操作可以在acquire()操作的前面，这在Semaphore源码中有提到：“There is no requirement that a thread that releases a permit must have acquired that permit by calling”

    /**
* Releases a permit, returning it to the semaphore.
*
* <p>Releases a permit, increasing the number of available permits by
* one.  If any threads are trying to acquire a permit, then one is
* selected and given the permit that was just released.  That thread
* is (re)enabled for thread scheduling purposes.
*
* <p>There is no requirement that a thread that releases a permit must
* have acquired that permit by calling {@link #acquire}.
* Correct usage of a semaphore is established by programming convention
* in the application.
*/
public void release() {
sync.releaseShared(1);
}

class ZeroEvenOdd {
private int n;

Semaphore evenSem = new Semaphore(0);
Semaphore oddSem = new Semaphore(0);
Semaphore zeroSem = new Semaphore(1);
public ZeroEvenOdd(int n) {
this.n = n;
}

/**
* zero函数acquire zeroSem之后才能进行 输出0 和 release evenSem、release oddSem，
* 且release zeroSem的操作应该交给其它两个函数来进行，也就是输出0后得先输出奇数或偶数之后，
* 才能再输出0。同样的，输出奇数或偶数后，也得先输出0才能继续输出奇数或偶数。
*/
// printNumber.accept(x) outputs "x", where x is an integer.
public void zero(IntConsumer printNumber) throws InterruptedException {
for(int i = 1; i <= n; i++){
zeroSem.acquire();
printNumber.accept(0);
if(i % 2 == 1){
oddSem.release();
} else {
evenSem.release();
}
}
}

// 输出偶数
public void even(IntConsumer printNumber) throws InterruptedException {
for(int i = 2; i <= n; i += 2){
evenSem.acquire();
printNumber.accept(i);
// evenSem.release(); 不能release()，只能由zero()函数来release
zeroSem.release();
}
}

// 输出奇数
public void odd(IntConsumer printNumber) throws InterruptedException {
for(int i = 1; i <= n; i += 2){
oddSem.acquire();
printNumber.accept(i);
zeroSem.release();
}
}
}


# 可重入锁(ReentrantLock)和条件变量（Condition）

首先，可重入锁的实例对象 lock 通过lock.newCondition()创建一个 lock 的条件变量 condition 。

使用 condition.await() 可以使线程在该锁的condition条件上等待。使用 condition.signal() 可以唤醒一个正在该条件上等待的线程，使用await()和signal()都应该在lock() 和 unlock() 之间 (signal()在很多情况下都是如此)。

condition.await()会释放锁若没获得锁，则会抛出 IllegalMonitorStateException 异常，可以看Condition.java的源码。然后await()之后，线程会等待被唤醒，被唤醒之后回去重新获得锁，因此执行完工作后需要再释放锁。

condition.signal()会唤醒在该条件上等待的一个线程，规定也需包裹在lock()和unlock()之间。可以查看ReentrantLock的源码，发现ReentrantLock创建condition返回的是一个由final修饰的ConditionObject类的实例对象。通过查看源码，我们发现ConditionObject类的signal()方法如下：

/**
* Moves the longest-waiting thread, if one exists, from the
* wait queue for this condition to the wait queue for the
* owning lock.
*
* @throws IllegalMonitorStateException if {@link #isHeldExclusively}
*         returns {@code false}
*/
public final void signal() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
doSignal(first);
}


如果不持有锁，它也会抛出IllegalMonitorStateException异常。而且，调用signal()后需释放锁，以便唤醒的线程能去获取锁。

## 例题三个条件变量的解法

class ZeroEvenOdd {
private int n;

private volatile int state;
private ReentrantLock rLock = new ReentrantLock();
private Condition zeroCond;
private Condition oddCond;
private Condition evenCond;
public ZeroEvenOdd(int n) {
this.n = n;
this.state = 0;
this.zeroCond = rLock.newCondition();
this.oddCond = rLock.newCondition();
this.evenCond = rLock.newCondition();
}

/**
* zero函数acquire zeroSem之后才能进行 输出0 和 release evenSem、release oddSem，
* 且release zeroSem的操作应该交给其它两个函数来进行，也就是输出0后得先输出奇数或偶数之后，
* 才能再输出0。同样的，输出奇数或偶数后，也得先输出0才能继续输出奇数或偶数。
*/
// printNumber.accept(x) outputs "x", where x is an integer.
public void zero(IntConsumer printNumber) throws InterruptedException {
for(int i = 1; i <= n; i++){
rLock.lock();
try {
// await()需要包裹在循环里，因为线程可能不经过signal就被
// 唤醒，且唤醒后可能不符合条件得继续await()
while (state != 0){
zeroCond.await();
}
printNumber.accept(0);
if (i % 2 == 1){
state = 1;
oddCond.signal();
} else {
state = 2;
evenCond.signal();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
rLock.unlock();
}
}
}

// 输出偶数
public void even(IntConsumer printNumber) throws InterruptedException {
for(int i = 2; i <= n; i += 2){
rLock.lock();
try {
while (state != 2){
evenCond.await();
}
printNumber.accept(i);
state = 0;
zeroCond.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
rLock.unlock();
}
}
}

// 输出奇数
public void odd(IntConsumer printNumber) throws InterruptedException {
for(int i = 1; i <= n; i += 2){
rLock.lock();
try {
while (state != 1){
oddCond.await();
}
printNumber.accept(i);
state = 0;
zeroCond.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
rLock.unlock();
}
}
}
}


## 例题一个条件变量的解法(使用signalAll())

class ZeroEvenOdd {
private int n;

private volatile int state;
private ReentrantLock rLock = new ReentrantLock();
private Condition cond;
public ZeroEvenOdd(int n) {
this.n = n;
this.state = 0;
this.cond = rLock.newCondition();
}

/**
* zero函数acquire zeroSem之后才能进行 输出0 和 release evenSem、release oddSem，
* 且release zeroSem的操作应该交给其它两个函数来进行，也就是输出0后得先输出奇数或偶数之后，
* 才能再输出0。同样的，输出奇数或偶数后，也得先输出0才能继续输出奇数或偶数。
*/
// printNumber.accept(x) outputs "x", where x is an integer.
public void zero(IntConsumer printNumber) throws InterruptedException {
for(int i = 1; i <= n; i++){
rLock.lock();
try {
// await()需要包裹在循环里，因为线程可能不经过signal就被
// 唤醒，且唤醒后可能不符合条件得继续await()
while (state != 0){
cond.await();
}
printNumber.accept(0);
if (i % 2 == 1){
state = 1;
} else {
state = 2;
}
// 只使用一个条件变量必须signalAll()
// 三个线程都在该条件上等待
// 不确定唤醒的是那个线程
cond.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
rLock.unlock();
}
}
}

// 输出偶数
public void even(IntConsumer printNumber) throws InterruptedException {
for(int i = 2; i <= n; i += 2){
rLock.lock();
try {
while (state != 2){
cond.await();
}
printNumber.accept(i);
state = 0;
cond.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
rLock.unlock();
}
}
}

// 输出奇数
public void odd(IntConsumer printNumber) throws InterruptedException {
for(int i = 1; i <= n; i += 2){
rLock.lock();
try {
while (state != 1){
cond.await();
}
printNumber.accept(i);
state = 0;
cond.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
rLock.unlock();
}
}
}
}


# 线程创建

public static void main(String[] args) {
PrintZeroEvenOdd4 p = new PrintZeroEvenOdd4();
ZeroEvenOdd z = p.new ZeroEvenOdd(4);

@Override
public void run() {
try {
z.zero(new IntConsumer() {
@Override
public void accept(int value) {
System.out.println(value);
}
});
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public void run() {
try {
z.even(new IntConsumer() {
@Override
public void accept(int value) {
System.out.println(value);
}
});
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public void run() {
try {
z.odd(new IntConsumer() {
@Override
public void accept(int value) {
System.out.println(value);
}
});
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

更多相关内容
• 1.按序打印

## 1.按序打印

#### 解法一：使用volatile

public class FooWithVolatile {
private volatile int count;

public FooWithVolatile() {

}

public void first(Runnable printFirst) throws InterruptedException {
// printFirst.run() outputs "first". Do not change or remove this line.
printFirst.run();
count++;

}

public void second(Runnable printSecond) throws InterruptedException {
// printSecond.run() outputs "second". Do not change or remove this line.
while(count != 1) { }
printSecond.run();
count++;
}

public void third(Runnable printThird) throws InterruptedException {
while(count != 2) { }
// printThird.run() outputs "third". Do not change or remove this line.
printThird.run();
}
}



类似的，我们也可以使用AtomicInteger，不过其实AtomicInteger底层也是使用了volatile字段，只不过在计算时会使用CAS解决原子性问题，但是这里的while循环对自增操作进行了阻塞，所以不会出现三个线程同时对count自增的情况，所以没必要使用AtomicInteger，更何况CAS操作里面的while循环也是很耗费资源的

#### 解法二：使用CountDownLatch

public class FooWithCountDownLatch {
private CountDownLatch second = new CountDownLatch(1);
private CountDownLatch third = new CountDownLatch(1);

public FooWithCountDownLatch() {
}

public void first(Runnable printFirst) throws InterruptedException {
// printFirst.run() outputs "first". Do not change or remove this line.
printFirst.run();
second.countDown();
}

public void second(Runnable printSecond) throws InterruptedException {
second.await();
// printSecond.run() outputs "second". Do not change or remove this line.
printSecond.run();
third.countDown();
}

public void third(Runnable printThird) throws InterruptedException {
third.await();
// printThird.run() outputs "third". Do not change or remove this line.
printThird.run();
}
}



类似的，这里我们使用两个“门栓”栓住（阻塞）second方法和third方法执行run方法打印结果，当first方法执行完毕后，释放second的门栓让second方法打印结果，second方法执行完毕后，释放third的门栓让third方法打印结果

## 2.交替打印FooBar

class FooBarWithCountDownLatch {
private int n;
private CountDownLatch fooLatch = new CountDownLatch(0);
private CountDownLatch barLatch = new CountDownLatch(1);

public FooBarWithCountDownLatch(int n) {
this.n = n;
}

public void foo(Runnable printFoo) throws InterruptedException {

for (int i = 0; i < n; i++) {
fooLatch.await();
// printFoo.run() outputs "foo". Do not change or remove this line.
printFoo.run();
fooLatch = new CountDownLatch(1);
barLatch.countDown();
}
}

public void bar(Runnable printBar) throws InterruptedException {

for (int i = 0; i < n; i++) {
barLatch.await();
// printBar.run() outputs "bar". Do not change or remove this line.
printBar.run();
barLatch = new CountDownLatch(1);
fooLatch.countDown();
}
}
}


这里要注意，CountDownLatch和CyclicBarrier不一样，CountDownLatch是一次性的，countDown到0之后不会自己恢复成1，所以要每次new一个CountDownLatch对象。

## 3.打印零与奇偶数

public class ZeroEvenOdd {
private int n;
private CountDownLatch zero = new CountDownLatch(0);
private CountDownLatch even = new CountDownLatch(1);
private CountDownLatch odd = new CountDownLatch(1);

public ZeroEvenOdd(int n) {
this.n = n;
}

// printNumber.accept(x) outputs "x", where x is an integer.
public void zero(IntConsumer printNumber) throws InterruptedException {
for (int i = 0; i < n; i++) {
zero.await();
printNumber.accept(0);
zero = new CountDownLatch(1);
if(i % 2 == 0) {
odd.countDown();
} else {
even.countDown();
}
}

}

public void even(IntConsumer printNumber) throws InterruptedException {
for (int i = 2; i < n; i+=2) {
even.await();
printNumber.accept(i);
even = new CountDownLatch(1);
zero.countDown();
}

}

public void odd(IntConsumer printNumber) throws InterruptedException {
for (int i = 1; i < n; i+=2) {
odd.await();
printNumber.accept(i);
odd = new CountDownLatch(1);
zero.countDown();
}
}
}


这道题没什么好说的，做法也同样很多样，只要仔细点，都可以做对，但是我感觉都没直接用CoutDownLatch好。

## 4.H2O

public class H2O {
private Semaphore hSemaphore = new Semaphore(2);
private Semaphore oSemaphore = new Semaphore(0);

public H2O() {

}

public void hydrogen(Runnable releaseHydrogen) throws InterruptedException {
hSemaphore.acquire();
// releaseHydrogen.run() outputs "H". Do not change or remove this line.
releaseHydrogen.run();
oSemaphore.release();
}

public void oxygen(Runnable releaseOxygen) throws InterruptedException {
oSemaphore.acquire(2);
// releaseOxygen.run() outputs "H". Do not change or remove this line.
releaseOxygen.run();
hSemaphore.release(2);
}
}


实在想不到Semaphore以外的做法，虽然看题解确实有，但是反而不怎么好

展开全文
• 并发】目录下为学习并发的总结 (深入到jvm指令) 【数据库】目录下为使用数据库的一些总结 【趣唠Linux操作系统】目录下为学习Linux内核的一些总结 【问题】目录下为阅读中遇到的一些问题搜集 - 总结了OSI分层的...
• leetcode 分类 concurrency Leetcode concurrency 分类题解
• 异步下载题目描述，高速并发导出文件 支持增量更新，当 LeetCode-cn 有新内容（题目/提交的代码）时，可以选择增量形式更新 使用 使用 git clone 或直接下载本仓库代码至本地 本项目需要用到第三方库 requests 和 ...
• Concurrency-Practice:解决了LeetCode上所有并发问题的列表
• leetcode赛车并发 好读/看 竞争条件 当多个线程执行临界区的结果可能因线程执行的顺序而异时，则称临界区包含竞争条件。 术语竞态条件源于线程正在通过临界区竞争的比喻，并且该竞争的结果影响执行临界区的结果。 ...
• LeetCode上专门的多线程题目版块，本文通过解决几道leetCode上的多线程协作交替打印的编程题，来加深对并发编程的理解，并训练一下并发编程思想。 1、交替打印FooBar 示例 输入: n = 2 输出: “foobarfoobar” 解释...

LeetCode上专门的多线程题目版块，本文通过解决几道leetCode上的多线程协作交替打印的编程题，来加深对并发编程的理解，并训练一下并发编程思想。

## 一、交替打印

交替打印FooBar

示例
输入: n = 2
输出: “foobarfoobar”
解释: “foobar” 将被输出两次。

### 方法1：synchronized锁

背景知识
1、wait()使当前线程阻塞，前提是必须先获得锁，一般配synchronized 关键字使用，即只能在synchronized同步代码块里使用wait()、notify/notifyAll()方法。

2、 由于wait()、notify/notifyAll()在synchronized 代码块执行，说明当前线程一定是获取了锁的。当线程执行wait()方法时候，会释放当前的锁，然后让出CPU，进入等待状态。只有当notify/notifyAll()被执行时候，才会唤醒一个或多个正处于等待状态的线程，然后继续往下执行，直到执行完synchronized代码块的代码或是中途遇到wait() ，才释放锁

3、notify/notifyAll()的执行只是唤醒沉睡的线程，而不会立即释放锁，锁的释放要看代码块的具体执行情况。所以在编程中，尽量在使用了notify/notifyAll()后立即退出临界区，以唤醒其他线程让其获得锁

4、notify和wait的顺序不能错，如果A线程先执行notify方法，B线程再执行wait方法，那么B线程是无法被唤醒的。

5、notify方法只唤醒一个等待（对象的）线程并使该线程开始执行。所以如果有多个线程等待一个对象，这个方法只会唤醒其中一个线程，选择哪个线程取决于操作系统对多线程管理的实现。notifyAll会唤醒所有等待(对象的)线程，尽管哪一个线程将会第一个处理取决于操作系统的实现。如果当前情况下有多个线程需要被唤醒，推荐使用notifyAll方法。比如在生产者——消费者场景下使用，每次都需要唤醒所有的消费者或是生产者，以判断程序是否可以继续往下执行。

public class FooBarSync {

private  int n;
private  volatile boolean fooExec = true;

public FooBarSync(int n) {
this.n = n;
}

public void foo() throws InterruptedException {
int i = 0;
synchronized (this) {
while (i < n) {
if (fooExec) {
System.out.print("foo");
i++;
fooExec = false;
this.notifyAll();
} else {
//foo线程等待
this.wait();
}
}
}
}

public void bar() throws InterruptedException {
int i = 0;
synchronized (this) {
while (i < n) {
if (!fooExec) {
System.out.print("bar");
i++;
fooExec = true;
this.notifyAll();
} else {
//bar线程等待
this.wait();
}
}
}
}

public static void main(String[] args) throws InterruptedException {
FooBarSync fooBar = new FooBarSync(5);
try {
fooBar.foo();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();

try {
fooBar.bar();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}


### 方法2：ReentrantLock

背景知识
ReentrantLock主要利用CAS+AQS队列来实现。它支持公平锁和非公平锁，两者的实现类似。CAS是一个原子操作，被广泛的应用在Java的底层实现中。在Java中，CAS主要是由sun.misc.Unsafe这个类通过JNI调用CPU底层指令实现。

ReentrantLock和synchronized都是独占锁，只允许线程互斥的访问临界区。但是实现上两者不同：synchronized加锁解锁的过程是隐式的,用户不用手动操作，优点是操作简单，但显得不够灵活。一般并发场景使用synchronized的就够了；ReentrantLock需要手动加锁和解锁，且解锁的操作尽量要放在finally代码块中，保证线程正确释放锁

public class FooBarLock {
private  int n;
private  ReentrantLock lock = new ReentrantLock();
private  volatile boolean fooExec = true;

public FooBarLock(int n) {
this.n = n;
}
public void foo() throws InterruptedException {
for (int i = 0; i < n; ) {
lock.lock();
try {
if (fooExec) {
System.out.print("foo");
fooExec = false;
i++;
}
} finally {
lock.unlock();
}
}
}
public void bar() throws InterruptedException {
for (int i = 0; i < n; ) {
lock.lock();
try {
if (!fooExec) {
System.out.print("bar");
fooExec = true;
i++;
}
} finally {
lock.unlock();
}
}
}

public static void main(String[] args) throws InterruptedException {
FooBarLock fooBar = new FooBarLock(5);
try {
fooBar.foo();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();

try {
fooBar.bar();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}


## 二、打印零与奇偶数

打印零与奇偶数

示例
输入：n = 2
输出：“0102”
说明：三条线程异步执行，其中一个调用 zero()，另一个线程调用 even()，最后一个线程调用odd()。正确的输出为 “0102”。

输入：n = 5
输出：“0102030405”

### 方法1：ReentrantLock + Condition

Condition强大的地方在于它能够精确的控制多线程的休眠与唤醒（注意是唤醒，唤醒只意味着进入了同步队列，不意味着一定能获得资源），相比使用Object的wait()/notify()，使用Condition的await()/signal()这种方式能够更加安全和高效地实现线程间协作

public class ZeroEvenOdd {
/**
* 最后一个打印的数字
*/
private int n;
/**
* 控制打印的标志变量
* flag = 1表示要打印奇数了，flag = 2表示要打印偶数了，flag = 0表示打印0
*/
private int flag = 0;

private Lock lock = new ReentrantLock();
private Condition zeroCondition = lock.newCondition();
private Condition evenCondition = lock.newCondition();
private Condition oddCondition = lock.newCondition();

public ZeroEvenOdd(int n) {
this.n = n;
}

// 仅打印出 0
public void zero(IntConsumer printNumber) throws InterruptedException {
lock.lock();
try {
for (int i = 1; i <= n; i++) {
if (flag != 0) {
// zero线程等待
zeroCondition.await();
}
printNumber.accept(0);
if (i % 2 == 1) {
flag = 1;
// 指定唤醒odd线程
oddCondition.signal();
} else {
flag = 2;
// 指定唤醒even线程
evenCondition.signal();
}
}
} finally {
lock.unlock();
}
}

// 仅打印出 偶数
public void even(IntConsumer printNumber) throws InterruptedException {
lock.lock();
try {
for (int i = 2; i <= n; i += 2) {
if (flag != 2) {
// even线程等待
evenCondition.await();
}
printNumber.accept(i);
flag = 0;
// 唤醒zero线程
zeroCondition.signal();
}
} finally {
lock.unlock();
}
}

// 仅打印出 奇数
public void odd(IntConsumer printNumber) throws InterruptedException {
lock.lock();
try {
for (int i = 1; i <= n; i += 2) {
if (flag != 1) {
// odd线程等待
oddCondition.await();
}
printNumber.accept(i);
flag = 0;
// 唤醒zero线程
zeroCondition.signal();
}
} finally {
lock.unlock();
}
}

public static void main(String[] args) {
ZeroEvenOdd zeroEvenOdd = new ZeroEvenOdd(5);
try {
zeroEvenOdd.odd(e -> System.out.print(e));
} catch (InterruptedException e) {
}
}).start();
try {
zeroEvenOdd.zero(e -> System.out.print(e));
} catch (InterruptedException e) {
}
}).start();
try {
zeroEvenOdd.even(e -> System.out.print(e));
} catch (InterruptedException e) {
}
}).start();
}
}


### 方法2：CountDownLatch

背景知识
CountDownLatch这个类使一个线程等待其他线程各自执行完毕后再执行。是通过一个计数器来实现的，计数器的初始值是线程的数量。每当一个线程执行完毕后，计数器的值就-1，当计数器的值为0时，表示所有线程都执行完毕，然后在锁上等待的线程就可以恢复工作了

//调用await()方法的线程会被挂起，它会等待直到count值为0才继续执行
public void await() throws InterruptedException;
//和await()类似，只不过等待一定的时间后count值还没变为0的话就会继续执行
public boolean await(long timeout, TimeUnit unit) throws InterruptedException;
//将count值减1
public void countDown();

public class ZeroEvenOdd {
/**
* 最后一个打印的数字
*/
private int n;
private CountDownLatch zeroLatch = new CountDownLatch(1);
private CountDownLatch evenLatch = new CountDownLatch(1);
private CountDownLatch oddLatch = new CountDownLatch(1);

public ZeroEvenOdd(int n) {
this.n = n;
}
// 仅打印出 0
public void zero(IntConsumer printNumber) throws InterruptedException {
for (int i = 1; i <= n; i++) {
zeroLatch = new CountDownLatch(1);
printNumber.accept(0);
// 如果已经打印了奇数个0，那么接下来要打印奇数
if ((i&1) == 1) {
oddLatch.countDown();
} else {
evenLatch.countDown();
}
// 当前线程挂起，直到值减为0
zeroLatch.await();
}
}
// 仅打印出 偶数
public void even(IntConsumer printNumber) throws InterruptedException {
for (int i = 2; i <= n; i += 2) {
evenLatch.await();
printNumber.accept(i);
// 接下来要打印0
evenLatch = new CountDownLatch(1);
zeroLatch.countDown();
}
}
// 仅打印出 奇数
public void odd(IntConsumer printNumber) throws InterruptedException {
for (int i = 1; i <= n; i += 2) {
oddLatch.await();
printNumber.accept(i);
// 接下来要打印0
oddLatch = new CountDownLatch(1);
zeroLatch.countDown();
}
}

public static void main(String[] args) {
ZeroEvenOdd zeroEvenOdd = new ZeroEvenOdd(5);
try {
zeroEvenOdd.odd(e -> System.out.print(e));
} catch (InterruptedException e) {
}
}).start();
try {
zeroEvenOdd.zero(e -> System.out.print(e));
} catch (InterruptedException e) {
}
}).start();
try {
zeroEvenOdd.even(e -> System.out.print(e));
} catch (InterruptedException e) {
}
}).start();
}
}


## 三、手写阻塞队列

实现一个有界阻塞队列，拥有put方法与take方法，当队列满时put方法阻塞，当队列空时take方法阻塞。

public class MyBlockingQueue {

private Deque<Integer> queue;
private int capacity;

private Lock lock = new ReentrantLock();
private Condition isFull = lock.newCondition();
private Condition isEmpty = lock.newCondition();

public MyBlockingQueue(int capacity) {
this.capacity = capacity;
}

public void put(String item) throws InterruptedException {
lock.lock();
try {
// 队列已满
while (queue.size() >= capacity) {
// put线程等待
isFull.await();
}
// 唤醒take线程
isEmpty.signalAll();
} finally {
lock.unlock();
}
}

public String take() throws InterruptedException {
lock.lock();
String res = null;
try {
// 队列已满
while (container.size() == 0) {
// take线程等待
isEmpty.await();
}
res = queue.pollFirst();
// 唤醒put线程
isFull.signalAll();
return res;
} finally {
lock.unlock();
}
}

public static void main(String[] args) {
MyBlockingQueue blockingQueue = new MyBlockingQueue(64);
for (int i = 0; i < 100; i++) {
try {
blockingQueue.put(String.valueOf(i));
System.out.println("放入" + i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}

}).start();

for (;;) {
try {
System.out.println("取出" + blockingQueue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
}

}).start();
}
}


## 四、按顺序输出字符串

问题描述：有4个线程和1个公共的字符串数组。线程1的功能就是向数组输出A，线程2的功能就是向数组输出B，线程3的功能就是向数组输出C，线程4的功能就是向数组输出D。要求按顺序向数组赋值ABCDABCDABCD，ABCD的个数由输入的参数指定。请编写一个类，类中封装公共字符串数组及输出逻辑，4个不同的线程将会共用一个该类的实例。

### 方法1：ReentrantLock + Condition

Condition强大的地方在于它能够精确的控制多线程的休眠与唤醒（注意是唤醒，唤醒只意味着进入了同步队列，不意味着一定能获得资源），相比使用Object的wait()/notify()，使用Condition的await()/signal()这种方式能够更加安全和高效地实现线程间协作

public class AbcdLock {
private List<String> strList;
private int n;

private Lock lock = new ReentrantLock();
private Condition aCondition = lock.newCondition();
private Condition bCondition = lock.newCondition();
private Condition cCondition = lock.newCondition();
private Condition dCondition = lock.newCondition();

public AbcdLock(List<String> strList, int n) {
this.strList = strList;
this.n = n;
}

for (int i = 0; i < n; ) {
lock.lock();
try {
if (strList.size() % 4 != 0) {
// 添加A的线程等待
aCondition.await();
}
i++;
// 指定唤醒添加B的线程
bCondition.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
for (int i = 0; i < n; ) {
lock.lock();
try {
if (strList.size() % 4 != 1) {
// 添加B的线程等待
bCondition.await();
}
i++;
// 指定唤醒添加C的线程
cCondition.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
for (int i = 0; i < n; ) {
lock.lock();
try {
if (strList.size() % 4 != 2) {
// 添加C的线程等待
cCondition.await();
}
i++;
// 指定唤醒添加D的线程
dCondition.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
for (int i = 0; i < n; ) {
lock.lock();
try {
if (strList.size() % 4 != 3) {
// 添加A的线程等待
dCondition.await();
}
i++;
// 指定唤醒添加A的线程
aCondition.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
public static void main(String[] args) throws InterruptedException {
List<String> strList = new ArrayList<>(128);
AbcdLock abcdLock = new AbcdLock(strList, 4);
}).start();
}).start();
}).start();
}).start();
}


### 方法2：synchronized关键字

只用一个Object锁，4个线程同时竞争一个锁，然后同时唤醒再竞争，再附加一个条件变量就行了。

将要输入的字符串以及字符串的丢失顺序，作为变量传入。只有当数组当前索引与本线程要输入字符串的位置匹配时，才做输入动作，否则线程等待。

public class AbcdSync extends Thread {

private List<String> strList;
private String       element;
private int          cycle;
// 用一个变量来规定要输入字符串的顺序，例如0、1、2、3
private int          index;

public AbcdSync(List<String> strList, String element, int cycle, int index) {
this.strList = strList;
this.element = element;
this.cycle = cycle;
this.index = index;
}

@Override
public void run()  {
try {
for (int i = 0;i < cycle;) {
synchronized (strList) {
// 这里的判断是关键
if (strList.size() % 4 == index) {
i++;
// 唤醒其他所有线程
strList.notifyAll();
} else {
// 当前线程阻塞
strList.wait();
}
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}

public static void main(String[] args) throws InterruptedException {
List<String> strList = new ArrayList<>(256);
new AbcdSync(strList, "A", 4, 0).start();
new AbcdSync(strList, "B", 4, 1).start();
new AbcdSync(strList, "C", 4, 2).start();
new AbcdSync(strList, "D", 4, 3).start();
}
}

展开全文
• 并发并不太好， 性能也不太好， 不太稳定 GO 多核计算 JS + GO 是很多大厂的标配 package 包 模块化方案 函数 是代码行的集合 组织代码的方式 多个函数， 由一个文件组成， 完成一个大的功能， 就叫一个package ...
• leetcode账号怎么注销 目录 PythonLearning 学习资料 Python 开发工程师 工作职责： 优酷 APP 服务端业务需求开发，性能优化 参与系统架构设计、接口规范制定、技术文档编写 ...网络编程和大规模并发服务器
• 并发 1/9 数据库 3/112 深度优先搜索 61/134 设计 11/55 分而治之 8/19 动态编程 83/222 几何学 3/9 图形 18/47 贪婪的 39/100 哈希表 48/133 堆 10/35 链表 30/39 数学 47/122 记忆化 1/1 极小值 3/...
• leetcode题库 笔记 Kubernetes 使用 Kubernetes 开发 Kubernetes 源码分析 Docker Docker 原理相关 Bash Linux Linux gawk 由于 gawk 语言太过强大，想了想还是把它单独拎出来说。 关于 gawk 与 awk 的区别：gawk...
• leetcode下载 LeetCode-Nowcoder-DataStruct 记录日常刷题与数据结构,包含以下六个大包 一. BUFFcode 记录牛客网的刷题记录 题目与注释都在代码里,题目链接在开头,一个Test包含两个题目(每日必刷哦~) 二. ...
• 并发练习，顺序打印ABC的四种实现 semaphore：信息量 ThreadPoolExecutor： extendsTest： 继承练习 Guava：Guava学习 NIODemo：NIO练习 Redis： Redis学习记录 redispractice： 练习 SwordOffer： 剑指offer题目...
• leetcode 2 和 c 力码 LeetCode 问题的解决方案 算法 # 标题 解决方案 困难 1 简单的 2 中等的 3 中等的 4 难的 ...并发 # 标题 解决方案 困难 数据库 # 标题 解决方案 困难 壳 # 标题 解决方案 困难
• leetcode安卓 学习算法,终生学习. 算法本质是使程序片段执行得到一种最优最快的方法, 从而实现计算量最少最优,CPU占用最低,响应最快的结果. 实战算法 提供golang,php,c语言及多种解法实现.详细每一个步骤 图解算法 ...
• leetcode 2 算法 总结leetcode刷题的常见算法 常见的排序算法 ...数组的最大值（尝试并发和通道） 判断平方和 sqrt实现 LIS（最长增量子字符串） LCS（最长公共子串） LD（莱文斯坦距离） DATrie（未完成）
• 多线程leetcode 去游乐场 面试练习 准备编码面试的 4 个技巧： 去一般 Golang 函数的命名约定： 匿名函数： 数组增量类型，arr[i]++ 与 arr[i++]： 无符号整数 (uint) 总是非负（零或正）： 。 没有条件的 for 循环...
• 异步下载题目描述，高速并发导出文件 支持增量更新，当 LeetCode-cn 有新内容（题目/提交的代码）时，可以选择增量形式更新 使用 使用 git clone 或直接下载本仓库代码至本地 本项目需要用到第三方库 requests 和 ...
• 八皇子leetcode 编码实践 大整数的算术运算，格式为字符串。 Column和Interger之间的转换。 . 找出因子的尾随零的数量。 () 评估字符串表达式，它与单个数字正整数和算术运算符组合，包括+ - * / ( ) 使用 Python ...
• 多线程leetcode 力扣并发 我的Java问题解决方案 # 标题 困难 1114 简单的 1115 中等的 1116 中等的 1117 中等的 1188 中等的 1195 中等的 1226 中等的 1242 中等的 1279 简单的
• Java并发没讲好，不会NIO，spark没讲好，spark并发不会 字节 商业化技术 上海 3.15 一面 3.19 二面挂 docker和vm虚拟机区别，k8s有所了解吗，写道题DFS，做了一个小时很垃圾 腾讯 笔试 3.21 AC 1.75 美团 数据开发...
• 涵盖高并发、分布式、高可用、微服务、海量数据处理等领域知识 2 记录了一些需要重点掌握的 JVM 知识点，如果想更加全面地了解 JVM 底层原理，可以阅读周志明老师《深入理解 Java 虚拟机——JVM 高级特性与最佳实践...
• leetcode下载 Spring篇： spring为什么是单实例的？有什么好处？ spring单实例如何解决有状态bean的线程安全问题的？ ThreadLocal的原理？ spring事务7种传播特性和隔离级别的理解？ spring boot的启动过程 spring...
• 并发 ID 名称 日期 1114 按顺序打印 10/30 1115 交替打印 FooBar 10/30 1116 打印零偶数 10/30 1995年 Fizz Buzz 多线程 11/16 树 ID 名称 日期 303 范围总和查询 - 不可变 11/4 307 范围总和查询 - 可变 11/4 850 ...

...