解题:Leetcode1114题:多线程顺序打印问题
< 返回列表时间: 2020-07-14来源:OSCHINA
1.使用synchronized关键字,利用内部对象锁和条件来控制打印顺序 class Foo { private int idOfPrinter; public Foo() { idOfPrinter = 1; } public synchronized void first(Runnable printFirst) throws InterruptedException { while (idOfPrinter!=1) wait(); printFirst.run(); idOfPrinter = 2; notifyAll(); } public synchronized void second(Runnable printSecond) throws InterruptedException { while (idOfPrinter!=2) wait(); printSecond.run(); idOfPrinter = 3; notifyAll(); } public synchronized void third(Runnable printThird) throws InterruptedException { while (idOfPrinter!=3) wait(); printThird.run(); idOfPrinter = 1; notifyAll(); } }
思路:所有线程试图获取内部对象锁,没获取锁的线程阻塞在那里,成功获取到了但是不满足执行条件(idOfPrinter不是本线程期望的),本线程让出锁并进入等待状态,成功获取了内部对象锁并且满足执行条件(idOfPrinter正是本线程期望的)执行打印,再将idOfPrinter的值赋为下一个书序打印线程期望的id,并执行notifyAll();唤醒所有内部对象锁条件等待集上的线程,使其重新进行锁的竞争,再次获得锁之后,重新从wait()处开始运行,并再次检查执行条件,重复上述过程。
2.使用JUC包中的可重入锁和条件来实现打印顺序的控制 public class Foo { private int idOfPrinter; private ReentrantLock lock; private Condition condition; public Foo() { idOfPrinter = 1; lock = new ReentrantLock(); condition = lock.newCondition(); } public void first(Runnable printFirst) throws InterruptedException { lock.lock(); try { while (idOfPrinter!=1) condition.await(); printFirst.run(); idOfPrinter = 2; condition.signalAll(); }finally { lock.unlock(); } } public void second(Runnable printSecond) throws InterruptedException { lock.lock(); try { while (idOfPrinter!=2) condition.await(); printSecond.run(); idOfPrinter = 3; condition.signalAll(); }finally { lock.unlock(); } } public void third(Runnable printThird) throws InterruptedException { lock.lock(); try { while (idOfPrinter!=3) condition.await(); printThird.run(); idOfPrinter = 1; condition.signalAll(); }finally { lock.unlock(); } } }
思路:思路和上面使用synchronized方法没有任何区别,只是换了一种实现方式,synchronized使用java语言层面实现基于锁的控制,可重入锁是从API层面实现基于锁的控制;
3.使用线程同步器countDownLatch来控制一组协同工作的线程 class Foo { private CountDownLatch twolatch; private CountDownLatch threelatch; public Foo() { twolatch = new CountDownLatch(1); threelatch = new CountDownLatch(1); } public void first(Runnable printFirst) throws InterruptedException { printFirst.run(); twolatch.countDown(); } public void second(Runnable printSecond) throws InterruptedException { twolatch.await(); printSecond.run(); threelatch.countDown(); } public void third(Runnable printThird) throws InterruptedException { threelatch.await(); printThird.run(); } }
思路:三个线程需要按照一定的规则进行协同工作(按先后顺序打印),所以可以用线程同步器来控制这一组线程的行为。代码首先定义了两个JUC包中的线程同步器倒计时门栓,分别用来控制打印two的线程和打印three的线程。打印three的线程成功打印的前提是threeLatch为0,但是threeLatch只有在打印two线程成功打印后才能被修改。打印two的线程成功打印的前提是twoLatch为0,但是twoLatch只有在打印one的线程成功打印后才能被修改。由此可以控制三个线程的执行顺序,先one才能two才能three。
4.使用线程同步器SynchronousQueue来控制一组协同工作的线程 public class Foo { private SynchronousQueue threeToTwo; private SynchronousQueue twoToOne; public Foo() { threeToTwo = new SynchronousQueue<Integer>(); twoToOne = new SynchronousQueue<Integer>(); } public void first(Runnable printFirst) throws InterruptedException { printFirst.run(); twoToOne.take(); } public void second(Runnable printSecond) throws InterruptedException { twoToOne.put(1); printSecond.run(); threeToTwo.take(); } public void third(Runnable printThird) throws InterruptedException { threeToTwo.put(2); printThird.run(); } }
思路:这个思路和上面使用倒计时门栓的思路是一样,倒计时门栓和线程同步队列都属于线程同步器。同步队列没有长度可言,它虽然实现了队列接口,但是事实上它只是一种将生产者与消费者匹配的机制,单纯的单方向地将消息同生产者传递到消费者。当producer调用一个同步队列的put方法,producer会阻塞直到cosumer调用take方法;反之,当cosumer调用take方法,consumer会阻塞直到producer调用这个同步队列的put方法。上面的代码正式利用了这个特点,来实现线程打印顺序的控制。
5.使用volatile关键字控制线程的打印顺序 class Foo { private volatile int idOfPrinter; public Foo() { idOfPrinter = 1; } public void first(Runnable printFirst) throws InterruptedException { while (idOfPrinter!=1) Thread.sleep(0,10); printFirst.run(); idOfPrinter=2; } public void second(Runnable printSecond) throws InterruptedException { while (idOfPrinter!=2) Thread.sleep(0,10); printSecond.run(); idOfPrinter=3; } public void third(Runnable printThird) throws InterruptedException { while (idOfPrinter!=3) Thread.sleep(0,10); printThird.run(); idOfPrinter=1; } }
思路:volatile关键字的语义有两个:1.禁止指令重排序;2写线程的修改对其他线程来说是立即可见的。volatile关键字虽然不能保证原子性,但是对于满足以下条件的应用场景使用volatile是合适的:1.程序中的运算结果不依赖于volatile变量的当前值;2.volatile变量不需要与其他状态变量共同参与不变约束。对于本体来说,不需要任何时候只有一个写线程其他都是读线程,所以使用volatile是恰当的。使用volatile关键字能够禁止指令重排序,防止一个执行引擎在执行打印操作之前就将idOfPrinter的值改变导致后一个线程比前一个线程先打印的情况,volatile关键字还能够保证其中任何写线程修改之后其他读线程能够立刻读取到最新的操作;
热门排行