假设我有一个检查db中的id的方法,如果该id没有退出,则插入具有该id的值。我如何知道这是否是线程安全的,以及如何确保其线程安全。我是否可以使用任何一般规则来确保它不包含竞争条件,并且通常是线程安全的。
public TestEntity save(TestEntity entity) {
if (entity.getId() == null) {
entity.setId(UUID.randomUUID().toString());
}
Map<String, TestEntity > map = dbConnection.getMap(DB_NAME);
map.put(entity.getId(), entity);
return map.get(entity.getId());
}
Run Code Online (Sandbox Code Playgroud)
这是一道线有多长的问题...
如果一个方法在声明中使用synchronized关键字,那么它就是线程安全的。
然而,即使你的setId和getId方法使用了synchronized关键字,你上面设置id的过程(如果它之前没有被初始化)也不是。..但即便如此,这个问题仍然有一个“视情况而定”的方面。如果两个线程不可能使用未初始化的 id 获取相同的对象,那么您就是线程安全的,因为您永远不会尝试同时修改 id。
鉴于您问题中的代码,完全有可能对同一个对象同时调用线程安全 getid 两次。它们一一获得返回值(null)并立即被抢占以让另一个线程运行。这意味着两者都将再次一一运行线程安全的 setId 方法。
您可以将整个 save 方法声明为同步,但如果这样做,整个方法将是单线程的,这首先就违背了使用线程的目的。您往往希望将同步代码减少到最低限度,以最大限度地提高并发性。
您还可以在关键 if 语句周围放置一个同步块,并最大限度地减少处理的单线程部分,但是您还需要小心代码的其他部分是否也可能设置 Id(如果不是)之前已初始化。
另一种有各种优点和缺点的可能性是将 Id 的初始化放入 get 方法中并使该方法同步,或者在构造函数中创建对象时简单地分配 Id。
我希望这有帮助...
编辑...上面讲的是java语言的特性。有几个人提到了 java 类库中的设施(例如 java.util.concurrent),它们也提供了对并发的支持。所以这是一个很好的补充,但也有完整的包以各种方式解决并发和其他相关的并行编程范例(例如并行性)。
为了完成这个列表,我将添加Akka和Cats-effect(并发)等工具。
更不用说专门讨论该主题的书籍和课程了。
我刚刚重读了您的问题并注意到您在询问数据库。答案又是视情况而定。Rdbms 通常允许您在事务中使用记录锁来执行此类操作。有些(如 Teradata)使用特殊子句,例如将rowhashlocking row for write select * from some table where pi_cols = 'somevalues'锁定给您,直到您更新它或某些其他条件。这称为悲观锁定。
其他的(特别是 nosql)有乐观锁定。这是当您读取记录时(就像您使用 getid 暗示的那样),没有机会锁定记录。然后您进行条件更新。条件更新有点像这样:write the id as x provided that when you try to do so the Id is still null (or whatever the value was when you checked)。这些类型的操作通常通过 API 进行。
您还可以在 RDBM 中进行乐观锁定,如下所示:
SQL
Update tbl
Set x = 'some value',
Last_update_timestamp = current_timestamp()
Where x = bull AND last_update_timestamp = 'same value as when I last checked'
在本示例中,where 子句的第二部分是关键位,它基本上表示“仅在没有其他人这样做的情况下才更新记录,并且我相信其他人都会更新最后的更新”当他们这样做时”。“信任”位有时可以用触发器代替。
数据库引擎保证这些类型的数据库操作(如果可用)是“线程安全的”。
这让我回到了这个答案开头的“一根绳子有多长”的观察结果......