假设我有一个方法可以检查数据库中的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());
}
答案 0 :(得分:1)
这是一个字符串问题有多长时间...
如果方法在其声明中使用synced关键字,则将是线程安全的。
但是,即使您的setId和getId方法使用了synced关键字,上面的设置ID(如果尚未预先初始化)的过程也不是。 ..但即使如此,问题还是有一个“取决于”的方面。如果两个线程不可能获得具有未初始化ID的同一对象,则您是线程安全的,因为您永远不会尝试同时修改ID。
考虑到您问题中的代码,完全有可能针对同一对象在同一时间对线程安全getid进行两次调用。他们一个接一个地获得返回值(空),并立即被抢占让另一个线程运行。这意味着两者都将再次运行线程安全的setId方法-一次又一次。
您可以将整个save方法声明为已同步,但是如果这样做,则整个方法将是单线程的,这首先会破坏使用线程的目的。您倾向于将同步代码最小化到最低限度,以最大程度地提高并发性。
您还可以在关键的if语句周围放置一个同步块,并最小化处理的单线程部分,但是如果代码的其他部分也可能设置Id,则还需要小心以前没有初始化。
另一种有利弊的可能性是将ID的初始化放入get方法中并使该方法同步,或者在构造函数中创建对象时简单地分配ID。
我希望这对您有帮助...
编辑... 上面讨论了Java语言功能。少数人提到了Java类库中的功能(例如java.util.concurrent),这些功能也提供了对并发的支持。因此,这是一个很好的附加功能,但是也有完整的软件包,它们以各种方式解决了并发和其他相关的并行编程范例(例如并行性)。
要完成此列表,我将添加诸如Akka和Cats-effect (concurrency)之类的工具。
更不用说专门针对该主题的书籍和课程了。
我只是重新阅读了您的问题,并指出您正在询问数据库。同样,答案取决于它。 Rdbms通常使您可以使用事务中的记录锁定来执行这种类型的操作。有些(例如Teradata)使用特殊的子句,例如locking row for write select * from some table where pi_cols = 'somevalues'
,这些子句将 rowhash 锁定给您,直到您更新它或某些其他条件为止。这称为悲观锁定。
其他(值得注意的是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子句的第二部分是关键部分,其基本内容是“仅在没有其他人执行 的情况下才更新记录,并且我相信其他人都将最后一次更新更新为他们这样做” 。有时可以用触发器代替“信任”位。
数据库引擎保证这些类型的数据库操作(如果可用)是“线程安全的”。
在这个答案的开头,哪个让我回到“一串弦有多长”的观察...
答案 1 :(得分:0)
一种方法,该方法检查数据库中的ID,如果该ID没有退出,则插入具有该ID的值。
在共享资源上进行的任何测试和设置操作对本质上都是不安全的,容易受到race condition的攻击。如果两个操作是分开的(不是原子的),则必须将它们作为一对保护。当一个线程完成测试但尚未完成设置时,另一个线程可能会潜入并进行测试和设置。现在,第一个线程无需完成重复的操作即可完成其设置。
正如其他人在这里所说的那样,提供必要的保护对于“堆栈溢出问题”来说是一个广泛的话题。
UPSERT
但是,让我指出,使测试和设置原子化的另一种方法。
INSERT INTO … ON CONFLICT
命令。有关详细信息,请参见this explanation。答案 2 :(得分:-1)
通常,当我们说“一种方法是线程安全的”时,它所在对象的内部和外部数据结构没有竞争条件。换句话说,方法调用的顺序是严格执行的。
例如,假设您有一个HashMap对象和两个线程,thread_a和thread_b。
thread_a调用put(“ a”,“ a”),thread_b调用put(“ a”,“ b”)。
put方法不是线程安全的(请参阅其文档),因为在thread_a执行其put的同时,thread_b也可以进入并执行自己的put。
看跌期权包含读写部分。
thread_a.read("a")
thread_b.read("a")
thread_b.write("a", "b")
thread_a.write("a", "a")
如果发生上述顺序,您可以说...方法不是线程安全的。
如何使方法成为线程安全方法是通过确保在执行线程安全方法时不会干扰整个对象的状态。一种更简单的方法是将“ synchronized”关键字放入方法声明中。
如果您担心性能,请使用带有锁定对象的同步块进行手动锁定。使用精心设计的信号灯可以进一步提高性能。