是的,它是线程安全的。但是,你的代码不是原子的,那就是你的问题所在。我会讲到localStorage
的线程安全问题,但首先要解决你的问题。
两个选项卡都可以通过if
检查,并且互相覆盖写入项目。正确处理此问题的方法是使用StorageEvent
。
这些允许您在localStorage中的键已更改时通知其他窗口,以有效地以一种内置的消息传递安全方式为您解决该问题。 这是一个关于它们的很好的阅读资料。让我们举一个例子:
localStorage.setItem("Foo","Bar");
window.addEventListener("storage",function(e){
alert("StorageChanged!");
});
现在,我来履行我关于线程安全的承诺 :)
就像我喜欢的方式一样——我们从规范和实现两个角度来观察。
规范
让我们从规范上证明它是线程安全的。
如果我们检查一下Web Storage的规范,我们可以看到它特别指出:
由于使用了存储互斥锁,多个浏览上下文将能够同时访问本地存储区,使得脚本无法检测到任何并发脚本执行。
因此,在脚本执行时,Storage
对象的长度属性和该对象的各种属性的值都不能更改,除了以脚本自身可预测的方式之外。
它甚至进一步详细说明:
每当要检查、返回、设置或删除localStorage
属性的Storage
对象的属性时,无论是作为直接属性访问的一部分,还是在检查属性存在性时,在枚举属性时,在确定存在的属性数时,还是作为执行Storage接口上定义的任何方法或属性的一部分时,用户代理必须首先获得存储互斥锁。
这里强调了我的内容。它还指出,一些实现者不喜欢这个注释。
实践中
让我们从实现上证明它是线程安全的。
选择一个随机的浏览器,我选择了WebKit(因为我之前不知道那里的代码在哪里)。如果我们检查一下WebKit的Storage实现,我们可以看到它有很多互斥锁。
让我们从头开始。当你调用setItem
或赋值时,会发生以下情况:
void Storage::setItem(const String& key, const String& value, ExceptionCode& ec)
{
if (!m_storageArea->canAccessStorage(m_frame)) {
ec = SECURITY_ERR;
return;
}
if (isDisabledByPrivateBrowsing()) {
ec = QUOTA_EXCEEDED_ERR;
return;
}
bool quotaException = false;
m_storageArea->setItem(m_frame, key, value, quotaException);
if (quotaException)
ec = QUOTA_EXCEEDED_ERR;
}
接下来,在 StorageArea
中发生了这件事:
void StorageAreaImpl::setItem(Frame* sourceFrame, const String& key, const String& value, bool& quotaException)
{
ASSERT(!m_isShutdown);
ASSERT(!value.isNull());
blockUntilImportComplete();
String oldValue;
RefPtr<StorageMap> newMap = m_storageMap->setItem(key, value, oldValue, quotaException);
if (newMap)
m_storageMap = newMap.release();
if (quotaException)
return;
if (oldValue == value)
return;
if (m_storageAreaSync)
m_storageAreaSync->scheduleItemForSync(key, value);
dispatchStorageEvent(key, oldValue, value, sourceFrame);
}
注意这里的blockUntilImportComplete
。让我们来看一下:
void StorageAreaSync::blockUntilImportComplete()
{
ASSERT(isMainThread());
if (!m_storageArea)
return;
MutexLocker locker(m_importLock);
while (!m_importComplete)
m_importCondition.wait(m_importLock);
m_storageArea = 0;
}
他们甚至还加了一张温馨的便条:
// FIXME: In the future, we should allow use of StorageAreas while it's importing (when safe to do so).
// Blocking everything until the import is complete is by far the simplest and safest thing to do, but
// there is certainly room for safe optimization: Key/length will never be able to make use of such an
// optimization (since the order of iteration can change as items are being added). Get can return any
// item currently in the map. Get/remove can work whether or not it's in the map, but we'll need a list
// of items the import should not overwrite. Clear can also work, but it'll need to kill the import
// job first.
解释如何运作,但可以更高效。
else
或_早期_返回。此外,JS 中的函数隐式返回undefined
而不是 null。 - Benjamin Gruenbaum