精华内容
下载资源
问答
  • 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:35
    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);
            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();
        }
    }
    
    展开全文
  • 并发并不太好, 性能也不太好, 不太稳定 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题库-blog:博客

    2021-06-29 18:40:05
    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 ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 8,502
精华内容 3,400
热门标签
关键字:

leetcode 并发