JavaRush /Java Blog /Random EN /Top 50 Java Core Interview Questions and Answers. Part 3

Top 50 Java Core Interview Questions and Answers. Part 3

Published in the Random EN group
Top 50 Java Core Interview Questions and Answers. Part 1 Top 50 Java Core Interview Questions and Answers. Part 2

Multithreading

37. How to create a new thread (flow) in Java?

One way or another, creation occurs through the use of the Thread class. But there may be options here...
  1. We inherit fromjava.lang.Thread
  2. We implement an interface java.lang.Runnablewhose object accepts a constructor Threadclass
Let's talk about each of them.

We inherit from the Thread class

To make this work, in our class we inherit from java.lang.Thread. It contains meth run(), which is exactly what we need. All the life and logic of the new thread will be in this method. This is a kind of mainmethod for a new thread. After this, all that remains is to create an object of our class and execute the method start(), which will create a new thread and run the logic written in it. Let's look:
/**
* Пример того, How создавать треды путем наследования {@link Thread} класса.
*/
class ThreadInheritance extends Thread {

   @Override
   public void run() {
       System.out.println(Thread.currentThread().getName());
   }

   public static void main(String[] args) {
       ThreadInheritance threadInheritance1 = new ThreadInheritance();
       ThreadInheritance threadInheritance2 = new ThreadInheritance();
       ThreadInheritance threadInheritance3 = new ThreadInheritance();
       threadInheritance1.start();
       threadInheritance2.start();
       threadInheritance3.start();
   }
}
The output to the console will be like this:

Thread-1
Thread-0
Thread-2
That is, even here we see that the threads are executed not in turn, but as the JVM decided)

Implementing the Runnable interface

If you are against inheritance and/or already inherit one of the other classes, you can use the java.lang.Runnable. Here in our class we implement this interface and implement the method run(), as it was in that example. You just need to create more objects Thread. It would seem that more lines are worse. But we know how harmful inheritance is and that it is better to avoid it by all means ;) Let's look:
/**
* Пример того, How создавать треды из интерфейса {@link Runnable}.
* Здесь проще простого - реализуем этот интерфейс и потом передаем в конструктор
* экземпляр реализуемого an object.
*/
class ThreadInheritance implements Runnable {

   @Override
   public void run() {
       System.out.println(Thread.currentThread().getName());
   }

   public static void main(String[] args) {
       ThreadInheritance runnable1 = new ThreadInheritance();
       ThreadInheritance runnable2 = new ThreadInheritance();
       ThreadInheritance runnable3 = new ThreadInheritance();

       Thread threadRunnable1 = new Thread(runnable1);
       Thread threadRunnable2 = new Thread(runnable2);
       Thread threadRunnable3 = new Thread(runnable3);

       threadRunnable1.start();
       threadRunnable2.start();
       threadRunnable3.start();
   }
}
And the execution result:

Thread-0
Thread-1
Thread-2

38. What is the difference between a process and a thread?

Top 50 Java Core Interview Questions and Answers.  Part 3 - 1There are the following differences between a process and a thread:
  1. A program in execution is called a process, while a Thread is a subset of a process.
  2. Processes are independent, whereas threads are a subset of a process.
  3. Processes have different address space in memory, while threads contain a common address space.
  4. Context switching is faster between threads compared to processes.
  5. Interprocess communication is slower and more expensive than interthread communication.
  6. Any changes in the parent process do not affect the child process, whereas changes in the parent thread can affect the child thread.

39. What are the advantages of multithreading?

Top 50 Java Core Interview Questions and Answers.  Part 3 - 2
  1. Multithreading allows an application/program to always respond to input even if it is already running some background tasks;
  2. Multithreading allows you to complete tasks faster because the threads execute independently;
  3. Multithreading provides better cache utilization because threads share common memory resources;
  4. Multithreading reduces the amount of server required because one server can run multiple threads simultaneously.

40. What are the states in the life cycle of a thread?

Top 50 Java Core Interview Questions and Answers.  Part 3 - 3
  1. New: In this state, a class object Threadis created using the new operator, but the thread does not exist. The thread doesn't start until we call the start().
  2. Runnable: In this state, the thread is ready to run after calling the method start(). However, it has not yet been selected by the thread scheduler.
  3. Running: In this state, the thread scheduler selects a thread from the ready state and it runs.
  4. Waiting/Blocked: In this state, the thread is not running but is still alive or waiting for another thread to complete.
  5. Dead/Terminated: When the method exits, run()the thread is in a terminated or dead state.

41. Is it possible to start a thread twice?

No, we cannot restart the thread because once the thread is started and executed, it goes into the Dead state. So if we try to run the thread twice, it will throw a runtimeException " java.lang.IllegalThreadStateException ". Let's look:
class DoubleStartThreadExample extends Thread {

   /**
    * Имитируем работу треда
    */
   public void run() {
	// что-то происходит. Для нас не существенно на этом этапе
   }

   /**
    * Запускаем тред дважды
    */
   public static void main(String[] args) {
       DoubleStartThreadExample doubleStartThreadExample = new DoubleStartThreadExample();
       doubleStartThreadExample.start();
       doubleStartThreadExample.start();
   }
}
As soon as the work reaches the second start of the same thread, then there will be an exception. Try it yourself ;) it’s better to see once than to hear a hundred times.

42. What if you call the run() method directly without calling the start() method?

Yes, run()of course you can call a method, but this will not create a new thread and execute it as a separate thread. In this case, it is a simple object that calls a simple method. If we are talking about the method start(), then it is a different matter. By launching this method, runtimeit launches a new one and it, in turn, runs our method ;) If you don’t believe me, try it:
class ThreadCallRunExample extends Thread {

   public void run() {
       for (int i = 0; i < 5; i++) {
           System.out.print(i);
       }
   }

   public static void main(String args[]) {
       ThreadCallRunExample runExample1 = new ThreadCallRunExample();
       ThreadCallRunExample runExample2 = new ThreadCallRunExample();

       // просто будут вызваны в потоке main два метода, один за другим.
       runExample1.run();
       runExample2.run();
   }
}
And the output to the console will be like this:

0123401234
It can be seen that no thread was created. Everything worked like a normal class. First the first class method worked, then the second.

43. What is a daemon thread?

Top 50 Java Core Interview Questions and Answers.  Part 3 - 4Daemon thread (hereinafter referred to as daemon thread) is a thread that performs tasks in the background in relation to another thread. That is, its job is to perform auxiliary tasks that need to be done only in conjunction with another (main) thread. There are many daemon threads that work automatically, such as Garbage Collector, finalizer, etc.

Why does Java close the daemon thread?

The only purpose of a daemon thread is that it provides services to the user thread for background support task. Therefore, if the main thread has completed, then runtime automatically closes all its daemon threads.

Methods for working in the Thread class

The class java.lang.Threadprovides two methods for working with the thread daemon:
  1. public void setDaemon(boolean status)- indicates that this will be a daemon thread. The default is false, which means that non-daemon threads will be created unless specified separately.
  2. public boolean isDaemon()- essentially this is a getter for the variable daemonthat we set using the previous method.
Example:
class DaemonThreadExample extends Thread {

   public void run() {
       // Проверяет, демон ли этот поток or нет
       if (Thread.currentThread().isDaemon()) {
           System.out.println("daemon thread");
       } else {
           System.out.println("user thread");
       }
   }

   public static void main(String[] args) {
       DaemonThreadExample thread1 = new DaemonThreadExample();
       DaemonThreadExample thread2 = new DaemonThreadExample();
       DaemonThreadExample thread3 = new DaemonThreadExample();

       // теперь thread1 - поток-демон.
       thread1.setDaemon(true);

       System.out.println("демон?.. " + thread1.isDaemon());
       System.out.println("демон?.. " + thread2.isDaemon());
       System.out.println("демон?.. " + thread3.isDaemon());

       thread1.start();
       thread2.start();
       thread3.start();
   }
}
Console output:

демон?.. true
демон?.. false
демон?.. false
daemon thread
user thread
user thread
From the output we see that inside the thread itself, using a static currentThread()method, we can find out which thread it is on the one hand, on the other hand, if we have a reference to the object of this thread, we can find out directly from it. This gives the necessary flexibility in configuration.

44. Is it possible to make a thread a daemon after it has been created?

No. If you do this it will throw an exception IllegalThreadStateException. Therefore, we can only create a daemon thread before it starts. Example:
class SetDaemonAfterStartExample extends Thread {

   public void run() {
       System.out.println("Working...");
   }

   public static void main(String[] args) {
       SetDaemonAfterStartExample afterStartExample = new SetDaemonAfterStartExample();
       afterStartExample.start();

       // здесь будет выброшено исключение
       afterStartExample.setDaemon(true);
   }
}
Console output:

Working...
Exception in thread "main" java.lang.IllegalThreadStateException
	at java.lang.Thread.setDaemon(Thread.java:1359)
	at SetDaemonAfterStartExample.main(SetDaemonAfterStartExample.java:14)

45. What is a shutdownhook?

Shutdownhook is a thread that is implicitly called before the JVM (Java Virtual Machine) shuts down. So we can use it to clean up a resource or save state when the Java Virtual Machine shuts down normally or suddenly. We can add shutdown hookusing the following method:
Runtime.getRuntime().addShutdownHook(new ShutdownHookThreadExample());
As shown in the example:
/**
* Программа, которая показывает How запустить shutdown hook тред,
* который выполнится аккурат до окончания работы JVM
*/
class ShutdownHookThreadExample extends Thread {

   public void run() {
       System.out.println("shutdown hook задачу выполнил");
   }

   public static void main(String[] args) {

       Runtime.getRuntime().addShutdownHook(new ShutdownHookThreadExample());

       System.out.println("Теперь программа засыпает, нажмите ctrl+c чтоб завершить ее.");
       try {
           Thread.sleep(60000);
       } catch (InterruptedException e) {
           e.printStackTrace();
       }
   }
}
Console output:

Теперь программа засыпает, нажмите ctrl+c чтоб завершить ее.
shutdown hook задачу выполнил

46. ​​What is synchronization?

Synchronization in Java is the ability to control multiple threads' access to any shared resource. When multiple threads try to perform the same task, there is a possibility of an erroneous result, so to overcome this problem, Java uses synchronization, due to which only one thread can work at a time. Synchronization can be achieved in three ways:
  • Synchronizing Method
  • By synchronizing a specific block
  • Static synchronization

Method synchronization

The synchronized method is used to lock an object for any shared resource. When a thread calls a synchronized method, it automatically acquires a lock on that object and releases it when the thread completes its task. To make it work, you need to add the synchronized keyword . Let's see how this works with an example:
/**
* Пример, где мы синхронизируем метод. То есть добавляем ему слово synchronized.
* Есть два писателя, которые хотят использовать один принтер. Они подготовor свои поэмы
* И конечно же не хотят, чтоб их поэмы перемешались, а хотят, чтоб работа была сделана по * * * очереди для каждого из них
*/
class Printer {

   synchronized void print(List<String> wordsToPrint) {
       wordsToPrint.forEach(System.out::print);
       System.out.println();
   }

   public static void main(String args[]) {
       // один an object для двух тредов
       Printer printer  = new Printer();

       // создаем два треда
       Writer1 writer1 = new Writer1(printer);
       Writer2 writer2 = new Writer2(printer);

       // запускаем их
       writer1.start();
       writer2.start();
   }
}

/**
* Писатель номер 1, который пишет свою поэму.
*/
class Writer1 extends Thread {
   Printer printer;

   Writer1(Printer printer) {
       this.printer = printer;
   }

   public void run() {
       List<string> poem = Arrays.asList("Я ", this.getName(), " Пишу", " Письмо");
       printer.print(poem);
   }

}

/**
* Писатель номер 2, который пишет свою поэму.
*/
class Writer2 extends Thread {
   Printer printer;

   Writer2(Printer printer) {
       this.printer = printer;
   }

   public void run() {
       List<String> poem = Arrays.asList("Не Я ", this.getName(), " Не пишу", " Не Письмо");
       printer.print(poem);
   }
}
And the output to the console:

Я Thread-0 Пишу Письмо
Не Я Thread-1 Не пишу Не Письмо

Synchronization block

A synchronized block can be used to perform synchronization on any specific method resource. Let’s say that in a large method (yes, yes, you can’t write such things, but sometimes it happens) you need to synchronize only a small part, for some reason. If you put all the codes of a method in a synchronized block, it will work the same as a synchronized method. The syntax looks like this:
synchronized (“an object для блокировки”) {
   // сам code, который нужно защитить
}
In order not to repeat the previous example, we will create threads through anonymous classes - that is, immediately implementing the Runnable interface.
/**
* Вот How добавляется блок синхронизации.
* Внутри нужно указать у кого будет взят мьютекс для блокировки.
*/
class Printer {

   void print(List<String> wordsToPrint) {
       synchronized (this) {
           wordsToPrint.forEach(System.out::print);
       }
       System.out.println();
   }

   public static void main(String args[]) {
       // один an object для двух тредов
       Printer printer = new Printer();

       // создаем два треда
       Thread writer1 = new Thread(new Runnable() {
           @Override
           public void run() {
               List<String> poem = Arrays.asList("Я ", "Writer1", " Пишу", " Письмо");
               printer.print(poem);
           }
       });
       Thread writer2 = new Thread(new Runnable() {
           @Override
           public void run() {
               List<String> poem = Arrays.asList("Не Я ", "Writer2", " Не пишу", " Не Письмо");
               printer.print(poem);
           }
       });

       // запускаем их
       writer1.start();
       writer2.start();
   }
}

}
and output to the console

Я Writer1 Пишу Письмо
Не Я Writer2 Не пишу Не Письмо

Static synchronization

If you make a static method synchronized, the lock will be on the class, not on the object. In this example, we apply the synchronized keyword to a static method to perform static synchronization:
/**
* Вот How добавляется блок синхронизации.
* Внутри нужно указать у кого будет взят мьютекс для блокировки.
*/
class Printer {

   static synchronized void print(List<String> wordsToPrint) {
       wordsToPrint.forEach(System.out::print);
       System.out.println();
   }

   public static void main(String args[]) {

       // создаем два треда
       Thread writer1 = new Thread(new Runnable() {
           @Override
           public void run() {
               List<String> poem = Arrays.asList("Я ", "Writer1", " Пишу", " Письмо");
               Printer.print(poem);
           }
       });
       Thread writer2 = new Thread(new Runnable() {
           @Override
           public void run() {
               List<String> poem = Arrays.asList("Не Я ", "Writer2", " Не пишу", " Не Письмо");
               Printer.print(poem);
           }
       });

       // запускаем их
       writer1.start();
       writer2.start();
   }
}
and the output to the console:

Не Я Writer2 Не пишу Не Письмо
Я Writer1 Пишу Письмо

47. What is a volatile variable?

The keyword volatileis used in multi-threaded programming to provide thread safety because a modification to one mutable variable is visible to all other threads, so one variable can be used by one thread at a time. Using the keyword, volatileyou can guarantee that the variable will be thread-safe and will be stored in shared memory, and threads will not take it into their cache. What does it look like?
private volatile AtomicInteger count;
We just add to the variable volatile. But this does not mean complete thread safety... After all, operations may not be atomic on a variable. But you can use Atomicclasses that perform the operation atomically, that is, in one execution by the processor. Many such classes can be found in the package java.util.concurrent.atomic.

48. What is deadlock

Deadlock in Java is part of multithreading. A deadlock can occur in a situation where a thread is waiting on an object lock acquired by another thread, and a second thread is waiting on an object lock acquired by the first thread. Thus, these two threads wait for each other and will not continue executing their code. Top 50 Java Core Interview Questions and Answers.  Part 3 - 5Let's look at an Example in which there is a class that implements Runnable. It accepts two resources in its constructor. Inside the run() method, it takes the lock for them one by one, so if you create two objects of this class and transfer the resources in different orders, you can easily run into a lock:
class DeadLock {

   public static void main(String[] args) {
       final Integer r1 = 10;
       final Integer r2 = 15;

       DeadlockThread threadR1R2 = new DeadlockThread(r1, r2);
       DeadlockThread threadR2R1 = new DeadlockThread(r2, r1);

       new Thread(threadR1R2).start();
       new Thread(threadR2R1).start();
   }
}

/**
* Класс, который принимает два ресурса.
*/
class DeadlockThread implements Runnable {

   private final Integer r1;
   private final Integer r2;

   public DeadlockThread(Integer r1, Integer r2) {
       this.r1 = r1;
       this.r2 = r2;
   }

   @Override
   public void run() {
       synchronized (r1) {
           System.out.println(Thread.currentThread().getName() + " захватил ресурс: " + r1);

           try {
               Thread.sleep(1000);
           } catch (InterruptedException e) {
               e.printStackTrace();
           }

           synchronized (r2) {
               System.out.println(Thread.currentThread().getName() + " захватил ресурс: " + r2);
           }
       }
   }
}
Console output:

Первый тред захватил первый ресурс
Второй тред захватывает второй ресурс

49. How to avoid deadlock?

Based on what we know how a deadlock occurs, we can draw some conclusions...
  • As shown in the example above, the deadlock was due to the nesting of locks. That is, inside one lock there is another or more. You can avoid this in the following way - instead of nesting, you need to add a new abstraction on top and give the lock to a higher level, and remove nested locks.
  • The more blocking there is, the greater the chance that there will be a deadlock. Therefore, every time you add a lock, you need to think about whether it is really needed and whether adding a new one can be avoided.
  • Uses Thread.join(). A deadlock can also be done when one thread is waiting for another. To avoid this problem, you might consider setting a time limit on join()the method.
  • If we have one thread, there will be no deadlock ;)

50. What is a race condition?

If in real races the cars perform, then in the racing terminology of multi-threading, threads perform in the races. But why? There are two threads that are running and that can have access to the same object. And they can try to update the state at the same time. So far everything is clear, right? So the threads work either in real parallel (if there is more than one core in the processor) or conditionally in parallel, when the processor allocates a short period of time. And we cannot control these processes, so we cannot guarantee that when one thread reads data from an object, it will have time to change it BEFORE some other thread does it. Problems like this happen when this test-and-act combination goes through. What does it mean? For example, we have ifan expression in the body of which the condition itself changes, that is:
int z = 0;

// проверь
if (z < 5) {
//действуй
   z = z + 5;
}
So there may be a situation when two threads simultaneously enter this block of code at a time when z is still equal to zero and together they change this value. And in the end we will not get the expected value of 5, but 10. How to avoid this? You need to lock before and after execution. That is, for the first thread to enter the block if, perform all the actions, change it, zand only then give the next thread the opportunity to do this. But the next thread will not enter the block if, since zit will already be equal to 5:
// получить блокировку для z
if (z < 5) {
   z = z + 5;
}
// выпустить из блокировки z
===================================================

Instead of output

I want to say thank you to everyone who read to the end. It was a long journey and you made it! Not everything may be clear. This is fine. As soon as I started learning Java, I couldn’t wrap my head around what a static variable was. But nothing, I slept with this thought, read a few more sources and finally understood. Preparing for an interview is more of an academic matter than a practical one. Therefore, before each interview, you need to repeat and refresh your memory of things that you may not use very often.

And as always, useful links:

Thank you all for reading, See you soon) My profile on GitHub
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION