Java 如何使此代码线程安全
下面代码中的Java 如何使此代码线程安全,java,multithreading,Java,Multithreading,下面代码中的testMethod()在运行时被许多线程访问。如果在映射中找到条目,它接受“firstName”,并立即返回“lastName”。如果没有,它会在API中查找姓氏,更新映射并返回相同的结果。现在这个方法在相同的map数据结构中执行put和get操作,我认为这不是“线程安全的”。我现在不知道是让函数“同步”还是使用ConcurrentHashMap而不是HashMap public class Sample { Map<String, String> first
testMethod()
在运行时被许多线程访问。如果在映射中找到条目,它接受“firstName”,并立即返回“lastName”。如果没有,它会在API中查找姓氏,更新映射并返回相同的结果。现在这个方法在相同的map数据结构中执行put和get操作,我认为这不是“线程安全的”。我现在不知道是让函数“同步”还是使用ConcurrentHashMap而不是HashMap
public class Sample {
Map<String, String> firstNameToLastName = new HashMap<>();
public String testMethod(String firstName) {
String lastName = firstNameToLastName.get(firstName);
if (lastName!= null)
return lastName;
String generateLastName = SomeAPI.generateLastName(firstName);
firstNameToLastName.put(firstName, generateLastName);
return generateLastName;
}
}
公共类示例{
Map firstNameToLastName=new HashMap();
公共字符串testMethod(字符串名){
字符串lastName=firstNameToLastName.get(firstName);
if(lastName!=null)
返回姓氏;
字符串generateLastName=SomeAPI.generateLastName(firstName);
firstName到lastname.put(firstName,generateLastName);
返回generateLastName;
}
}
您的代码不是线程安全的,这是对的。这会导致以下问题,其严重的缺点是大多数情况下它都可以正常工作:
synchronized
一个非常基本的修复方法是,使用synchronized关键字(这可以添加到函数定义中,但应该使用私有对象进行同步),同时只允许一个线程访问函数的并发部分
正如您所看到的,这可以使实现非常优雅。但是,如果您在映射上有更多(和更复杂)的操作,您可能会遇到麻烦。您可以使用
ReentrantReadWriteLock
,这样您可以让多个线程读取
public class Sample {
final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
Map<String, String> firstNameToLastName = new HashMap<>();
public String testMethod(String firstName) {
rwl.readLock().lock();
String lastName = firstNameToLastName.get(firstName);
rwl.readLock().unlock();
if (lastName!= null)
return lastName;
lastName = SomeAPI.generateLastName(firstName);
// Must release read lock before acquiring write lock, it is already released
rwl.writeLock().lock();
//now another thread could already put a last name, so we need to check again
lastName = firstNameToLastName.get(firstName);
if (lastName== null)
firstNameToLastName.put(firstName, lastName);
rwl.writeLock().unlock();
return lastName;
}
公共类示例{
final ReentrantReadWriteLock rwl=新的ReentrantReadWriteLock();
Map firstNameToLastName=new HashMap();
公共字符串testMethod(字符串名){
rwl.readLock().lock();
字符串lastName=firstNameToLastName.get(firstName);
rwl.readLock().unlock();
if(lastName!=null)
返回姓氏;
lastName=SomeAPI.generateLastName(firstName);
//必须在获取写锁之前释放读锁,它已被释放
rwl.writeLock().lock();
//现在另一个线程可能已经输入了姓氏,所以我们需要再次检查
lastName=firstName到lastName.get(firstName);
if(lastName==null)
firstName tolastname.put(firstName,lastName);
rwl.writeLock().unlock();
返回姓氏;
}
}在我看来,您只需要同步尝试访问共享资源(例如集合)的部分代码 在您的代码中,除了您正在调用的api(我们对此一无所知)之外,唯一的共享资源是您的名字到姓氏的映射,因此如果您将其设置为并发集合(并发hashMap),那么映射中的数据就可以了(在两个线程进入“testMethod”的情况下)并且在map中找不到名称,在竞争条件下,其中一个线程首先成功调用put方法并将姓氏添加到map,然后另一个线程使用相同的键/值调用put方法,但最终您的map具有正确的值)
但是在您的代码中,testMethod的整体操作是意外的,例如,在一个线程中,当另一个线程使用相同的键更新映射时,可能找不到键并调用api来生成姓氏 应该使用IMO ConcurrentHashMap,因为该映射是实例变量,所以最好使其线程安全。您的方法中的其他数据似乎是本地的,因此它已经是线程安全的。这两种方法都适用于您。但ConcurrentHashMap是更好的解决方案,因为很可能有人会编写额外的方法,而忘记添加
synchronized
关键字。您在firstname到lastname
上只有一个操作,但是synchronized testMethod将阻止整个方法,而不仅仅是一个关键操作。当某个键的缓存项丢失时,只有一个线程计算该值,而获取相同键的任何其他线程都会耐心等待。这可以减少后端的负载。CacheBuilder还允许配置逐出策略。是否将对象实例传递给同步块?既然我们必须传递某个对象,我们可以将HashMap对象传递到同步块吗?。谢谢(这可以添加到函数定义中,但您应该使用私有对象进行同步)在您的示例中,这些解决方案之间的区别是什么?方法的完整块位于同步块中。@AxelH@ankitjava这是一个众所周知的约定。请参阅:您应该仅在私有最终对象
字段上进行同步,并且永远不要将该引用暴露在类之外。否则,其他类可能会在同一对象上同步。这使得人们很难理解在何处以及如何使用特定的锁,从而导致死锁和争用。在具有其他目的的对象(如HashMap)上进行同步可能会导致将该锁暴露在类之外。@AxelH选择一个私有对象,该对象不用于任何其他用途,以确保控制在该对象上同步的操作。如果在映射上同步,则不知道其他事情(如映射的实现)是否也会在该对象上同步。如果在方法定义上使用关键字,则实际上是在“this”对象上进行同步。这意味着类的任何用户也可以在同一对象上同步。我展示的方式是大多数人是如何做到这一点的,尽管还有很多其他的方法可以做到这一点。@Thirler,谢谢,我忘记了同步方法是在上完成的。这是有道理的,可能是杀伤力太大了
public class Sample {
ConcurrentHashMap<String, String> firstNameToLastName = new ConcurrentHashMap<>();
public String testMethod(String firstName) {
return firstNameToLastName.computeIfAbsent(firstName,
name -> SomeAPI.generateLastName(name));
}
}
}
public class Sample {
final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
Map<String, String> firstNameToLastName = new HashMap<>();
public String testMethod(String firstName) {
rwl.readLock().lock();
String lastName = firstNameToLastName.get(firstName);
rwl.readLock().unlock();
if (lastName!= null)
return lastName;
lastName = SomeAPI.generateLastName(firstName);
// Must release read lock before acquiring write lock, it is already released
rwl.writeLock().lock();
//now another thread could already put a last name, so we need to check again
lastName = firstNameToLastName.get(firstName);
if (lastName== null)
firstNameToLastName.put(firstName, lastName);
rwl.writeLock().unlock();
return lastName;
}