A
A
aiwan92012-08-14 21:55:35
Java
aiwan9, 2012-08-14 21:55:35

How to ensure performance in a multi-threaded environment?

Good afternoon.
There is the following problem. There is a service class OrderService . It has a create(Order order, Long companyId) method , which checks if the order amount does not exceed the company's balance before creating an order. The class code is something like the following.

public class OrderService {
   public Order create(Order order, Long companyId) {
      Company company = companyService.get(companyId);
      checkCompanyBalance(company.getBalance(), order.getSum());
      return create(order);
   }
}

There is a possibility that two users will create two orders for the company at the same time, thus exceeding the balance of the company. To avoid this, we make our synchronized method and that's it. But at the same time, productivity sags a lot, because there is no way to create an order for another company.
What are the solutions to this problem?
It only occurs to me to create a pool of IDs for companies that are currently being ordered.
public class CompanyPool {
   private Set<Long> companyIds = new HashSet<Long>();

   public synchronized containsAndPut(Long companyId) {
      if(companyIds.contains(companyId)) {
         return false;
      } else {
         companyIds.add(companyId);
         return true;
      }
   }

   public synchronized remove(Long companyId) {
      companyIds.remove(companyId);
   }
}

and rewrite the OrderService#create() method as follows
public class OrderService {
   private CompanyPool companyPool = new CompanyPool();

   public Order create(Order order, Long companyId) {
      if(companyPool.containsAndPut(companyId)) {
         Company company = companyService.get(companyId);
         checkCompanyBalance(company.getBalance(), order.getSum());
         Order newOrder = create(order);
         companyPool.remove(companyId);
         return newOrder;
      } else {
         // wait till other thread will stop creating order for specified company
      }
}

How viable is such a solution? Comrades, can someone solve similar problems?

Answer the question

In order to leave comments, you need to log in

5 answer(s)
G
gvsmirnov, 2012-08-15
@aiwan9

As correctly noted above, it is not very clear why the company's balance sheet data is stored exclusively in memory. This kind of information must be persistent.
In essence, your problem: you just need to start a separate lock for each company. To be sure to have exactly one lock, use ConcurrentMap#putIfAbsent .
In general, you should not do something that you do not understand in any way. Read something first. I recommend Java Concurrency in Practice .

D
dborovikov, 2012-08-17
@dborovikov

> But at the same time, productivity sags a lot, because there is no way to create an order for another company.
I do not believe. Do you have a 100 million audience?

A
Alexey Huseynov, 2012-08-14
@kibergus

Use DBMS. This is only the first rake that you start stepping on. Your solution will misbehave if the program crashes. It may turn out that the money is written off, but the order is not remembered, or vice versa. Moreover, you need a DBMS with transaction support, the now fashionable noSQL solutions will not work, or you will write your own transactional engine on top of them (there is an example in the documentation for mongodb).
Well, or, if you really want to, you need to rewrite
checkCompanyBalance(company.getBalance(), order.getSum());
so that it enters the critical section (captures the mutex belonging to the company object), checks the balance, if there is enough money, subtracts the amount from the balance, writes a message to the transaction list, for which money was debited, and then writes all this information into non-volatile, multi-machine-reserved memory, and finally released the mutex. Then we create an order, write it to the same reliable storage. And, after that, we again climb into the company object and mark the transaction as completed.

M
Moxa, 2012-08-14
@Moxa

you can make synchronized by company… but this is if there is only one instance for each company…

K
knott, 2012-08-15
@knott

I completely agree with comrade gvsmirnov.
However, it seems to me that performance sags due to an overhead on the scheduler, if the thread fails to take the lock. Therefore, it seems to me that there is a place to be a spinlock based on AtomicBoolean.

public class OrderService {
  
   private AtomicBoolean locked = new AtomicBoolean(false);

   public Order create(Order order, Long companyId) {
      Order result;

      // Пробовать зайти в крит. секцию.
      while (!locked.compareAndSet(false, true)) {
          Company company = companyService.get(companyId);
          checkCompanyBalance(company.getBalance(), order.getSum());
          result = create(order);

          // Открыть путь другим потокам.
          locked.set(false);
          break;
      }
      return result;
   }
}

However, this approach is only suitable if:
  • methods checkCompanyBalanceand createare executed quickly . Otherwise, your code will just wait for the lock to be released, wasting CPU cycles completely incompetently.
  • multiprocessor system. There is no point in waiting for a lock held on another thread to be released.

Didn't find what you were looking for?

Ask your question

Ask a Question

731 491 924 answers to any question