Garvit Arora
Garvit Arora

Reputation: 135

Thread.sleep() stopping all threads

As per my understanding if there are multiple threads executing in parallel, Thread.sleep() will only sleep the thread in which it is called. However in my case Thread.sleep is making other threads sleep/wait. Is this expected behavior or am i doing something incorrect. Find below my code

package stackoverflow;


public class SO {
    public static void main(String[] args){
        CallMe callMe = new CallMe();
        Producer producer = new Producer(callMe);
        Consumer consumer = new Consumer(callMe);
        producer.thread.start();
        consumer.thread.start();

    }

}

class CallMe{
    int a;
    synchronized public int get(){
        System.out.println("Get "+a);
        return a;
    }
    synchronized public void put(int a){
        System.out.println("Put "+a);
        this.a=a;
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

class Producer implements Runnable{
    Thread thread;
    CallMe callMe;
    Producer(CallMe callMe){
        thread=new Thread(this);
        this.callMe=callMe;
    }
    public void run(){
        int i=0;
        while(true){
            callMe.put(i);
            i++;
        }
    }
}

class Consumer implements Runnable{
    Thread thread;
    CallMe callMe;
    Consumer(CallMe callMe){
        thread = new Thread(this);
        this.callMe=callMe;
    }
    public void run(){
        while (true) {
            callMe.get();
        }
    }
}

While the put thread is put to sleep, the get should keep printing the output right? I am not giving any priority to any of the threads so if one thread is sleeping other should start executing. Isn't it?

I tried giving large value of sleep (100000000 millis) still console only shows Put 0 not printing Get <X> at all.

Upvotes: 0

Views: 1401

Answers (1)

Basil Bourque
Basil Bourque

Reputation: 338654

You have synchronized on both methods of your CallMe class. There is only one lock on which to synchronize for each object of that class. And you produce a singleton of that class. So one object with one lock being called via either of two methods across two threads.

Within one of these methods you sleep. During that sleep, you are holding the lock. The consumer thread calls the get method while the producer is holding the lock. The get method blocks, waiting for the lock, stopping the thread of the consumer. Eventually the producer thread wakes, releases the lock. The consumer thread grabs the lock, and proceeds.

This behavior is shown on the tutorial by Oracle.

Be sure to study the classic book by Brian Goetz, et al., Java Concurrency in Practice.


You have another issue as well.

You are sharing a resource, the int member field of CallMe, across two threads. Your code there is subject to visibility problems per the Java Memory Model. Because of modern CPU architecture, two threads accessing the same primitive value or object reference may see two different cached values. One solution is use of volatile keyword. I prefer the alternative, using the Atomic… classes.

I would replace that that with a final member field of type AtomicInteger.

Using AtomicInteger would also allow you to eliminate the synchronized tags, thereby eliminating your frozen-thread bottleneck.

So, killing two birds with one stone, we solve both your contention problem and your visibility problem by using AtomicInteger.


I suggest you learn about using executor service rather than addressing the Thread class directly.

To run a task repeatedly, use a scheduled executor service rather than sleeping.


Putting all that advice together, we might get code that looks like this.

package work.basil.example.prodcon;

import java.time.Instant;
import java.util.concurrent.atomic.AtomicInteger;

public class CountManager
{
    final AtomicInteger counter;

    // Constructor
    public CountManager ( )
    {
        this.counter = new AtomicInteger();
        System.out.println( "CountManager constructor initialized the count to = " + this.counter.get() + " at " + Instant.now() );
    }

    public int getCount ( )
    {
        return this.counter.get();
    }

    public int increment ( )
    {
        return this.counter.incrementAndGet();
    }
}
package work.basil.example.prodcon;

import java.time.Instant;

public class Producer implements Runnable
{
    final private CountManager countManager;

    public Producer ( CountManager countManager )
    {
        this.countManager = countManager;
    }

    @Override
    public void run ( )
    {
        int newCount = this.countManager.increment();
        System.out.println( "The producer in thread # " + Thread.currentThread().getId()+ " incremented newCount = " + newCount + " at " + Instant.now() );
    }
}
package work.basil.example.prodcon;

import java.time.Instant;

public class Consumer implements Runnable
{
    final private CountManager countManager;

    public Consumer ( CountManager countManager )
    {
        this.countManager = countManager;
    }

    @Override
    public void run ( )
    {
        int currentCount = countManager.getCount();
        System.out.println( "The consumer on thread # " + Thread.currentThread().getId() + " reports currentCount = " + currentCount + " at " + Instant.now() );
    }
}

Run that code in an app.

package work.basil.example.prodcon;

import java.time.Duration;
import java.time.Instant;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class App
{
    public static void main ( String[] args )
    {
        App app = new App();
        app.demo();
    }

    private void demo ( )
    {
        System.out.println( "The app began demo on thread # " + Thread.currentThread().getId() + " at " + Instant.now() );

        // Setup.
        CountManager countManager = new CountManager();
        Producer producer = new Producer( countManager );
        Consumer consumer = new Consumer( countManager );

        // Schedule some work to be done on background threads.
        ScheduledExecutorService ses = Executors.newSingleThreadScheduledExecutor();
        ses.scheduleAtFixedRate( producer , 0 , 7 , TimeUnit.SECONDS );
        ses.scheduleAtFixedRate( consumer , 0 , 3 , TimeUnit.SECONDS );

        // Let the app run a while.
        try { Thread.sleep( Duration.ofMinutes( 1 ).toMillis() ); } catch ( InterruptedException e ) {e.printStackTrace(); }
        ses.shutdown();

        System.out.println( "The app ended demo on thread # " + Thread.currentThread().getId() + " at " + Instant.now() );
    }
}

When run:

The app began demo on thread # 1 at 2021-02-14T02:10:09.981741Z
CountManager constructor initialized the count to = 0 at 2021-02-14T02:10:10.003056Z
The producer in thread # 14 incremented newCount = 1 at 2021-02-14T02:10:10.014164Z
The consumer on thread # 14 reports currentCount = 1 at 2021-02-14T02:10:10.026975Z
The consumer on thread # 14 reports currentCount = 1 at 2021-02-14T02:10:13.019270Z
The consumer on thread # 14 reports currentCount = 1 at 2021-02-14T02:10:16.016694Z
The producer in thread # 14 incremented newCount = 2 at 2021-02-14T02:10:17.016730Z
The consumer on thread # 14 reports currentCount = 2 at 2021-02-14T02:10:19.017666Z
The consumer on thread # 14 reports currentCount = 2 at 2021-02-14T02:10:22.017923Z
The producer in thread # 14 incremented newCount = 3 at 2021-02-14T02:10:24.015972Z
The consumer on thread # 14 reports currentCount = 3 at 2021-02-14T02:10:25.018212Z
The consumer on thread # 14 reports currentCount = 3 at 2021-02-14T02:10:28.048834Z
The producer in thread # 14 incremented newCount = 4 at 2021-02-14T02:10:31.016746Z
The consumer on thread # 14 reports currentCount = 4 at 2021-02-14T02:10:31.016944Z
The consumer on thread # 14 reports currentCount = 4 at 2021-02-14T02:10:34.016674Z
The consumer on thread # 14 reports currentCount = 4 at 2021-02-14T02:10:37.017195Z
The producer in thread # 14 incremented newCount = 5 at 2021-02-14T02:10:38.016650Z
The consumer on thread # 14 reports currentCount = 5 at 2021-02-14T02:10:40.017862Z
The consumer on thread # 14 reports currentCount = 5 at 2021-02-14T02:10:43.016564Z
The producer in thread # 14 incremented newCount = 6 at 2021-02-14T02:10:45.017760Z
The consumer on thread # 14 reports currentCount = 6 at 2021-02-14T02:10:46.016909Z
The consumer on thread # 14 reports currentCount = 6 at 2021-02-14T02:10:49.014339Z
The producer in thread # 14 incremented newCount = 7 at 2021-02-14T02:10:52.013271Z
The consumer on thread # 14 reports currentCount = 7 at 2021-02-14T02:10:52.013389Z
The consumer on thread # 14 reports currentCount = 7 at 2021-02-14T02:10:55.017526Z
The consumer on thread # 14 reports currentCount = 7 at 2021-02-14T02:10:58.017050Z
The producer in thread # 14 incremented newCount = 8 at 2021-02-14T02:10:59.014334Z
The consumer on thread # 14 reports currentCount = 8 at 2021-02-14T02:11:01.013358Z
The consumer on thread # 14 reports currentCount = 8 at 2021-02-14T02:11:04.014889Z
The producer in thread # 14 incremented newCount = 9 at 2021-02-14T02:11:06.015598Z
The consumer on thread # 14 reports currentCount = 9 at 2021-02-14T02:11:07.014802Z
The consumer on thread # 14 reports currentCount = 9 at 2021-02-14T02:11:10.017313Z
The app ended demo on thread # 1 at 2021-02-14T02:11:10.017975Z

Upvotes: 2

Related Questions