- 不,线程 B 不会更改线程 A 的
recordid and
personid值`
- 不,线程 B 不会更改线程 A 对 的引用,但如果线程 B 都引用同一个对象,则
record
线程 B 可以更新对象的内部状态EnrollRecord
EnrollRecord
。
- 不过,最重要的是,您的方法可能不是线程安全的:
编辑:
我不允许发表评论,因此在您的其他评论中澄清您的问题:
但是“EnrollRecord 记录”将被视为线程 A 上下文中的本地对象。A 将包含它自己的副本,线程 B 将包含它自己的副本。难道不是
这可能不是这种情况,理解这一点很重要。EnrollRecord
,我假设,是一个类。因此它是一个引用类型。每个线程都可以通过 引用同一个对象record
,但它的引用变量record
在线程之间是不共享的。正如您的代码所代表的那样,每个线程都不会创建EnrollRecord
. 例外情况是,例如,您的 linq 查询导致发生 sql 查询,并且您的数据提供者允许在内存中创建对象的重复副本。我假设情况并非如此,因为您正在使用锁来保护您的删除,这表明为listEnrolledCandidates
.
结束编辑,继续原始答案:
要使此方法线程安全,您可能还需要使用锁保护以下行:
EnrollRecord record = (from lst in listEnrolledCandidates
where lst.PersonId == personid && lst.RecordId == recordId
select lst).FirstOrDefault();
为什么?
我将尝试将您的问题分解为各个部分,并逐一回答:
- 一次只有一个线程可以执行锁内的 3 行
- 线程 A 是否有可能在执行操作时获取以下更新值,因为 Request(
fRequest
) 对象不同:
EnrollRecord
personid
recordid
因此,我们将从您的陈述中假设“由于 Request ( fRequest
) 对象不同”,您的意思是:Request ( fRequest
) 对象是单独创建的,因此它不是线程 A 和 B 之间可以共享的引用。
把它放在一起:
一次只有一个线程执行锁定块中的 3 行。
personid
我知道这不是一个问题,但最初澄清这一点很重要,因为它有助于理解为什么recordId
线程 A 不会被线程 B 更新。
任意数量的线程都可以进入一个方法,但您需要记住该方法访问的对象的范围。您几乎可以将编写的方法描绘为“蓝图”,当线程进入一个方法时,它会从该蓝图创建一个物理构造。每个线程都有自己的物理构造。将蓝图视为汽车的设计。你和我可能驾驶相同品牌、型号、年份、颜色的汽车,但你不会在我开车时坐在我的腿上,当你更换电台时,我的电台不会改变 - 我们每个人都有我们自己的副本。
在您的示例中:
string personid
作用域为方法,因此它的值特定于调用此方法的线程。
- 这同样适用于
int recordId
EnrollRecord record
也适用于该方法,因此record
您在线程 A 中引用的 不能由record
线程 B更新。
但是,record
是引用类型,不像int
是值类型。这个非常重要。record
是对对象的引用。它被限定在一个方法内。因此,每个线程可能持有对同一对象或不同对象的引用。如果一个线程改变了它的引用,它不会影响另一个线程,但是如果他们改变了引用对象中的某些东西(参见下面的其他点),那么两个线程都会看到这个变化。
如果其中任何一个是类字段而不是方法变量,那么一个线程可以更改另一个线程看到的值/引用。
引用类型与值类型
值类型:如果线程 A 更新变量 recordId,线程 B 将看不到这个。如果 recordId 在类级别被声明为一个字段(例如 private int recordId),而不是在方法级别声明为变量,那么情况就不是这样了——任何线程都可以访问该值并且可以阅读和更新它。要理解为什么会这样,您需要研究堆栈和堆存储之间以及变量范围的区别。带回家:在方法中声明和使用的值类型(大多数情况下)不能被运行相同方法的其他线程访问。
引用类型
- 如果线程 A 将值设置record
为带有 (PersonId
为 (1, 1) 的 ( ,RecordId
,然后线程 B 将其设置为对应于 (2, 2) 的记录,那么线程 A 仍然会引用 (1, 1)。- 如果线程 A 将值设置为 (1, 1) 的 ( , )record
记录,然后线程 B 将其设置为 (1, 1) 对应的记录,那么线程 A 仍会引用 (1, 1) , 线程 B 也是如此。PersonId
RecordId
锁
所以,是的,一次只有一个线程可以进入锁定区域,但您不需要锁定来保护本地声明的变量。需要锁来保护共享状态,而不是线程本地的变量。如果不是这种情况,我们将无法从多个线程运行以下方法并获得正确答案:
public int MinusOne(int value) {
int newValue = value - 1;
return newValue;
}
你的问题最重要的一点
问题中最重要的部分是你实际上没有问的问题。问题是:
这个方法是线程安全的吗
当我看到您的代码时,答案必须是:否
为了回答您的问题,我必须假设这listEnrolledCandidates
是对类型列表的共享引用IEnumerable<EnrollRecord>
。我还必须假设,因为您使用锁定从该列表中删除记录,所以它不是线程安全的集合/列表/字典。
这很关键:您正在锁定,因此线程 B 无法尝试从listEnrolledCandidates
线程 A 删除记录,但在以下情况下会发生什么情况:
情景 1。
record
线程 A在其范围内设置引用
- 线程B开始枚举
listEnrolledCandidates
查找记录
- 线程A删除记录
- 线程 B 完成枚举
情景 2。
- 线程 A 将引用设置为
record
对应于搜索键 (1, 1)
- 线程 B 将引用设置为
record
对应于搜索键 (1, 1)
- 线程 A 现在删除记录
- 线程 B 现在删除记录
在这两种情况下,您最终都可能遇到问题。如果您使用的是非线程安全的集合、列表、字典,则必须保护搜索、枚举、循环以及添加、删除、更新等。出于这些目的,您可以积极使用lock
,也可以使用 ReaderWriterLock (或相同的苗条版本)。
在第二种情况下,您还想在进入删除锁定区域时再次检查该记录是否仍然存在,以防 Remove 引发record
不再在集合中的异常。
其他要点
假设您在EnrollRecord
诸如public string EnrollYear { get; set; }
. 还假设线程 A 和线程 B 都引用(1 PersonId
, RecordId
1) 的 ( , )。- 如果线程 A 更改EnrollYear
为record
新值,则线程 B 将看到此更改。- 如果线程 A 设置record
为null
,那么线程 B 将不会看到记录为null
,它仍然会看到保持其对记录的引用
在我们的汽车示例中:如果您调用我正在收听的广播电台(假设 RadioStation 是一个具有 CurrentSong 属性的对象)并请求一首新歌曲,您将更改我正在收听的歌曲。(我想我已经尽可能地采用了这个类比)。
在此示例中,每个线程都拥有对同一对象的单独引用