Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/java/325.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/multithreading/4.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java 如何使此代码线程安全_Java_Multithreading - Fatal编程技术网

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;
    }