1

I am practicing multithreading program and encountered a very strange problem. The code is as follows. When I run the program, sometimes(not always) it throws java.lang.NullPointerException. I have no idea how this problem occurs. Is there anybody who can help me with this? Thank you so so so much.

import java.util.*;

public class TestCME {
    public static void main(String[] args){
        TestMap tm = new TestMap();
        for(int i = 0;i < 40000 ;i++){
            tm.addCity(i,new City(i * 10));
        }
        RunnableA rA = new RunnableA(tm);
        Thread threadA = new Thread(rA);
        RunnableB rB = new RunnableB(tm);
        Thread threadB = new Thread(rB);

        threadA.start();
        threadB.start();
    }
}
class TestMap{
    Map<Integer,City> cityMap;

    public TestMap(){
        cityMap = Collections.synchronizedMap(new HashMap<Integer,City>());

    }

    public Set<Integer> getAllKeys(){
        return cityMap.keySet();
    }

    public City getCity(int id){
        return cityMap.get(id);
    }

    public void addCity(int id,City city){
        cityMap.put(id,city);
    }

    public void removeCity(int id){
        cityMap.remove(id);
    }
}

class City{
    int area;
    public City(int area){
        this.area = area;
    }
}

class RunnableA implements Runnable{
    TestMap tm;
    public RunnableA(TestMap tm){
        this.tm = tm;
    }
    public void run(){
        System.out.println("Thread A is starting to run......");

        if(tm != null && tm.cityMap != null && tm.cityMap.size() > 0){

            Set<Integer> idSet = tm.getAllKeys();
            Integer[] idArray = idSet.toArray(new Integer[idSet.size()]);
            for(int i = 0;i < idArray.length;i++){
                Integer id = idArray[i];
                City city = tm.getCity(id);
                if(city != null){
                    System.out.println(id + ":" + city.area);
                    /*try{
                        Thread.sleep(1);
                    }catch(Exception e){
                        e.printStackTrace();
                    }*/
                }
            }

        }
    }
}

class RunnableB implements Runnable{
    TestMap tm;
    public RunnableB(TestMap tm){
        this.tm = tm;
    }
    public void run(){
        System.out.println("Thread B is starting to run......");
        /*try{
            Thread.sleep(1);
        }catch(Exception e){
            e.printStackTrace();
        }*/
        System.out.println("Trying to add elements to map....");
        tm.addCity(50,new City(500));
        System.out.println("Done adding.");
        System.out.println("Trying to remove elements from map....");
        tm.removeCity(10);
        tm.removeCity(20);
        tm.removeCity(30);
        tm.removeCity(40);
        System.out.println("Done removing.");
    }
}

And the exception log is

Exception in thread "Thread-0" java.lang.NullPointerException
    at RunnableA.run(TestCME.java:69)
    at java.lang.Thread.run(Thread.java:745)

TestCME.java:69 is at City city = tm.getCity(id); in class RunnableA.

alice
  • 23
  • 4
  • @MadProgrammer End of the question. – Sotirios Delimanolis Sep 15 '15 at 01:16
  • 1
    You've got a good old fashioned race condition. One thread tries to make a copy of all the ids in `idArray`. After creating a new `Integer[]` with a size equal to the entire `idSet`, the other thread removes a bunch of entries from the map. The original thread then calls `toArray`, but because a bunch of entries have been removed, it doesn't fill up the `Integer[]`, ie. some elements are left `null`. You then loop over the ids and eventually try to unbox the `Integer` value `null` to an `int` by invoking `getCity`. That throws a NPE. – Sotirios Delimanolis Sep 15 '15 at 01:31
  • @SotiriosDelimanolis Thank you so much for your kind reply. So the solution will be synchronizing on the correct map for 'Integer[] idArray = idSet.toArray(new Integer[idSet.size()]);' right? I also tried another way to make a copy of all the ids which is 'Object[] idArray = idSet.toArray();' and I have run the program for this way many times, it seems this way is working fine without race condition. Is this way thread-safe? Could you please advise on this? Thanks very much. – alice Sep 15 '15 at 02:54
  • In this case, I'd provide an empty `Integer[]` and let the `idSet` create a properly sized one within the lock. So, `idSet.toArray(new Integer[0]);` – Sotirios Delimanolis Sep 15 '15 at 02:55
  • @SotiriosDelimanolis I tried the synchronization solution, it worked fine. Now I'm wondering if 'Object[] idArray = idSet.toArray();' thread safe without synchronization. It seems like it is since I tried a lot of times and no NPE occurred. Do you have any clues about this? – alice Sep 15 '15 at 03:13
  • It's also safe because the returned array is created and populated within a lock. You just lose the type, `Object[]` vs `Integer[]`. – Sotirios Delimanolis Sep 15 '15 at 03:14
  • @SotiriosDelimanolis Yes, I need to do `Integer id = (Integer)idArray[i];` later. It's good to learn these things through this problem. Many many thanks again. – alice Sep 15 '15 at 03:17

0 Answers0