是的,它是线程安全的。但是,您的代码不是原子的,那是您的问题所在。我将讨论 localStorage 的线程安全,但首先,如何解决您的问题。
两个选项卡都可以通过if 检查一起并写入相互覆盖的项目。处理这个问题的正确方法是使用StorageEvents。
这些可以让您在 localStorage 中的密钥发生更改时通知其他窗口,以内置的消息传递安全方式有效地为您解决问题。 Here is a nice read about them。举个例子吧:
// tab 1
localStorage.setItem("Foo","Bar");
// tab 2
window.addEventListener("storage",function(e){
alert("StorageChanged!"); // this will run when the localStorage is changed
});
现在,我对线程安全的承诺 :)
我喜欢 - 让我们从两个角度观察这一点 - 从规范和使用实现。
规范
让我们通过规范来证明它是线程安全的。
如果我们检查specification of Web Storage,我们可以看到specifically notes:
由于使用了存储互斥体,多个浏览上下文将能够同时访问本地存储区域,这样脚本就无法检测到任何并发的脚本执行。
因此,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());
// Fast path. We set m_storageArea to 0 only after m_importComplete being true.
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.
解释这行得通,但它可能更有效。