-
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; * 如果某方法还没有轮到它,它会一直通过Thread.yield()让出CPU资源。 * Thread.yield()的作用是线程让出CPU资源,重新进入就绪队列去和 * 其他线程竞争CPU,Thread.yield()之后有可能又是该线程得到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){ Thread.yield(); } 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){ Thread.yield(); } printNumber.accept(i); state = 0; } } // 输出奇数 public void odd(IntConsumer printNumber) throws InterruptedException { for(int i = 1; i <= n; i += 2){ while (state != 1){ Thread.yield(); } 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); new Thread(new Runnable() { @Override public void run() { try { z.zero(new IntConsumer() { @Override public void accept(int value) { System.out.println(value); } }); } catch (InterruptedException e) { e.printStackTrace(); } } }, "ThreadA").start(); new Thread(new Runnable() { @Override public void run() { try { z.even(new IntConsumer() { @Override public void accept(int value) { System.out.println(value); } }); } catch (InterruptedException e) { e.printStackTrace(); } } }, "ThreadB").start(); new Thread(new Runnable() { @Override public void run() { try { z.odd(new IntConsumer() { @Override public void accept(int value) { System.out.println(value); } }); } catch (InterruptedException e) { e.printStackTrace(); } } }, "ThreadC").start(); }
更多相关内容 -
leetcode并发题解
2019-09-26 20:19:351.按序打印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以外的做法,虽然看题解确实有,但是反而不怎么好
-
leetcode题库-learn:redis缓存activiti工作流leetcode算法并发vueLinux内核MQ
2021-06-29 18:45:16【并发】目录下为学习并发的总结 (深入到jvm指令) 【数据库】目录下为使用数据库的一些总结 【趣唠Linux操作系统】目录下为学习Linux内核的一些总结 【问题】目录下为阅读中遇到的一些问题搜集 - 总结了OSI分层的... -
leetcode分类-concurrency:并发
2021-06-30 00:45:25leetcode 分类 concurrency Leetcode concurrency 分类题解 -
leetcode题库-leetcode-helper:leetcode助手
2021-06-29 18:59:26异步下载题目描述,高速并发导出文件 支持增量更新,当 LeetCode-cn 有新内容(题目/提交的代码)时,可以选择增量形式更新 使用 使用 git clone 或直接下载本仓库代码至本地 本项目需要用到第三方库 requests 和 ... -
Concurrency-Practice:解决了LeetCode上所有并发问题的列表
2021-03-26 01:54:52Concurrency-Practice:解决了LeetCode上所有并发问题的列表 -
leetcode赛车-Concurrency:#JAVA#并发
2021-07-01 04:58:54leetcode赛车并发 好读/看 竞争条件 当多个线程执行临界区的结果可能因线程执行的顺序而异时,则称临界区包含竞争条件。 术语竞态条件源于线程正在通过临界区竞争的比喻,并且该竞争的结果影响执行临界区的结果。 ... -
学透并发编程——LeetCode多线程协作经典题
2021-11-09 11:11:28LeetCode上专门的多线程题目版块,本文通过解决几道leetCode上的多线程协作交替打印的编程题,来加深对并发编程的理解,并训练一下并发编程思想。 1、交替打印FooBar 示例 输入: n = 2 输出: “foobarfoobar” 解释...LeetCode上专门的多线程题目版块,本文通过解决几道leetCode上的多线程协作交替打印的编程题,来加深对并发编程的理解,并训练一下并发编程思想。
一、交替打印
示例
输入: 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); new Thread(()-> { try { fooBar.foo(); } catch (InterruptedException e) { e.printStackTrace(); } }).start(); new Thread(()-> { 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); new Thread(()-> { try { fooBar.foo(); } catch (InterruptedException e) { e.printStackTrace(); } }).start(); new Thread(()-> { 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); new Thread(()-> { try { zeroEvenOdd.odd(e -> System.out.print(e)); } catch (InterruptedException e) { } }).start(); new Thread(()-> { try { zeroEvenOdd.zero(e -> System.out.print(e)); } catch (InterruptedException e) { } }).start(); new Thread(()-> { 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); new Thread(()-> { try { zeroEvenOdd.odd(e -> System.out.print(e)); } catch (InterruptedException e) { } }).start(); new Thread(()-> { try { zeroEvenOdd.zero(e -> System.out.print(e)); } catch (InterruptedException e) { } }).start(); new Thread(()-> { 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.queue = new LinkedList<>(); this.capacity = capacity; } public void put(String item) throws InterruptedException { lock.lock(); try { // 队列已满 while (queue.size() >= capacity) { // put线程等待 isFull.await(); } queue.addLast(item); // 唤醒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); new Thread(()-> { for (int i = 0; i < 100; i++) { try { blockingQueue.put(String.valueOf(i)); System.out.println("放入" + i); } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); new Thread(()-> { 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; } public void addA() { for (int i = 0; i < n; ) { lock.lock(); try { if (strList.size() % 4 != 0) { // 添加A的线程等待 aCondition.await(); } strList.add("A"); i++; // 指定唤醒添加B的线程 bCondition.signal(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } } public void addB() { for (int i = 0; i < n; ) { lock.lock(); try { if (strList.size() % 4 != 1) { // 添加B的线程等待 bCondition.await(); } strList.add("B"); i++; // 指定唤醒添加C的线程 cCondition.signal(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } } public void addC() { for (int i = 0; i < n; ) { lock.lock(); try { if (strList.size() % 4 != 2) { // 添加C的线程等待 cCondition.await(); } strList.add("C"); i++; // 指定唤醒添加D的线程 dCondition.signal(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } } public void addD() { for (int i = 0; i < n; ) { lock.lock(); try { if (strList.size() % 4 != 3) { // 添加A的线程等待 dCondition.await(); } strList.add("D"); 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); new Thread(() -> { abcdLock.addA(); }).start(); new Thread(() -> { abcdLock.addB(); }).start(); new Thread(() -> { abcdLock.addC(); }).start(); new Thread(() -> { abcdLock.addD(); }).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) { strList.add(element); 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(); } }
-
leetcode叫数-leetcode:leetcode刷题笔记及视频
2021-06-30 16:11:29并发并不太好, 性能也不太好, 不太稳定 GO 多核计算 JS + GO 是很多大厂的标配 package 包 模块化方案 函数 是代码行的集合 组织代码的方式 多个函数, 由一个文件组成, 完成一个大的功能, 就叫一个package ... -
leetcode账号怎么注销-python-learning:Python语言学习
2021-06-30 04:08:29leetcode账号怎么注销 目录 PythonLearning 学习资料 Python 开发工程师 工作职责: 优酷 APP 服务端业务需求开发,性能优化 参与系统架构设计、接口规范制定、技术文档编写 ...网络编程和大规模并发服务器 -
LeetCode:leetcode的练习记录
2021-03-22 16:42:56并发 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题库-blog:博客
2021-06-29 18:40:05leetcode题库 笔记 Kubernetes 使用 Kubernetes 开发 Kubernetes 源码分析 Docker Docker 原理相关 Bash Linux Linux gawk 由于 gawk 语言太过强大,想了想还是把它单独拎出来说。 关于 gawk 与 awk 的区别:gawk... -
leetcode下载-LeetCode-Nowcoder-DataStruct:记录日常刷题与数据结构
2021-06-29 19:23:40leetcode下载 LeetCode-Nowcoder-DataStruct 记录日常刷题与数据结构,包含以下六个大包 一. BUFFcode 记录牛客网的刷题记录 题目与注释都在代码里,题目链接在开头,一个Test包含两个题目(每日必刷哦~) 二. ... -
牛客的代码leetcode代码区别-offer:代码练习
2021-06-30 16:48:30并发练习,顺序打印ABC的四种实现 semaphore:信息量 ThreadPoolExecutor: extendsTest: 继承练习 Guava:Guava学习 NIODemo:NIO练习 Redis: Redis学习记录 redispractice: 练习 SwordOffer: 剑指offer题目... -
leetcode2sumc-leetcode:LeetCode问题的解决方案
2021-07-06 17:47:16leetcode 2 和 c 力码 LeetCode 问题的解决方案 算法 # 标题 解决方案 困难 1 简单的 2 中等的 3 中等的 4 难的 ...并发 # 标题 解决方案 困难 数据库 # 标题 解决方案 困难 壳 # 标题 解决方案 困难 -
leetcode安卓-LeetCode-Study:leetcode学习记录
2021-06-30 02:28:41leetcode安卓 学习算法,终生学习. 算法本质是使程序片段执行得到一种最优最快的方法, 从而实现计算量最少最优,CPU占用最低,响应最快的结果. 实战算法 提供golang,php,c语言及多种解法实现.详细每一个步骤 图解算法 ... -
leetcode2-algorithm-problems:来自leetcode的基本算法问题与go解决方案
2021-06-29 21:56:16leetcode 2 算法 总结leetcode刷题的常见算法 常见的排序算法 ...数组的最大值(尝试并发和通道) 判断平方和 sqrt实现 LIS(最长增量子字符串) LCS(最长公共子串) LD(莱文斯坦距离) DATrie(未完成) -
多线程leetcode-GoPlayGround:黑客等级挑战
2021-06-30 08:35:59多线程leetcode 去游乐场 面试练习 准备编码面试的 4 个技巧: 去一般 Golang 函数的命名约定: 匿名函数: 数组增量类型,arr[i]++ 与 arr[i++]: 无符号整数 (uint) 总是非负(零或正): 。 没有条件的 for 循环... -
leetcode题库-LeetCode_Helper:Python实现的LeetCode仓库美化程序。爬取LeetCode-cnAC的题目描述
2021-06-29 18:21:56异步下载题目描述,高速并发导出文件 支持增量更新,当 LeetCode-cn 有新内容(题目/提交的代码)时,可以选择增量形式更新 使用 使用 git clone 或直接下载本仓库代码至本地 本项目需要用到第三方库 requests 和 ... -
八皇后leetcode-codekata:纯娱乐
2021-07-01 11:35:56八皇子leetcode 编码实践 大整数的算术运算,格式为字符串。 Column和Interger之间的转换。 . 找出因子的尾随零的数量。 () 评估字符串表达式,它与单个数字正整数和算术运算符组合,包括+ - * / ( ) 使用 Python ... -
多线程leetcode-leetcode-concurrency:我的LeetCode解决方案
2021-06-30 08:30:17多线程leetcode 力扣并发 我的Java问题解决方案 # 标题 困难 1114 简单的 1115 中等的 1116 中等的 1117 中等的 1188 中等的 1195 中等的 1226 中等的 1242 中等的 1279 简单的 -
leetcode可以在线debug吗-solo:只要
2021-06-30 04:43:30Java并发没讲好,不会NIO,spark没讲好,spark并发不会 字节 商业化技术 上海 3.15 一面 3.19 二面挂 docker和vm虚拟机区别,k8s有所了解吗,写道题DFS,做了一个小时很垃圾 腾讯 笔试 3.21 AC 1.75 美团 数据开发... -
阿里巴巴面试题leetcode-BeatJavaMonster:努力修炼,干掉”Java“这只怪兽
2021-07-01 02:57:50涵盖高并发、分布式、高可用、微服务、海量数据处理等领域知识 2 记录了一些需要重点掌握的 JVM 知识点,如果想更加全面地了解 JVM 底层原理,可以阅读周志明老师《深入理解 Java 虚拟机——JVM 高级特性与最佳实践... -
leetcode下载-JavaTopic:Java面试题总结
2021-06-29 19:57:53leetcode下载 Spring篇: spring为什么是单实例的?有什么好处? spring单实例如何解决有状态bean的线程安全问题的? ThreadLocal的原理? spring事务7种传播特性和隔离级别的理解? spring boot的启动过程 spring... -
多线程leetcode-leetcode:2020年8月
2021-06-30 08:32:33并发 ID 名称 日期 1114 按顺序打印 10/30 1115 交替打印 FooBar 10/30 1116 打印零偶数 10/30 1995年 Fizz Buzz 多线程 11/16 树 ID 名称 日期 303 范围总和查询 - 不可变 11/4 307 范围总和查询 - 可变 11/4 850 ...