力扣题库1114,在单例模式的前提下,使得类中的3个方法按照指定顺序执行。
题目链接点这里。
题目描述
我们提供了一个类:
public class Foo {
public void first() { print("first"); }
public void second() { print("second"); }
public void third() { print("third"); }
}
三个不同的线程将会共用一个 Foo 实例。
- 线程 A 将会调用 first() 方法
- 线程 B 将会调用 second() 方法
- 线程 C 将会调用 third() 方法
请设计修改程序,以确保 second() 方法在 first() 方法之后被执行,third() 方法在 second() 方法之后被执行。
解题思路
看到这道题,三个线程公用一个实例,首先想到的是单例模式。于是想着在类里面设置一个变量,这里因为题目指定了三个线程公用同一个实例,所以类变量还是实例变量不重要。然后3个方法的执行对应变量的3个不同的值。但是单单有变量的判断还不够,因为子线程没有阻塞,所以3个线程同时在跑,最终永远只有first()
方法被执行。
所以用如下死循环的思路加入阻塞。
变量的初始值为1,方法first()
可以直接执行,执行完毕将变量值改为2;方法second()
执行前先做一个死循环,退出条件是变量值为2;方法third()
类似,不过变量值需要为3。
初始答案
class Foo:
def __init__(self):
self.temp = 1
def first(self, printFirst: 'Callable[[], None]') -> None:
# printFirst() outputs "first". Do not change or remove this line.
printFirst()
self.temp = 2
def second(self, printSecond: 'Callable[[], None]') -> None:
while self.temp != 2:
pass
# printSecond() outputs "second". Do not change or remove this line.
printSecond()
self.temp = 3
def third(self, printThird: 'Callable[[], None]') -> None:
while self.temp != 3:
pass
# printThird() outputs "third". Do not change or remove this line.
printThird()
这样子可以达到目的,但是效率很差,执行用时2244ms,仅超过了10.42%的提交。
改进答案一
死循环无法做到有效的监控线程,所以考虑引入threading
模块。
首先是利用锁来进行阻塞。
创建2个锁,分别对应着后面两个方法的运行开关
import threading
class Foo:
def __init__(self):
self.lock1 = threading.Lock()
self.lock2 = threading.Lock()
self.lock1.acquire()
self.lock2.acquire()
def first(self, printFirst: 'Callable[[], None]') -> None:
# printFirst() outputs "first". Do not change or remove this line.
printFirst()
self.lock1.release()
def second(self, printSecond: 'Callable[[], None]') -> None:
self.lock1.acquire()
# printSecond() outputs "second". Do not change or remove this line.
printSecond()
self.lock1.release()
self.lock2.release()
def third(self, printThird: 'Callable[[], None]') -> None:
self.lock2.acquire()
# printThird() outputs "third". Do not change or remove this line.
printThird()
首先定义两个锁,然后类在实例化的时候创建并获得了锁,此后针对这两个锁如果没有release()
操作的话是会阻塞的。当first()
方法执行完毕释放了lock1
,之后second()
方法才会被执行,然后释放lock2
,之后third()
方法才会被执行。
这样修改以后确实效率大增,执行用时56ms,超过了40%的提交。
改进答案二
前面虽然用锁解决了问题,但是加入函数多了需要用到的锁个数也会增加,这并不是个可扩展的方案。
下面尝试用threading
模块中的Condition
来解决。
Condition底层也是通过锁实现的,相当于在锁的基础上又加了一层判断条件。初始化的时候使用threading.Condition([lock/rlock])
,如果不传递一个锁对象,会自动生成。之后可以通过Condition对象来调用下面几种方法
-
acquire
获取锁,调用底层锁的对应方法。通常用with来代替。
-
release
释放锁,调用底层锁的对应方法。通常用with来代替。
-
wait
线程挂起,等待被其他线程的notify或者notify_all唤醒。该方法会释放底层锁,然后阻塞。调用该方法的线程必须先获得锁,否则引发
RuntimeError
报错 -
wait_for
需要带一个可调用对象,当该对象返回True时被唤醒,通常用lambda。其余跟wait一样
-
notify
唤醒等待池中的一个线程(如果有),调用该方法的线程必须先获得锁,否则引发
RuntimeError
报错。 -
notify_all
跟notify类似,不过是唤醒所有等待池中的线程
import threading
class Foo:
def __init__(self):
self.con = threading.Condition()
self.temp = 0
def first(self, printFirst: 'Callable[[], None]') -> None:
with self.con:
self.con.wait_for(lambda: self.temp == 0)
# printFirst() outputs "first". Do not change or remove this line.
printFirst()
self.temp = 1
self.con.notify_all()
def second(self, printSecond: 'Callable[[], None]') -> None:
with self.con:
self.con.wait_for(lambda: self.temp == 1)
# printSecond() outputs "second". Do not change or remove this line.
printSecond()
self.temp = 2
self.con.notify_all()
def third(self, printThird: 'Callable[[], None]') -> None:
with self.con:
self.con.wait_for(lambda: self.temp == 2)
# printThird() outputs "third". Do not change or remove this line.
printThird()
self.temp = 3
self.con.notify_all()
这样优化以后执行用时48ms,超过了69.85%的提交。
改进答案三
阻塞还可以使用队列,分两种情况。对于不定长队列,当队列为空时,消费者那一端因为读不到内容会阻塞;对于定长队列,当队列满了时,生产者那一端因为无法写入内容会阻塞。
这里采用第一种非定长队列的方法来实现。
import queue
class Foo:
def __init__(self):
self.q1 = queue.Queue()
self.q2 = queue.Queue()
def first(self, printFirst: 'Callable[[], None]') -> None:
# printFirst() outputs "first". Do not change or remove this line.
printFirst()
self.q1.put(1)
def second(self, printSecond: 'Callable[[], None]') -> None:
self.q1.get()
# printSecond() outputs "second". Do not change or remove this line.
printSecond()
self.q2.put(1)
def third(self, printThird: 'Callable[[], None]') -> None:
self.q2.get()
# printThird() outputs "third". Do not change or remove this line.
printThird()
这里同样需要两个队列,最后执行用时44ms,超过83%的提交。
注意事项
因为不涉及到锁的获取和释放,队列方式还是要好一些。
但是如果说扩展性,Condition对象通过一个对象加一个变量就可以处理多个线程。同时还可以把每个方法中的重复代码单独提取为一个独立方法进行调用。
我是T型人小付,一位坚持终身学习的互联网从业者。喜欢我的博客欢迎在csdn上关注我,如果有问题欢迎在底下的评论区交流,谢谢。