为何添加synchronized的Azure Function未出现排队变慢问题?
函数实现
我编写了如下存在竞态条件的Azure Function:
public class Function { final RefWrapper toReturn = new RefWrapper(); private static class RefWrapper { String value = ""; } @FunctionName("raceConditionTest") public HttpResponseMessage raceConditionTest( @HttpTrigger( name = "req", methods = {HttpMethod.GET}, authLevel = AuthorizationLevel.ANONYMOUS ), HttpRequestMessage<Optional<String>> request, final ExecutionContext context ) throws IOException { toReturn.value = request.getHeaders().getOrDefault("x-return-me", "0"); Thread.sleep(1000 * 10); return request.createResponseBuilder(HttpStatus.OK).body(toReturn.value).build(); } }
当我并行发起10次调用,每次请求头携带不同的x-return-me值时,10个请求全部返回相同值,符合预期——确实触发了竞态条件。随后我为方法添加synchronized修饰,同时对toReturn对象加同步锁,修改后代码如下:
@FunctionName("raceConditionTest") public synchronized HttpResponseMessage raceConditionTest( /*args*/ ) { synchronized (toReturn) { toReturn.value = request.getHeaders().getOrDefault("x-return-me", "0"); Thread.sleep(1000 * 10); return request.createResponseBuilder(HttpStatus.OK).body(toReturn.value).build(); } }
再次发起并行请求时,符合预期的是每个请求都正确返回对应的传入值,竞态条件被成功消除。
但出现了不符合预期的现象:所有请求几乎同时返回,并没有出现排队等待的情况。我查看了监控指标,确认运行过程中没有创建额外的函数实例。
问题咨询
请问为什么这段添加了同步锁的代码没有出现执行变慢的情况,请求为何不需要排队等待?我猜测该现象和Azure Function运行时有关,想确认该行为是否有官方保障、是否有对应文档说明,是否属于可稳定依赖的预期行为?
问题解答
核心原因是对Azure Functions Java的实例模型理解存在偏差:监控里看到的「没有额外函数实例」指的是平台没有扩容出额外的应用进程/容器实例,但同一个应用进程内部,Java worker并不保证只会创建一个Function类的对象处理所有请求,添加的进程内锁根本没有作用在跨请求共享的对象上。
现象解释
- 第一次无锁测试触发竞态:当时10个并发请求刚好被调度到同一个Function类对象上处理,实例成员
toReturn被多个工作线程共享。所有线程几乎同时进入方法,先后修改toReturn.value为自己携带的请求头值,10秒sleep结束时所有线程读到的都是最后一次写入的值,因此全部返回相同结果。 - 第二次加锁后竞态消失但无排队:这次10个并发请求被worker分配到了多个独立的Function类对象上处理,每个类对象持有自己独立的
toReturn成员。添加的两层同步锁:- 方法级
synchronized锁的是当前Function类的对象实例 - 同步块
synchronized (toReturn)锁的是当前实例下的toReturn对象
两个锁的锁对象都是每个请求专属的,不同请求的锁完全不相关,不会产生互斥阻塞,因此所有请求可以并行执行,总耗时就是单次请求的10秒,看起来几乎同时返回;同时每个请求操作的是自己实例的toReturn.value,不会被其他线程覆盖,竞态自然消失。
- 方法级
官方规则与使用建议
- 该行为是Java worker的正常调度表现,但没有任何确定性保障:官方明确说明,Java函数类的实例生命周期完全由运行时自主管理,运行时既可能在多个调用间复用同一个类实例,也可能为每个调用创建新的类实例,分配逻辑会随并发压力、worker版本、参数配置动态调整,没有稳定可预测的规则。
- 绝对不要依赖进程内的实例变量、实例级锁做跨调用的状态共享或者并发控制:当前观测到的现象不具备稳定性,比如低并发下所有请求被调度到同一个类实例时,同步锁就会生效,请求会排队,耗时陡增,业务逻辑会出现预期外的问题。
- 如果需要实现跨调用的状态共享、并发互斥,必须使用独立于函数实例的外部组件,比如分布式锁、数据库、共享缓存等,不要依赖进程内的同步机制。
内容的提问来源于stack exchange,提问作者Kjeld Schmidt
相关产品推荐
相关产品推荐

