1114. 按序打印/Print in Order
第一次接触多线程的编程题,之前仅仅是在准备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
Semaphore
与CountDownLatch
相似,不同的地方在于Semaphore
的值被获取到后是可以释放的,并不像CountDownLatch
那样一直减到底。它也被更多地用来限制流量,类似阀门的功能。如果限定某些资源最多有N个线程可以访问,那么超过N个主不允许再有线程来访问,同时当现有线程结束后,就会释放,然后允许新的线程进来。有点类似于锁的lock
与unlock
过程。
相对来说它也有两个主要的方法:用于获取权限的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();
}
}