How will you go about explaining the following Java concepts to a beginner?
Q. How will you go about explaining the following Java concepts to a beginner who is starting to learn Java
A.
1. Process Vs Threads
2. Heap versus Stack
3. Local variables versus instance variables
4. How do threads communicate with each other?
5. Are Java methods reentrant?
6. Does Java support recursive method calls?
7. Object creation and Garbage collection
8. Can you garbage collect objects that have a circular reference?
9. Producer-consumer design pattern.
The best way to do this is to write some basic code and then have a pictorial representation as to how the objects are created, how do threads work and communicate with each other, and what is stored in a heap and what is stored in a stack, etc.
Here is an example of a producer thread (thread-0) producing by incrementing the counter from 0, and the consumer thread (i.e. thread-1) consumes by decrementng the counter. These two user created threads are spawned by the main thread, which is created by the JVM and is alwyas there by default. The ConsumerProducer is the shared object with synchronized methods that commuincate with each other via wait( ) and notifyAll methods. Only one of the two synchronized methods can be executed at a time. The wait( ) call in consume( ) relinquishes the lock to the produce( ) method, and once the produce method has incremented the count, it notifies all threads and one of the waiting threads will resume. In this example, there is only one waiting consumer (i.e. Thread-1) thread. So, both threads will be communicating with each other via the wait( ) and notifyAll( ) calls in the shared object ConsumerProducer. This is an example of the producer-consumer design pattern.
Firstly, look at the code and then the diagram. The diagram is simplified to get an understanding and should not be construed as exactly what happens in the JVM.
public class ConsumerProducer { private int count; public synchronized void consume() { while (count == 0) { // keep waiting if nothing is produced to consume try { wait(); // give up lock and wait } catch (InterruptedException e) { // keep trying } } count--; // consume System.out.println(Thread.currentThread().getName() + " after consuming " + count); } public synchronized void produce() { count++; //produce System.out.println(Thread.currentThread().getName() + " after producing " + count); notifyAll(); // notify waiting threads to resume } }
The main thread spawns a consumer and a producer thread. The ConsumerProducer is shared between two threads. The boolean flag is used to signal if it is a consumer thread or a producer thread to invoke the relevant methods.
public class ConsumerProducerTest implements Runnable { boolean isConsumer; ConsumerProducer cp; public ConsumerProducerTest(boolean isConsumer, ConsumerProducer cp) { this.isConsumer = isConsumer; this.cp = cp; } public static void main(String[] args) { ConsumerProducer cp = new ConsumerProducer(); //shared by both threads to communicate Thread producer = new Thread(new ConsumerProducerTest(false, cp)); Thread consumer = new Thread(new ConsumerProducerTest(true, cp)); producer.start(); consumer.start(); } @Override public void run() { for (int i = 1; i <= 10; i++) { if (!isConsumer) { cp.produce(); } else { cp.consume(); } } //try with introducing a sleep for 100ms. } }The output will vary, but the last thing consumed will be 0
Thread-0 after producing 1 Thread-0 after producing 2 Thread-0 after producing 3 Thread-0 after producing 4 Thread-0 after producing 5 Thread-0 after producing 6 Thread-0 after producing 7 Thread-0 after producing 8 Thread-0 after producing 9 Thread-0 after producing 10 Thread-1 after consuming 9 Thread-1 after consuming 8 Thread-1 after consuming 7 Thread-1 after consuming 6 Thread-1 after consuming 5 Thread-1 after consuming 4 Thread-1 after consuming 3 Thread-1 after consuming 2 Thread-1 after consuming 1 Thread-1 after consuming 0
The above code can be diagramatically represented as shown below.
- The JVM is a process. As you could see in the diagram above that each thread has its own stack, but share the same heap space. One heap space per process.
- The local variables like producer, cp, and consumer are stored in the stack along with the method calls like main( ), run( ), etc.
- The objects are created in the heap. For example, 2 instances of ConsumerProducerTest and a single instance of ConsumerProducer. Unless the methods of these instances are properly managed, multiple threads can concurrently access these intsnaces to cause thread-safety issues.
- There are scenarios where the threads want to commuincate with each other. For example, a worker thread produces something and a consumer thread consumes what the producer thread consumed. This can be achieved through thread coordination with the wait and notify( ) / notifyAll( ) methods. In the above example, since both the produce( ) and consume( ) methods of the object ConsumerProducer are synchronized, only one thread can execute either one of the methods. If the count == 0, the consumer thread relinquishes the lock by invoking the wait( ) method and waits for it to be notified again to resume. The producer thread will increment the counter and then will notify all the waiting threads via the notifyAll( ) method. One of the waiting threads will then be able to resume from its waiting state.
- The local variable isConsumer is used to differentiate between a consumer thread and a producer thread.
- The count is an instance variable shared by both the consumer and producer thread in a thread-safe manner.
Here are the points that answers the above questions.
- Each time an object is created in Java it goes into the area of memory known as heap.
- The primitive variables like int, double, etc are allocated in the stack if they are local variables and in the heap if they are instance variables (i.e. fields of a class).
- Java is a stack based language and local variables are pushed into stack when a method is invoked and stack pointer is decremented when a method call is completed.
- In a multi-threaded application, each thread will have its own stack but will share the same heap. This is why care should be taken in your code to avoid any concurrent access issues in the heap space.
- The stack is thread-safe because each thread will have its own stack with say 1MB RAM allocated for each thread but the heap is not thread-safe unless guarded with synchronization through your code. The stack space can be increased with the –Xss option.
- All Java methods are automatically re-entrant. It means that several threads can be executing the same method at once, each with its own copy of the local variables.
- A Java method may call itself without needing any special declarations. This is known as a recursive method call. Given enough stack space, recursive method calls are perfectly valid in Java though it is tough to debug. Recursive methods are useful in removing iterations from many sorts of algorithms. All recursive functions are re-entrant but not all re-entrant functions are recursive.
- Idempotent methods are methods, which are written in such a way that repeated calls to the same method with the same arguments yield same results.
- Each time an object is created in Java, it goes into the area of memory known as heap. The Java heap is called the garbage collectable heap. The garbage collection cannot be forced. The garbage collector runs in low memory situations. When it runs, it releases the memory allocated by an unreachable object. The garbage collector runs on a low priority daemon (i.e. background thread). You can nicely ask the garbage collector to collect garbage by calling System.gc( ) but you can’t force it.
- An object’s life has no meaning unless something has reference to it. If you can’t reach it then you can’t ask it to do anything. Then the object becomes unreachable and the garbage collector will figure it out. Java automatically collects all the unreachable objects periodically and releases the memory consumed by those unreachable objects to be used by the future reachable objects.
- If two objects have circular reference with each other in the heap, but none of them are reachable from any thread, then those circular referenced objects can be garbage collected.
Labels: Core Java
19 Comments:
thanks for posting this article...
thanks for sharing this article. I really like your way of explaining things in this manner . Since I'm new to java so really enjoyed reading your article.
Thanks. I believe in the quote :)
“If you can’t explain it to a six year old, you don’t understand it yourself.”
- Albert Einstein
gud article...
Great thanks Arul and Nice your articles. I am much impressed and intersted. Keep doing the same...
Great indeed!!
Either i have some experience with OOP, especially Java, you make it very clear as theoretical background. Congrats for that
It's really gud!! Thanks for posting!!
veri nice article.
Very nice explanation Arul..i really like all your article.keep posting..Thanks for your effort and time.
Very good article.
Good quote...Excellent sir.
Thanks man. Appreciate it. I came here just to cover interview questions but learned a few new things.
Anup
very very nice tutorial
Java Tutorial
Hi Arul,
Thanks for the gr8 explanation.
I would like to ask one question, say with the same program if i try to use Multiple Consumer and single Producer then how would i exit the program gracefully?
Use the the ExecutorFramework that allows you to write a shutdown hook.
thanks a lot sir for your java rich article. i have a problem, i don't know when to and how to use executors,executor services, thread pools. please sir i would be thankful if you use your example to show when and how we can use them.
thanks in advance.
good quote............
thaths trueee
Post a Comment
Subscribe to Post Comments [Atom]
<< Home