Skip to content

Instantly share code, notes, and snippets.

@sats17
Last active December 23, 2024 06:09
Show Gist options
  • Save sats17/1d9d8722332b430fd03a22277c58ea87 to your computer and use it in GitHub Desktop.
Save sats17/1d9d8722332b430fd03a22277c58ea87 to your computer and use it in GitHub Desktop.
Gist contains information about core java things.

public: Visible to all classes and packages. protected: Visible to subclasses and classes in the same package. default (package-private): Visible only to classes in the same package. private: Visible only within the same class.

===========================================================================================================================

===========================================================================================================================

                            All about core java

===========================================================================================================================

===========================================================================================================================

  1. Factory design pattern - It is a creational type design pattern where we are hiding implementation of object creations and we are providing abstraction factory on top of the object creations which will create objects for clients. Here object creation logic is hidden from client. Example 1 -

Interface Notification Class PushNotification implements Notification Class SMSNotification implements Notification Class MailNotification implements Notification

Class NotificationFactory { if String == "PUSH" then create object of PushNotification if String == "MAIL" then create object of MailNotification if String == "SMS" then create object of SMSNotification else Error }

Class Client { new NotificationFactory("SMS") }

Example 2 -

Interface Excersize Class UpperBody implements Excersize Class LowerBody implements Excersize Class Cardio implements Excersize

Class ExcersizeFactory { if String == "UpperBody" then create object of UpperBody if String == "LowerBody" then create object of LowerBody if String == "Cardio" then create object of Cardio else "Get out" }

Class Gym { new ExcersizeFactory("UpperBody") }

Ref link = https://www.geeksforgeeks.org/factory-method-design-pattern-in-java/ Note: Some of the design pattern used to create spring frameworks - https://www.baeldung.com/spring-framework-design-patterns Design pattern everyone uses according to their need, spring uses factory design pattern and singleton design pattern combination to create a Objects container and further operations.

  1. Abstract factory pattern = Abstract factory pattern is provide abstraction layer using that layer client can create factories. Later on using those factories they can get objects(Same like factory method). It is design pattern where we are hiding factories implementation.

Interface UpperBodyExcersize BackBiceps implements UpperBodyExcersize ChestShoulderTriceps implements UpperBodyExcersize

Interface LowerBodyExcersize LowerLegs implements LowerBodyExcersize UpperLegs implements LowerBodyExcersize

Interface Excersize UpperBodyFactory implements Excersize LowerBodyFactory implements Excersize //CardioClass implements Excersize

Class Gym { method static getExcersizes('type') { if String == UpperBody return new UpperBodyFactory if String == LowerBody return new LowerBodyFactory if String == Cardio return new CardiClass } }

Class client { main() { // Today is my day to perform backbiceps, So I will ask trainer to give my schedule Excersize upperBodyExcersize = Gym.getExcersizes('UpperBody'); UpperBodyExcersize backbiceps = upperBodyExcersize.getBackBiceps(); backbiceps.start() } }

So using this abstractions layers we are here abstracted all excersize factories by single excersize factory.

  1. Singleton design pattern = Singleton design pattern is another creation design pattern where we create only single object of any class, and we reused that object throughout program. It is widely used in spring, spring boot and whenevver we want to create DB connections then we can use this.

Example - Class Singleton{ private static Singelton instance = null; private Singleton(){} // private constructor

public static Singelton getInstance() {
    if (instance == null)
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        return instance;
} // end of getInstance()

} //end of Singleton class

  1. Prototype Design pattern = Prototype design pattern is another object creation design pattern. In this design pattern we are cloning objects instead of creating object newly. By cloning means we are using cloneable interface which allows us to get clone method, then we are returning new object from that clone method with copying all data from previous object. Example you can say. let's say your object needs to do bulk load from database and object 1 is doing bulk load for you. but for object 2 you don't need to connect database again, you can simply copy data from object 1 to object 2. By doing this we are saving lot of time for object 2 creation. So this is called prototype design pattern. Ref - https://www.youtube.com/watch?v=nZ76x13Nm8Q Example - https://github.com/sats17/java-assignments/blob/main/basic-assignments/src/main/java/com/github/sats17/PrototypeClass.java

  2. Builder design pattern - In builder design pattern when you are creating object having lot of variables that you need to pass through contructors or setters. Usually those variables are optionals, so you might end up with lot of constructors with diffrent permutation and combination. Or else you might use setters here. Problem with constructors is that first there will be lot of constructors will be created and second you need to remember for those values in constructor. Problem with setter is that from whole code point of view you can set those variables from anywhere, which leads to inconsistent and you need to watch thoroughly. Other is if you want immutable object then you will end up using constructor only and not setter which can lead to problem.

Where we wil use this type of big chain object - Pizza, where pizza creates according user needs.

Solution is using builder design pattern, where you will build object accordingly and client will just pass values to you. Using this your object can be immutable. Builder design pattern is type of lazy initialization, where all values will be get collected and when build() method called then only object gets created. In builder pattern we have another class which helps us to build our required object.

Example - https://github.com/sats17/java-assignments/blob/main/basic-assignments/src/main/java/com/github/sats17/BuilderDesignPattern.java

  1. Object pool pattern - This pattern is where objects are stored in pool and will be fetched from pool and once his use is complete then, it will put back into pool. Example - ThreadPool, DBConnectionPool

  2. Template design pattern - In mcd domain gateway application template design pattern was used. As it have abstract class contains single implemented method which contains abstract methods in order. Like domain gw was middleware application hence it has common methods like validateRequest, modifyRequst, modifyResponse etc.

===================== Java Only ================================== Checked vs unchecked exception - All Exceptions are part of run time and not compile time. There are two kinds of exceptions checked exceptions and unchecked exceptions. Examples of checked exceptions are IO Exceptions, ClassNotFound Exception and examples of unchecked exceptions are runtime exceptions. In the case of checked exceptions, error or warning message comes at compile time so that the user will take care of it at runtime by using throws keyword, which is used to throw exceptions to the default catch mechanism system. But in case of unchecked exceptions warning is not there at compile time.

Unchecked exceptions are those that extend RuntimeException class. Compiler will never force you to catch such exception or force you to declare it in the method using throws keyword. All other exception types (that do not extend RuntimeException) are checked and therefore must be declared to be thrown and/or catched.

Ref - https://www.infoworld.com/article/3649089/how-to-handle-java-errors-and-cleanup-without-finalize.html

Java deprecated finalize() method. What is use of finalize() method -> 1) The basic idea is to allow you to define a method on your objects that will execute when the object is ready for garbage collection. Technically, an object is ready for garbage collection when it becomes phantom reachable, meaning no strong or weak references are left in the JVM. 2) So we can add logic in finalize method to close the resources, such as IO stream or file reader.

Main problem was with finalize is that we don't know when JVM will trigger this method as JVM gc works with different algorithm. So adding any buisness logic in finalize method was not good. Also, java deprecated this method asked not to use. Another problem is that finalizers can run on any thread, introducing error conditions that are very hard to debug, just like any other concurrency issue. If such issues arise in production, they are hard to reproduce and debug.

Alternatives are try with resources, where any class that implements java.lang.AutoCloseable can be supplied to try-with-resource.

  1. Garbage Collector in java inbuild functionality and run automatically to free un-referenced objects from heap.
  2. Those objects which are not referenced with any variable from stack and subsequent reference(stack -> Object1 -> Object2) are eligible for garbage collections.
  3. What garbage collection do, it scans all the heap memory and collect objects those are referenced with stack. And it not consider other objects those are unreferened. https://www.geeksforgeeks.org/mark-and-sweep-garbage-collection-algorithm/
  4. there is java.lang.gc() method tell jvm to start collecting unreachable objects. It just notify jvm that collect objects, jvm will do these things by it's own. gc() will not gurantee garbage will be collected within this time period.
  5. java.lang.Object.finalize() method just call before object will be collected by garbage collector.

Heap Memory - Heap divides into

  1. Eden(250%) - Whenever any new objects created they stored into Eden space, when eden gets full garbage gets collected and survival objects passed to Survivors.
  2. Survivors(s1, s2)(5%) - Whatever objects moved to survivors, are shifted each other like s1 to s2 vice versa whenever gc happens. If it survives for 8th time then those objects moved to old gen. (Even though we get heap out of memory error these s1 and s2 are not actually occupied for these survived thing)
  3. Old gen (70%) - All old objects store here in old generation. When this get full then we received Out of memory issue.

This note is for after java 8 implementation -

Variables and inner classes:

  1. transient Node<K,V>[] table = This is the array of Node class in hashmap. What ever key value we are putting inside hashmap those are get stored inside this array. Sample Node array after adding map.put("test", "test") output = [null, null, null, null, test=test, null, null, null, null, null, null, null, null, null, null, null] putVal() method by calculating index puts those value in this array.

  2. Node class = Node class is static class present inside HashMap, implements Map.Entry<K,V>. So basically whatever data we stored in hashmap are we take that data create node class with those data and put this node class in Node<K,V> table by calculating index. If any collision happen at index then we create linked list(Prior java 8) inside this table array for particular index.

Fields = final int hash; final K key; V value; Node<K,V> next;

Constructor = Node(int hash, K key, V value, Node<K,V> next) { this.hash = hash; this.key = key; this.value = value; this.next = next; }

HashMap methods:

  1. put(): This method calls internal putVal() method.

  2. putVal(): this method mainly insert data into {} in hashmap.

  3. How serialization is done in hashmap even node[] is transient ? In Java's HashMap, the table field is marked as transient to prevent it from being serialized directly, which means its internal structure isn't included in the serialized form. Despite this, the actual key-value pairs in the HashMap are still successfully serialized and deserialized due to custom writeObject and readObject methods implemented in the class. These methods handle the serialization by writing out the number of entries and each key-value pair individually, and upon deserialization, they read back the entries and reconstruct the HashMap, ensuring that the logical content is preserved without needing to serialize the transient internal table structure.

  4. Step by Step working - Here’s how HashMap handles collisions step-by-step:

Hashing: When you insert a key-value pair into the HashMap, the hashCode() method of the key is called to generate a hash code for that key. The hash code is an integer value that represents the location (index) where the key-value pair will be stored in the HashMap’s underlying array of buckets.

Bucket Index Calculation: The HashMap calculates the bucket index by taking the hash code modulo the number of buckets in the array. The number of buckets is determined by the initial capacity of the HashMap and may dynamically change based on resizing.

Checking for Collisions: If two or more keys have the same hash code (hash collision), they will be mapped to the same bucket index.

Chaining: When a collision occurs, HashMap uses a linked list (or a similar data structure, depending on the JDK version) to handle it. The key-value pairs with the same bucket index are stored in a linked list structure within the bucket. Each node in the linked list represents a key-value pair.

Retrieval and Update: When you want to retrieve a value associated with a specific key, the HashMap follows the same process. It calculates the bucket index using the key’s hash code, then iterates through the linked list within the bucket to find the key-value pair that matches the given key.

Performance: In most cases, HashMap provides O(1) constant-time complexity for insertion, retrieval, and deletion operations. However, in the worst-case scenario, when all keys have the same hash code and end up in a single bucket, the performance can degrade to O(n), where n is the number of key-value pairs in the map.

To improve performance, it is essential to choose a good hash function for keys to minimize collisions. Additionally, HashMap automatically resizes and rehashes its underlying array when the number of key-value pairs exceeds a certain threshold, which helps maintain a proper balance between load factor and performance.

Basically, Hashset is a hashmap with dummy values. So when we create object of HashSet, we are initialising new HashMap object.

Constructor = public HashSet() { map = new HashMap<>(); }

When we add element in hashset, there is hashmap which add this key in map. // Dummy value to associate with an Object in the backing Map private static final Object PRESENT = new Object(); public boolean add(E e) { return map.put(e, PRESENT)==null; }

Intermittent operation is only applied during the terminal operation. Think of something like this:

public Stream filter(Predicate p) { this.filter = p; // just store it, don't apply it yet return this; // in reality: return a new stream } public List collect() { for (Object o : stream) { if (filter.test(o)) list.add(o); } return list; }

Java-Interface -

  1. Abstract method are always public, reason behind this is that you cannot make private abstract method because some one other needs to call those methods when you write Interface as a code.

Quetion - Why we should follow Interface while declaring an object class. in example you can see we have declared as Interface and instantiated a class. Example : List list = new ArrayList<>(); Answer - 1) By providing Interface as a contract, we are only publishing the methods those are writtern in interface to client(One who is going to use arrayList). So, apart from these contracted methods client cannot access other methods that present in the class. 2) Using interface seggregation, let's say if class shop have addItem and getItem methods. And customer class want to getItem, But also customer class should be prohibated from using addItem method. Same for Worker class, for him he has access to addItem but he should prohibited from getItem. So, using point 1 we cannot achieve this. So, here we can use Interface sagregation and we can create Interface Like ShopRead(Contains only getItem method) and shopWrite(Contains only addItem method). So by doing this we can provide different contract to different clients and they can access only those methods which are present in contract.

  1. Static vs Default vs Private method in interface: 2.1) Default = Default method introduced in java8 to support backward compatibility in interface. Default method cannot be abstract and implemenentation must be provided in interface. Default method can be overriden by child class and hence default method cannot be private. Anyone with default method can enhance their interface without breaking their contracts. 2.2) Static = Static method are bound to interface, static method cannot be abstract, implementation must be provided in interface. Static method cannot be overrident by child class. 2.3) Private = Private method is part of java 9, private method cannot be abstract, implementation must be provided in interface and as this is private method hence it can't be override

  2. WHat is marker interface = Interface are those we used in java to mark or tag that class have some special behaviour. Like serializable we implement so we are telling jvm that serialize this object while transferring this to outside of jvm.

  3. Functional Interface = Functional Interface is introduced in JAVA 8. With annotation of @FunctionalInterface that interface marked as functional interface. Interface should have only one abstract method. Why only one abstract method -> Java created functional interface so that it can enable functional programming in java. They introduced lambda expressions and within lambda expression syntax in java we pass functional interface abstract method. Since we have single abstract method it by default enables lambda expression to execute only single method. Hence, without functional interface lambda expresion wouldn't be achievable.

INternal working - https://amanagrawal9999.medium.com/internal-working-of-functional-interface-and-lambda-expression-d6a19e5d2f46

public class ExtendsAndImplementsDemo{ public static void main(String args[]){

    Dog dog = new Dog("Tiger",16);
    Cat cat = new Cat("July",20);

    System.out.println("Dog:"+dog);
    System.out.println("Cat:"+cat);

    dog.remember();
    dog.protectOwner();
    Learn dl = dog;
    dl.learn();

    cat.remember();
    cat.protectOwner();

    Climb c = cat;
    c.climb();

    Man man = new Man("Ravindra",40);
    System.out.println(man);

    Climb cm = man;
    cm.climb();
    Think t = man;
    t.think();
    Learn l = man;
    l.learn();
    Apply a = man;
    a.apply();

}

}

abstract class Animal{ String name; int lifeExpentency; public Animal(String name,int lifeExpentency ){ this.name = name; this.lifeExpentency=lifeExpentency; } public void remember(){ System.out.println("Define your own remember"); } public void protectOwner(){ System.out.println("Define your own protectOwner"); }

public String toString(){
    return this.getClass().getSimpleName()+":"+name+":"+lifeExpentency;
}

} class Dog extends Animal implements Learn{

public Dog(String name,int age){
    super(name,age);
}
public void remember(){
    System.out.println(this.getClass().getSimpleName()+" can remember for 5 minutes");
}
public void protectOwner(){
    System.out.println(this.getClass().getSimpleName()+ " will protect owner");
}
public void learn(){
    System.out.println(this.getClass().getSimpleName()+ " can learn:");
}

} class Cat extends Animal implements Climb { public Cat(String name,int age){ super(name,age); } public void remember(){ System.out.println(this.getClass().getSimpleName() + " can remember for 16 hours"); } public void protectOwner(){ System.out.println(this.getClass().getSimpleName()+ " won't protect owner"); } public void climb(){ System.out.println(this.getClass().getSimpleName()+ " can climb"); } } interface Climb{ public void climb(); } interface Think { public void think(); }

interface Learn { public void learn(); } interface Apply{ public void apply(); }

class Man implements Think,Learn,Apply,Climb{ String name; int age;

public Man(String name,int age){
    this.name = name;
    this.age = age;
}
public void think(){
    System.out.println("I can think:"+this.getClass().getSimpleName());
}
public void learn(){
    System.out.println("I can learn:"+this.getClass().getSimpleName());
}
public void apply(){
    System.out.println("I can apply:"+this.getClass().getSimpleName());
}
public void climb(){
    System.out.println("I can climb:"+this.getClass().getSimpleName());
}
public String toString(){
    return "Man :"+name+":Age:"+age;
}

}

Definition - Abstraction is concept where we hide our implemenation from user and provide some interface to user, using that user can interact with our application.

Uses of abstraction -

  1. Using abstraction we are hiding our implementations from end user
  2. In terms of java using abstraction you can have multiple implementation of single interface/definitons. Which you can use to not change a contract from user, like user does not need to know what is happening behind.
  3. You can achieve code reusablity using abstraction and in java using abstract classes. E.g - You have tax abstract class where your all abstract method would be there and you can have one concrete method for general tax calculation. So whatever classes you would be implements from abstrac will have that code reusablity using this abstraction
  4. When you work on monolith application or library where in single code other people wants to get implementation of your code. You can give them interface or contract so they can use them.
  5. Using interface in microservice is not a good idea, untill you really requires it.

How you can achieve abstraction in Java -

  1. Using interface
  2. Using abstract class

Re "hiding the implementation" -- it simply means that the client would have no idea exactly how something is done. Take for example a method of an interface like List. The client code doesn't know or care about how the add() method would be implemented. All the client cares is that an element will in fact be added to the list when this method is invoked. How that happens exactly will be up to whatever the author of a particular implementation decides to do.

In short, using interfaces is all about the intent of the client, not the implementation.

Links -

  1. https://medium.com/w-logs/think-twice-before-implementing-interface-classes-in-microservices-abc67ae11fac
  2. https://stackoverflow.com/questions/7716435/why-would-you-declare-an-interface-and-then-instantiate-an-object-with-it-in-jav

Definition - Encapsulation in oops which means we are binding data into a kind of storage, which will be not be accessbile by outside world. In java terms we can have private variables where this private variables will not be accessible by other classess.

To access private members we can have getter methods which will provide your data for read only to outside class.

Definition - It is the mechanism in java by which one class is allow to inherit the features(fields and methods) of another class.

In java we have use inheritance by using extends keyword. Inheritance does not inherit private members. Using inheritance we are achieving code reusability.

Definition - Polymorphism is term which says we can have single object can have many forms.

Compile-time polymorphism (Method overloading) -> method abc(a, b) method abc(a, b, c)

Run-time polymorphism (Method Overriding) -> class a.method abc(a) | Inherit class b.method abc(b)

  1. Optional is a container object used to contain not-null objects. Optional object is used to represent null with absent value. This class has various utility methods to facilitate code to handle values as 'available' or 'not available' instead of checking null values.

  2. Null handling is present inside optional API, so you need to worry all those scenarios and you can deal with objects in oops.

  3. There is little performance overhead occurs in optional.

  1. Serialization is a Java-specific concept that converts objects into a byte stream.
  2. Serialized objects can generally only be deserialized using Java's serialization mechanism. For interoperability between different languages and platforms, it's important to choose serialization formats and mechanisms that support cross-platform communication.
  3. JSON, Protocol Buffers, and similar formats are commonly used for this purpose.
  4. Spring boot http request/response uses json/text, hence no serialization is required.
  5. Hibernate, JDBC also converts java pojo to sql specific data hence no serialization is required.
  6. EH Cache required serialization. As it stores cache data into disk. Hence in distributed JVM we may need serialization in eh cache.
  1. We would need a static variable if its value doesn’t need to be altered i.e. constant. Constants are in fact public static final by definition. Critically used by interfaces.
  2. Static variables are useful in inheritance hierarchy also. Although both static and instance variables are useful, but I was just extending the specific use of statics to inheritance.
  3. We can have static methods that can serve as getters and setters of static variables. These would come in handy if an instance is not needed to invoke them in main method.
  4. One of special reasons to use static variables and static methods is a singleton class. They use static variables and methods to return their only instance.

Example of static class - A good use of a static class is in defining one-off, utility and/or library classes where instantiation would not make sense. A great example is the Math class that contains some mathematical constants such as PI and E and simply provides mathematical calculations. Requiring instantiation in such a case would be unnecessary and confusing.

Static variables/fields stored in heap itself after java 8, and it is a class level variables can access by anyone. And static variables loads in memory at time of class loader(initialize phase).

  1. Flatmap vs map -> Map -> Map is a intermittent operation which take single value from stream and return same data type value. Ofcourse we can do modification to that value. Flatmap -> Flatmap is a combination of map and flattening feature. Flatmap also takes a value from stream(Either collection, single object or whatever) but flatmap need to return a stream. So that internally it flattern and produces the single stream to process further.

  2. Reduce vs Collect -> Reduce -> Reduce is a stream operation that collect the stream and produces a single result out of that. It is better to use reduce on immutable types because for each reduction intermittent step it produces a new object. So from performance basis it is always better to use on some data type like Integer, String(if not big dataset as String will create a multiple intermittent strings). reduce method have 3 overloaded methods

  3. (BinaryFunction accumulator) -> This one should have a same return type as stream data type. Accumulator basically takes a two argument, first one is result of previous and second is the stream element.

  4. (T Identity, BinaryFunction combiner) -> This one should have same return type as stream data type and identity also need to same. combiner basically takes a two argument, first one is result of previous(Or identity) and second is the stream element.

  5. (T Identity, BiFunction accumlator, BinaryFunction combiner) -> This one should have same return type as stream data type and identity also need to same. combiner basically takes a two argument, first one is result of previous(Or identity) and second is the stream element. accumulator takes two argument same as combiner but can return anything. So basically this overloaded used for parallel stream and also in worst case if we want to perform reduce with mutable object we can use this third overloaded. (but don' do this, because we have a collect method)

Collect -> Collect is a another reduction operation that produces a single result, but collect is introduce for mutable object as collect method will modify the object unlike reduce was creating a new object. Collect method have a 2 overloaded methods.

  1. collect(Collector interface) -> Generally we should reuse what Collectors factory class have given the predefined methods instead of writing our own collector interface. Like a code reusability. If we want you can rewrite but why ?
  2. collect(Supplier supplier, BiConsumer accumulator, BiConsumer consumer ) -> Here we can write our own logic for collections. Like with this we can leverage F.I and yes we are writing reduction by our own.

Thread - Thread is smallest unit of process, it is lightweight and it can be managed by schedular.

Use of threads -

  1. We can achieve parrallel works, we can run multiple part of programs using threads without blocking each others.
  2. Async we can achieve.

Thread: It simply refers to the smallest units of the particular process. It has the ability to execute different parts (referred to as thread) of the program at the same time.

Process: It simply refers to a program that is in execution i.e., an active program. A process can be handled using PCB (Process Control Block).

Ways to create thread in java -

  1. Extending java.lang.Thread class.
  2. Implementing java.lang.Runnable interface

Internal thread creation process -

  1. When we extend class with Thread class. -> When we extend class with Thread class, and we create a object of that class. Then we invoke start() method of that object(start() method is thread class internal method). 1.1) When start() method gets invoked internally it creates a new thread, and add that thread in thread group to notify others that thread is created. 1.2) There is native method start0() which gets invoked(I suppose this method is mainly responsible for creating new thread and calling run() method)

  2. When we use Runnable interface -> Runnable interface is another way of creating threads. The reason they used runnable interface is because if we used extends then we are limiting the inheriting of our class. Apart from this runnable helps us to create threads using lambda interface. As Runnable Interface is Functional Interface. As we are writing our logic inside run method of runnable interface, framework like executorService or completebleFuture we can pass our logic to this frameworks and they will execture our logic. They will take care of thread creation and handling. 2.1) When we use runnable interface means we have implementation of run() method and we just passed that implementation to Thread class. like this Thread t = new Thread(MyClassWithImplementedRunnableInterface); So here Thread is created with the our Runnable implementation that we provided. 2.2) When we call t.start() method it does same as 1.1) 1.2) above points, additonaly as we have our runnable implementation, while calling start0() this thread invokes our runnable implementation run() methods.(This is my theroy only)

B) Exectuors Framework =

  1. This frameworks helps to create threads by using it's factory method. And help us to submit actions(Runnable/Callable) interface. And those actions will be run by this frameworks.
  2. Hierarchy is Exector -> ExectuorService -> ScheduledExectorService
  3. In this service we can passed customize ThreadFactory to have our own thread creation implementation.
  4. ManagedExectorService = This service is extension of Java exectuor framework, and part of EE edition. 4.1) This service basically created because EE edition threads are managed by server or any container. So ExecutorService doesn't need any web container, where as ManagedExecutorService is used in the context of application deployed to a webserver, where threadpools are created and their life cycles are maintained by the container. 4.2) If we are using ExecutorService It is discouraged because all resources within the environment are meant to be managed, and potentially monitored, by the server. 4.3) https://stackoverflow.com/questions/533783/why-is-spawning-threads-in-java-ee-container-discouraged/533847#533847

Runnable vs Callable -

  1. Runnable interface does not return anything, so whatever logic we are going to put into runnable that logic will be run without returning anything. Usecase of this is fire/forget task or logging.
  2. Callable interface return the Generic value what we will provide, with wrapped inside Future block. So when we call future.get() method it executor service will block the current thread and wait for what callable interface method is returning. Executor framework have submit method which do method overloading for callable and runnable.

Volatile vs Synchronized

  • Visibility vs Mutual Exclusion:
  1. Use volatile for ensuring visibility of changes to variables across threads.
  2. Use synchronized for both visibility and mutual exclusion to ensure thread-safe access and modification of shared resources.
  3. Suitable for simple flags or variables accessed by multiple threads where only visibility is critical (e.g., status flags).
  • Atomicity:
  1. volatile does not provide atomicity of compound actions (e.g., read-modify-write operations).
  2. synchronized ensures atomicity by preventing concurrent execution of synchronized blocks or methods.
  3. Use when thread-safe access and modification of shared resources (e.g., counters, collections) are required.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment