I have implemented a thread safe inventory system. The Product is bound to a location in a warehouse. I have a Product class and a Location class. In the Product class constructor, you will pass the Location. Further, I have an Inventory class which will read from input file to build the inventory in a ConcurrentHashMap data structure for binding the product and the quantity. I have taken the ConcurrentHashMap so as to have the pick method and restock method inside the Inventory class to be thread safe. I have also taken HashMap for binding product id and product so as to retrieve product from id in \$O(1)\$ time. The ConcurrentHashMap is taken so as to implement pick and restock method quickly as quantity can be retrieved in \$O(1)\$ time and can be updated quickly. Also to mention here, Inventory will build a ConcurrentHashMap from Inventory.txt which is written in: ProductId, ProductName, InitialQuantity, LocationId, LocationName. The records of the file are: 1 Pens 20 100 Walmart, 2 Pencils 50 101 Walmart.Product.Javapublic class Product {    int id;    String name;    Location loc;    Product(int id, String name, Location loc){        this.id=id;        this.name=name;        this.loc=loc;    }}Location.javapublic class Location {    int id;    String name;    Location(int id, String name){        this.id=id;        this.name=name;    }}Inventory.javaimport java.io.BufferedReader;import java.io.FileInputStream;import java.io.InputStreamReader;import java.util.HashMap;import java.util.concurrent.ConcurrentHashMap;public class Inventory {    /*Map for binding Product with Quantity. This map will speed up the things in picking and re-stocking.    otherwise, we have to iterate every time.*/    ConcurrentHashMap<Product,Integer> Inv=new ConcurrentHashMap<Product, Integer>();    //Map for binding product id with Product object    HashMap<Integer, Product> actualProduct=new HashMap<Integer, Product>();    //Build inventory from input file. The file record is written in: ProductId, Product Name, Initial Quantity, Location Id, Location Name    //The records of the file are: 1 Pens 20 100 Walmart, 2 Pencils 50 101 Walmart    void buildInventory(String filename){        try{        FileInputStream fstream = new FileInputStream(C:\\dev\\eclipse_workspace\\Warehouse\\src\\+filename);        BufferedReader br = new BufferedReader(new InputStreamReader(fstream));        String strLine;         //Read File Line By Line        while ((strLine = br.readLine()) != null){          //split string          String S[]=strLine.split( );          int id=Integer.parseInt(S[0]);          int quantity=Integer.parseInt(S[2]);          //create location          int lid=Integer.parseInt(S[3]);          Location L=new Location(lid, S[4]);          //create product          Product P=new Product(id, S[1], L);          //Put product and Quantity in ConcurrentHashMap          Inv.put(P, quantity);          //Put id and Product in HashMap          actualProduct.put(id, P);        }        //Close the input stream        br.close();       }catch(Exception e){        e.printStackTrace();    }}    //Print current inventory    void printInventory(){        System.out.println(Printing Inventory);        for(Product P:Inv.keySet()){            System.out.print(P.id+ );            System.out.print(P.name+ );            System.out.print(P.loc.id+ +P.loc.name+ );            System.out.print(Inv.get(P));            System.out.println();        }    }    //Subtract quantity if the product is purchased.    void pickProduct(int id, int quantity){        if(!actualProduct.containsKey(id))        System.out.println(Sorry, the product is not available in inventory);        else if(quantity<=0)        System.out.println(Invalid Quantity);        else if(Inv.get(actualProduct.get(id)).intValue()<quantity)        System.out.println(Not enough stock);        else        Inv.put(actualProduct.get(id), Inv.get(actualProduct.get(id)).intValue()-quantity);     }    //Add quantity so as to restock the product    void restockProduct(int id, int quantity){        if(!actualProduct.containsKey(id))        System.out.println(Sorry, the product is not available in inventory);        else if(quantity<=0)        System.out.println(Invalid Quantity);        else        Inv.put(actualProduct.get(id), Inv.get(actualProduct.get(id)).intValue()+quantity);     }    public static void main(String args[]){        System.out.println(args[0]);        final Inventory Inv=new Inventory();        Inv.buildInventory(args[0]);        Inv.printInventory();        //Inv.pickProduct(2, 40);        //Inv.printInventory();        Thread t1 = new Thread(new Runnable() {               public void run() {                  Inv.pickProduct(2, 40);               }            });            t1.start();        Inv.printInventory();        Thread t2 = new Thread(new Runnable() {                   public void run() {                      Inv.pickProduct(2, 40);                   }                });            t2.start();        Inv.printInventory();        Thread t3 = new Thread(new Runnable() {                   public void run() {                      Inv.restockProduct(2, 50);                   }                });            t3.start();        Inv.printInventory();    }}
Thread-safe inventory system
java;thread safety
For all of your small value classes, you should have access modifiers and, optionally, accessors. For example,Product.javapublic final class Product {    public final int id;    public final String name;    public final Location location;    public Product(final int id, final String name, final Location location) {        this.id = id;        this.name = name;        this.location= location;    }}Notice the following changes:The class is declared as final so it cannot be extended.The instance variables are declared as public and final. Without the access modifier, the variables are defaulted to package access. And since you don't modifier them either, they can be nicely declared as immutable.The constructor parameters are declared as final. This is entirely optional, but ensures that you're not modifying them, which is often a no-no.Some lovely spacing in the assignments.Renaming loc to location.In the above refactor, I didn't provide any accessors because the fields are now final. However, if you wanted to, you could do so easily:Location.javapublic final class Location {    private final int id;    private final String name;    Location(final int id, final String name) {        this.id = id;        this.name = name;    }    public int getId() {        return id;    }    public String getName() {        return name;    }}Now, for your main class,Inventory.java/*Map for binding Product with Quantity. This map will speed up the things in picking and re-stocking.otherwise, we have to iterate every time.*/ConcurrentHashMap<Product,Integer> Inv=new ConcurrentHashMap<Product, Integer>();should be /** * Map for binding Product with Quantity. This map will speed up the things in picking and re-stocking; * otherwise, we have to iterate every time */Map<Product,Integer> inventory = new ConcurrentHashMap<>();Notice:Actual JavaDoc formatting of a comment is used.We define the variable using the interface (Map) rather than an implementation.We make use Java 7's Diamond Operator.The same can be done for actualProduct.For the buildInventory method, take a look into another Java 7 feature: Try-With-Resources. This will take care of closing your streams even in the event of an exception (which you're not currently doing).Also, no variable should start with a capital letter, or be a single character long (except in some instances). So this:String S[]=strLine.split( );could be final String[] values = strLine.split( );Additionally, there's a small performance issue using String#split, as a new Pattern has to be compiled each time. You can futher improve this with the following:private static final SPLIT_PATTERN = Pattern.compile( );...final String values = SPLIT_PATTERN.split(strLine);You're now playing with an array now, and accessing it blindly. This is scary stuff to me, as you're just inviting an ArrayIndexOutOfBoundsException to occur. Always assume faulty input, and be sure to handle those exceptional cases. Catching Exception e at the bottom of your method is not a suitable solution. Here is a possible refactor of the method:  private static final Pattern SPLIT_PATTERN = Pattern.compile( );  private static final int ID_INDEX = 0;  private static final int PRODUCT_INDEX = 1;  private static final int QUANTITY_INDEX = 2;  private static final int LOCATION_ID_INDEX = 3;  private static final int LOCATION_INDEX = 4;  private static final int EXPECTED_INDEX_SIZE = 4;  public void buildInventory(final String filename) {    try (final InputStream inputStream =             new FileInputStream(C:\\dev\\eclipse_workspace\\Warehouse\\src\\ + filename);         final BufferedReader reader =             new BufferedReader(new InputStreamReader(inputStream))) {      String strLine;      while ((strLine = reader.readLine()) != null) {        try {          final String line[] = SPLIT_PATTERN.split(strLine);          if (EXPECTED_INDEX_SIZE != line.length) {            System.out.println(Malformated line:  + strLine);            continue;          }          final int id = Integer.parseInt(line[ID_INDEX]);          final int quantity = Integer.parseInt(line[QUANTITY_INDEX]);          final int locationId = Integer.parseInt(line[LOCATION_ID_INDEX]);          final Location location = new Location(locationId, line[LOCATION_INDEX]);          final Product product = new Product(id, line[PRODUCT_INDEX], location);          inventory.put(product, quantity);          actualProduct.put(id, product);        } catch (final NumberFormatException e) {          System.out.println(Error parsing value:  + e.getMessage());        }      }    } catch (final FileNotFoundException e) {      System.out.println(Error reading file:  + e.getMessage());    } catch (final IOException e) {      System.out.println(Error reading file:  + e.getMessage());    }  }}Finally, if you're going to store objects in a hashmap, it's recommended practice to override equals and hashcode. IDEs can typically do this for you. For example, IntelliJ gives me the following for your Product class (which you use as a key in Inv object`):  public final class Product {    public final int id;    public final String name;    public final Location location;    public Product(final int id, final String name, final Location location) {      this.id = id;      this.name = name;      this.location = location;    }    @Override    public boolean equals(Object o) {      if (this == o) return true;      if (o == null || getClass() != o.getClass()) return false;      Inventory.Product product = (Inventory.Product) o;      if (id != product.id) return false;      if (name != null ? !name.equals(product.name) : product.name != null) return false;      return !(location != null ? !location.equals(product.location) : product.location != null);    }    @Override    public int hashCode() {      int result = id;      result = 31 * result + (name != null ? name.hashCode() : 0);      result = 31 * result + (location != null ? location.hashCode() : 0);      return result;    }  }