0

I`m trying to make an api-checking app that must send requests asynchronously using the pool of bots while termination date is not reached.

On startup creates a final List<Bot> is created and will be executed until the termination date. The idea is that at each iteration of the while cycle, we would iterate through the List<Bot> and try to launch them through the executor in wrapper BotRunner. (if the bot is already launched by another thread, we will simply hang on the mutex monitor and wait for completion Bot#run from another thread. After that - execute Bot#run normally).

After execution i see 1-2% of duplicating requests from different bots (server sends correct data i swear). Looks like simple race-condition, but i don't realize what is causes. Is it Bot? - i suppose not (use synchronized). Is it final List<Bot>? - is guess no (we don't change state of list)

Bot entity:

public class Bot 
{
    private final BotStatistic statisic = new BotStatistic();
    private final Object mutex = new Object();
    private String responseData;    

    void run() 
    {
        synchronized(mutex) {
          // first http call -> responseData = "j43iFS135SFSF";
          // second http call(responseData) -> {"success": "true"}
          // save to statistic
          // responseData = null;
        }
    }
}

BotRunner entity:

public class BotRunner implements Runnable 
{
    private final Bot bot;
    
    public BotRunner(Bot bot) 
    {
        this.bot = bot;
    }
    
    @Override
    public void run() 
    {
       bot.run();
    }
}

Trying to execute in this way:

//init executor
LocalDateTime termination = LocalDateTime.now().plusSeconds(5L);
while (LocalDateTime.now().isBefore(termination)) 
{
    for (Bot bot : bots) 
    {
        executor.execute(new BotRunner(bot));
    }
}
//shutdown & close executor

WHAT NOT WORKS:

  1. String -> final StringBuffer not helps.
  2. Use local method variable String not helps.

Could someone say what i did wrong?

14
  • mutex.wait() in synchronized block??? Commented Mar 17 at 0:30
  • 1
    Your BotRunner class seems entirely redundant. Bot already implements Runnable. Commented Mar 17 at 5:13
  • 1
    What is “replica”? What does it have to do with Bot? Is it even important to the Question here? Write your issue description as simply as possible. Commented Mar 17 at 5:20
  • 1
    @IndependenceCR you say “after execution i see the 1-2% of duplication responseData” but your code does not contain anything to detect the “after execution” state reliably nor to inspect the result of the operation in this state. As soon as one thread completes the operation (leaves the synchronized block), the next thread can start the next operation (enter the synchronized block). There is no in-between where you could check the result without races. Nor any other place where checking the result could work.
    – Holger
    Commented Mar 20 at 16:27
  • 1
    So your problem is with the BotStatistic object but for some reason, all your attempts were changes to the unrelated responseData variable?
    – Holger
    Commented Mar 20 at 17:08

1 Answer 1

2

Not sure if this is the cause of your problem (which you do not actually explain), but since you emphasized the importance of every bot running in its own thread I must point out that calling Executor#execute does not necessarily run its task on a background thread. Read the Javadoc; it clearly states that the given task may, or may not, run on a background thread.

If you always want to run tasks on a background thread, call the ExecutorService methods such as submit, invoke, invokeAll.


Kill your mutex and synchronized. Since each bot instance is updating its own member fields, and each bot instance is being run exactly once in its own thread, there is no conflict across threads.


Your BotRunner class seems utterly redundant. The Bot class already has a run method. So Bot can be declared as implementing Runnable and serve as our task.

Your response data member field could just as well be a local variable.

public class Bot implements Runnable
{
    private Statistic statistic = null ;

    void run() 
    {
        String responseData = firstHttpCall … 
        this.statistic = secondHttpCall ( responseData ) ;
    }
}

Simply submit your list of Bot objects to an executor service.

If your tasks involve blocking, such as making network calls, then you likely should be using virtual threads in Java 21+ for maximum performance.

In modern Java, an executor service is AutoCloseable. So we can use try-with-resources syntax to automatically close it.

try 
(
    ExecutorService executorService = Executors.newVirtualThreadPerTaskExecutor() ;
)
{
     bots.forEach( executorService :: submit ) ;
}
System.out.println( "Tasks done. Executor service closed." ) ;

By the way… Never call LocalDateTime.now. That class cannot represent a moment as it lacks the context of a time zone or offset from UTC. To track a point on the timeline, use Instant, OffsetDateTime, or ZonedDateTime.

4
  • You're right, I didn't describe the problem quite correctly. @Basil Bourque, i meant that the list of bots itself will be executed until the execution time ends. (And for that reason i`ve added synchonized block in Bot#run. I expected the behavior that while the bot is already busy with another thread, we will hang on the monitor, waiting until another thread makes changes to the bot itself. Commented Mar 17 at 13:59
  • Okay @Basil Bourque, unfortunately, solution (swap execute -> submit) doesn't work, race-condition still present. Could you say what it causes? (Maybe edited question will simplify understanding my problem) Commented Mar 17 at 22:24
  • 1
    Since the nested loops will send the same Bot instances to the executor service, it’s not correct to say that “each bot instance is being run exactly once in its own thread”. But the question’s sketch contains no effort to actually query or use the result, so no-one can say for sure whether there’s a race at that point (though it very likely is). Of course, replacing execute with submit does not change anything. It does not even attempt to address the question’s actual problem. Same for virtual threads. Why discuss performance, when the question is about thread safety?
    – Holger
    Commented Mar 20 at 16:19
  • Agree with you, @Holger. "each bot instance is being run exactly once in its own thread" not exactly describes my issue. I mean, each Bot should be thread-safety on nested loop iterations. Thats why i've tried use synchronized block on Bot#run method. (Any lock not works too) Commented Mar 20 at 16:28

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.