1114. 按序打印/Print in Order

2019-08-03  本文已影响0人  GritMan

第一次接触多线程的编程题,之前仅仅是在准备java面试的时候看过几篇多线程相关的博客,在实际工作中由于没有使用场景,所以日渐生疏。趁此机会熟悉熟悉多线程。

我的思路:volatile

题目大概意思就是保证三个线程执行任务的有序性。直觉告诉我使用标志位控制有序。由于是多线程环境,我考虑使用volatile关键词修饰标志位,以达到保证在多线程环境中标志位的可见性。(其实只是我对多线程只知道一个volatile,不管三七二十一先用上试试。。。)

class Foo {

// 保证printSecond在printFirst之后执行的标志位
    volatile boolean isFirstPrinted = false;
// 保证printThird在printSecond之后执行的标志位    
    volatile boolean isSecondPrinted = false;
    
    public Foo() {
        
    }

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

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

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

居然过了。。。不愧是Easy,真适合我:)

反思

我的这种实现每个线程均处于不断的运行状态,相比于使用线程阻塞的实现来说存在很大的资源浪费。

在访问volatile变量时不会执行加锁操作,因此也就不会使执行线程阻塞,因此volatile变量是一种比sychronized关键字更轻量级的同步机制。

扩展思路

synchronized

最基础的线程同步解决方案。

class Foo {

    boolean isFirstPrinted = false;
    
    boolean isSecondPrinted = false;
    
    Object lock = new Object();
    
    public Foo() {
        
    }

    public void first(Runnable printFirst) throws InterruptedException {
        synchronized(lock) {
            // printFirst.run() outputs "first". Do not change or remove this line.
            printFirst.run();
            isFirstPrinted = true;
            lock.notifyAll();
        }
    }

    public void second(Runnable printSecond) throws InterruptedException {
        synchronized(lock) {
            while(!isFirstPrinted) {
                lock.wait();
            }
            // printSecond.run() outputs "second". Do not change or remove this line.
            printSecond.run();
            isSecondPrinted = true;
            lock.notifyAll();
        }
    }

    public void third(Runnable printThird) throws InterruptedException {
        synchronized(lock) {
            while(!isSecondPrinted) {
                lock.wait();
            }
            // printThird.run() outputs "third". Do not change or remove this line.
            printThird.run();
        }
    }
}

CountDownLatch

CountDownLatch是一个计数器闭锁,通过它可以完成类似于阻塞当前线程的功能,即:一个线程或多个线程一直等待,直到其他线程执行的操作完成。
CountDownLatch用一个给定的计数器来初始化,该计数器的操作是原子操作,即同时只能有一个线程去操作该计数器。调用该类await方法的线程会一直处于阻塞状态,直到其他线程调用countDown方法使当前计数器的值变为零,每次调用countDown,计数器的值减1。当计数器值减至零时,所有因调用await方法而处于等待状态的线程就会继续往下执行。

import java.util.concurrent.CountDownLatch;

class Foo {

    final CountDownLatch firstLatch = new CountDownLatch(1);
    
    final CountDownLatch secondLatch = new CountDownLatch(1);
    
    public Foo() {
        
    }

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

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

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

Semaphore

SemaphoreCountDownLatch相似,不同的地方在于Semaphore的值被获取到后是可以释放的,并不像CountDownLatch那样一直减到底。它也被更多地用来限制流量,类似阀门的功能。如果限定某些资源最多有N个线程可以访问,那么超过N个主不允许再有线程来访问,同时当现有线程结束后,就会释放,然后允许新的线程进来。有点类似于锁的lockunlock过程。
相对来说它也有两个主要的方法:用于获取权限的acquire,其底层实现与CountDownLatch.countdown类似;用于释放权限的release,其底层实现与acquire是一个互逆的过程。

import java.util.concurrent.Semaphore;

class Foo {

    final Semaphore firstSem = new Semaphore(0);
    
    final Semaphore secondSem = new Semaphore(0);
    
    public Foo() {
        
    }

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

    public void second(Runnable printSecond) throws InterruptedException {
        firstSem.acquire();
        // printSecond.run() outputs "second". Do not change or remove this line.
        printSecond.run();
        secondSem.release();
    }

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

参考资料

Java中的Volatile关键字详解

Java并发之CountDownLatch、Semaphore和CyclicBarrier

上一篇下一篇

猜你喜欢

热点阅读