Java
ūüé°

Java

Files

ReadLines
Java
InputFile in = new InputFile("Cleanup.java"); //need to close
in.nextLine()
ReadFile and convert to stream
Java
// Method 1
Stream<String> linesStream = Files.lines(Paths.get("Cleanup.java"));

// Method 2
in = new FileInputStream(new File("MessyExceptions.java")); //need to close
int contents = in.read();

// Method 3
List<String> lines = Files.readAllLines(Paths.get("MainException.java"));

Stream

Array to stream
Java
Integer[] arr = {1,2,3,4,5};
Stream.of(arr);
Arrays.stream(arr);
  1. Group employees of a company and group them by their facility
Java
public void filterEmployeesThenGroup() {
    List<Employee> employees = getAllEmployees().stream()
            .filter(employee -> "abc".equals(employee.getSubCompany()))
            .collect(Collectors.toList());
    Map<String, List<Employee>> resultMap = new HashMap<>();
    for (Employee employee : employees) {
        List<Employee> groupList = resultMap
                .computeIfAbsent(employee.getDepartment(), k -> new ArrayList<>());
        groupList.add(employee);
    }
    System.out.println(resultMap);
}

public void filterEmployeesThenGroupByStream() {
    Map<String, List<Employee>> resultMap = getAllEmployees().stream()
            .filter(employee -> "abc".equals(employee.getSubCompany()))
            .collect(Collectors.groupingBy(Employee::getDepartment));
    System.out.println(resultMap);
}
  1. Join string
Java
public void testForJoinStrings() {
    List<String> ids = Arrays.asList("205", "10", "308", "49", "627", "193", "111", "193");
    StringBuilder builder = new StringBuilder();
    for (String id : ids) {
        builder.append(id).append(',');
    }
    builder.deleteCharAt(builder.length() - 1);
    System.out.println(builder.toString());
}

public void testCollectJoinStrings() {
    List<String> ids = Arrays.asList("205", "10", "308", "49", "627", "193", "111", "193");
    String joinResult = ids.stream().collect(Collectors.joining(","));
    System.out.println(joinResult);
}
 
notion image
notion image
Once the stream has been ended, we can’t execute it again.
Java
public void testSimpleStopOptions() {
    List<String> ids = Arrays.asList("205", "10", "308", "49", "627", "193", "111", "193");
    System.out.println(ids.stream().filter(s -> s.length() > 2).count());
    System.out.println(ids.stream().filter(s -> s.length() > 2).anyMatch("205"::equals));
    ids.stream().filter(s -> s.length() > 2)
            .findFirst()
            .ifPresent(s -> System.out.println("findFirst:" + s));
}
Method
Purpose
map
Transforms the elements in the stream by applying a given function to each element.
filter
Filters elements in the stream based on a predicate (a boolean-valued function).
forEach
Performs an action for each element in the stream.
collect
Transforms the stream into a different kind of result, like a Collection or an object.
reduce
Performs a reduction on the stream elements using a binary operator and returns an Optional.
findFirst
Returns an Optional for the first entry in the stream.
anyMatch
Returns whether any elements of this stream match the provided predicate.
allMatch
Returns whether all elements of this stream match the provided predicate.
noneMatch
Returns whether no elements of this stream match the provided predicate.
findAny
Returns an Optional describing some element of the stream, or an empty Optional if the stream is empty.
count
Returns the count of elements in the stream.
sorted
Returns a stream consisting of the elements of the original stream, sorted according to natural order or a provided comparator.
flatMap
Transforms each element of the stream into a stream of other objects.
distinct
Returns a stream consisting of the distinct elements of the original stream.
limit
Returns a stream that is no longer than a given size n.
skip
Returns a stream consisting of the remaining elements of the original stream after discarding the first n elements.
peek
Returns a stream consisting of the elements of the original stream, additionally performing an action on each element as elements are consumed from the resulting stream.
 

Collect results

Method
Purpose
toList()
Collects stream elements in a List.
toSet()
Collects stream elements in a Set.
toMap()
Returns a Collector that accumulates elements into a Map whose keys and values are the result of applying the provided mapping functions to the input elements.
collectingAndThen()
Collects stream elements and then transforms them using a Function
summingDouble(), summingLong(), summingInt()
Sums-up stream elements after mapping them to a Double/Long/Integer value using specific type Function
reducing()
Reduces elements of stream based on the BinaryOperator function provided
partitioningBy()
Partitions stream elements into a Map based on the Predicate provided
counting()
Counts the number of stream elements
groupingBy()
Produces Map of elements grouped by grouping criteria provided
mapping()
Applyies a mapping operation to all stream elements being collected
joining()
For concatenation of stream elements into a single String
minBy()/maxBy()
Finds the minimum/maximum of all stream elements based on the Comparator provided
Java
public void testCollectStopOptions() {
    List<Dept> ids = Arrays.asList(new Dept(17), new Dept(22), new Dept(23));
    // collect to list
    List<Dept> collectList = ids.stream().filter(dept -> dept.getId() > 20)
            .collect(Collectors.toList());
    System.out.println("collectList:" + collectList);
    // collect to Set
    Set<Dept> collectSet = ids.stream().filter(dept -> dept.getId() > 20)
            .collect(Collectors.toSet());
    System.out.println("collectSet:" + collectSet);
    // collect to HashMapÔľĆkeyto idÔľĆvalue to Dept
    Map<Integer, Dept> collectMap = ids.stream().filter(dept -> dept.getId() > 20)
            .collect(Collectors.toMap(Dept::getId, dept -> dept));
    System.out.println("collectMap:" + collectMap);
}
 
If we are sure that our function is going to return a primitive, instead of using map()  use mapToInt(), mapToLong()or mapToDouble().
  • anyMatch()
  • allMatch()
  • noneMatch()
 
sometimes we need to get the matched element instead of just verifying if it is present or not. The finding operations are used for this purpose. There are two basic finding operations in streams, i.e., findFirst() and findAny()

Numeric operations

Java
public void testNumberCalculate() {
    List<Integer> ids = Arrays.asList(10, 20, 30, 40, 50);
    Double average = ids.stream().collect(Collectors.averagingInt(value -> value));
    System.out.println("AverageÔľö" + average);
    IntSummaryStatistics summary = ids.stream().collect(Collectors.summarizingInt(value -> value));
    System.out.println("statistics analysis Ôľö " + summary);
}
 
notion image
 
notion image
 
Static Polymorphism
Dynamic Polymorphism
Polymorphism that is resolved during compile time is known as static polymorphism.
Polymorphism that is resolved during run time is known as dynamic polymorphism.
Method overloading is used in static polymorphism.
Method overriding is used in dynamic polymorphism.
 
notion image
 

Optional

Method
Purpose
empty()
Returns an empty Optional instance.
of(T value)
Returns an Optional with the specified present non-null value.
ofNullable(T value)
Returns an Optional describing the specified value, if non-null, otherwise returns an empty Optional.
get()
If a value is present in this Optional, returns the value, otherwise throws NoSuchElementException.
isPresent()
Returns true if there is a value present, otherwise false.
ifPresent(Consumer<? super T> consumer)
If a value is present, performs the given action with the value, otherwise does nothing.
ifPresentOrElse(Consumer<? super T> action, Runnable emptyAction)
If a value is present, performs the given action with the value, otherwise performs the given empty-based action.
map(Function<? super T,? extends U> mapper)
If a value is present, applies the provided mapping function to it, and if the result is non-null, returns an Optional describing the result.
flatMap(Function<? super T,? extends Optional<? extends U>> mapper)
If a value is present, applies the Optional-bearing function to it, returns that result, otherwise returns an empty Optional.
filter(Predicate<? super T> predicate)
If a value is present, and the value matches the given predicate, return an Optional describing the value, otherwise return an empty Optional.
orElse(T other)
Returns the value if present, otherwise returns other.
orElseGet(Supplier<? extends T> other)
Returns the value if present, otherwise returns the result produced by the supplying function.
orElseThrow()
If a value is present, returns the value, otherwise throws NoSuchElementException.
orElseThrow(Supplier<? extends X> exceptionSupplier)
If a value is present, returns the value, otherwise throws an exception produced by the exception supplying function.
  1. Check a string is between two length
Java
Predicate<String> len6 = pwd -> pwd.length() > 6;
Predicate<String> len10 = pwd -> pwd.length() < 10;

password = "1234567";
opt = Optional.ofNullable(password);
boolean result = opt.filter(len6.and(len10)).isPresent();
System.out.println(result);
 
caveat: can’t compile if both interface having the same default method
Java
interface InterfaceA {

    default void printSomething() {
        System.out.println("I am inside A interface");
    }
}

public interface InterfaceB {

    default void printSomething() {
        System.out.println("I am inside B interface");
    }
}

public class Main implements InterfaceA, InterfaceB {

    @Override
    public void printSomething() {

        //Option 1 -> Provide our own implementation.
        System.out.println("I am inside Main class");

        //Option 2 -> Use existing implementation from interfaceA or interfaceB or both.
        InterfaceA.super.printSomething();
        InterfaceB.super.printSomething();
    }

    public static void main(String args[]){
         Main main = new Main();
         main.printSomething();
    }
}
 
An interface that has a single abstract method (Can have one or more final methods)is called a functional interface.
Any interface that has only one abstract method can be annotated with the @FunctionalInterface annotation (not necessary).
 

Interface vs abstract class

Interfaces and abstract classes are both used to achieve abstraction but with some of the key differences:
Interfaces
Abstract Classes
Support multiple inheritance
Don’t support multiple inheritance
All members are public
Can have private, protected and public members
All data members are static and final
Can have non-static and non-final members too
Can’t have constructors
Constructors can be defined
Aggregation follows the has-A model.
In aggregation, the lifetime of the owned object does not depend on the lifetime of the owner.
Aggregation is when objects have their own life cycle and child object can associate with only one parent object.
notion image
Composition relationships are Part-of relationships where the part must constitute part of the whole object.
In composition, the lifetime of the owned object depends on the lifetime of the owner.
 
notion image

Difference between Collection and Collections

The differences between a Collection and Collections are given below.
  1. A Collection is an interface, whereas Collections is a class.
  1. A Collection interface provides the standard functionality of a data structure to List, Set, and Queue. However, the Collections class provides the utility methods that can be used to search, sort, and synchronise collection elements.
 
Plain Text
List                 | Add  | Remove | Get  | Contains | Next | Data Structure
---------------------|------|--------|------|----------|------|---------------
ArrayList            | O(1) |  O(n)  | O(1) |   O(n)   | O(1) | Array
LinkedList           | O(1) |  O(1)  | O(n) |   O(n)   | O(1) | Linked List
CopyOnWriteArrayList | O(n) |  O(n)  | O(1) |   O(n)   | O(1) | Array



Set                   |    Add   |  Remove  | Contains |   Next   | Size | Data Structure
----------------------|----------|----------|----------|----------|------|-------------------------
HashSet               | O(1)     | O(1)     | O(1)     | O(h/n)   | O(1) | Hash Table
LinkedHashSet         | O(1)     | O(1)     | O(1)     | O(1)     | O(1) | Hash Table + Linked List
EnumSet               | O(1)     | O(1)     | O(1)     | O(1)     | O(1) | Bit Vector
TreeSet               | O(log n) | O(log n) | O(log n) | O(log n) | O(1) | Red-black tree
CopyOnWriteArraySet   | O(n)     | O(n)     | O(n)     | O(1)     | O(1) | Array
ConcurrentSkipListSet | O(log n) | O(log n) | O(log n) | O(1)     | O(n) | Skip List



Queue                   |  Offer   | Peak |   Poll   | Remove | Size | Data Structure
------------------------|----------|------|----------|--------|------|---------------
PriorityQueue           | O(log n) | O(1) | O(log n) |  O(n)  | O(1) | Priority Heap
LinkedList              | O(1)     | O(1) | O(1)     |  O(1)  | O(1) | Array
ArrayDequeue            | O(1)     | O(1) | O(1)     |  O(n)  | O(1) | Linked List
ConcurrentLinkedQueue   | O(1)     | O(1) | O(1)     |  O(n)  | O(n) | Linked List
ArrayBlockingQueue      | O(1)     | O(1) | O(1)     |  O(n)  | O(1) | Array
PriorirityBlockingQueue | O(log n) | O(1) | O(log n) |  O(n)  | O(1) | Priority Heap
SynchronousQueue        | O(1)     | O(1) | O(1)     |  O(n)  | O(1) | None!
DelayQueue              | O(log n) | O(1) | O(log n) |  O(n)  | O(1) | Priority Heap
LinkedBlockingQueue     | O(1)     | O(1) | O(1)     |  O(n)  | O(1) | Linked List



Map                   |   Get    | ContainsKey |   Next   | Data Structure
----------------------|----------|-------------|----------|-------------------------
HashMap               | O(1)     |   O(1)      | O(h / n) | Hash Table
LinkedHashMap         | O(1)     |   O(1)      | O(1)     | Hash Table + Linked List
IdentityHashMap       | O(1)     |   O(1)      | O(h / n) | Array
WeakHashMap           | O(1)     |   O(1)      | O(h / n) | Hash Table
EnumMap               | O(1)     |   O(1)      | O(1)     | Array
TreeMap               | O(log n) |   O(log n)  | O(log n) | Red-black tree
ConcurrentHashMap     | O(1)     |   O(1)      | O(h / n) | Hash Tables
ConcurrentSkipListMap | O(log n) |   O(log n)  | O(1)     | Skip List
 

Sort a ArrayList

Java
Collections.sort(list); // inplace
Collections.sort(list, Collections.reverseOrder()); // reverse order

list.stream().sorted().collect(Collectors.toList()); // return new
list.stream().sorted(Comparator.reverseOrder()).collect(Collectors.toList());

ArrayList

Java
List list = new ArrayList(); //size of zero
list.add(12);
list.add(int index, E element);
list.addAll(anotherList);
list.addAll(3, anotherList);
list.get(1);

List sec_list = (ArrayList) list.clone();

list.remove(Integer.valueOf(30)); // This will remove 30 from the list

list.replaceAll((element) -> element.toUpperCase());

list.set(index, val);

list.indexOf(10)

Let object comparable

  1. Need to override compareTo() method to
  1. Custom Comparator
Java
import java.util.Comparator;

public class BrandComparator implements Comparator<Vehicle> {

	@Override
	public int compare(Vehicle o1, Vehicle o2) {
		return o1.brand.compareTo(o2.brand);
	}
}
  1. Lambda function

LinkedList

notion image
Internal implementation of linkedList
Java
private static class Node<E> {
     E item;
     Node<E> next;
     Node<E> prev;

     Node(Node<E> prev, E element, Node<E> next) {
         this.item = element;
         this.next = next;
         this.prev = prev;
     }
 }
 
Thread safe List
Java
List list = new CopyOnWriteArrayList();

Difference between a HashSet and TreeSet

  1. The HashSet allows one null element, whereas a TreeSet does not allow a null element.
  1. The elements are stored in random order in a HashSet, whereas it is stored in sorted order in TreeSet.
  1. HashSet is faster than Treeset for the operations like add, remove, contains, size, etc.
 
Java
// This TreeSet will store the elements in reverse order.
TreeSet<Integer> reverseSet = new TreeSet<>(Comparator.reverseOrder());

TreeSet<Integer> set = new TreeSet<>();

HashMap

Resizing a HashMap
If the current capacity is 16, and the load factor is 0.75, then the HashMap will be resized when it has 12 elements (16 * 0.75). and the size is doubled.
If the size of the LinkedList in a particular bucket becomes more than TREEIFY_THRESHOLD , then the LinkedList is converted to a red-black tree.
notion image
 
the class that is being used as a key must override both equals() and hashcode() methods.
Java
class Employee {
	int empId;
	String empName;

	public Employee(int empId, String empName) {
		super();
		this.empId = empId;
		this.empName = empName;
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + empId;
		result = prime * result + ((empName == null) ? 0 : empName.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		Employee emp = (Employee) obj;
		return this.empId == emp.empId;
	}
}

TreeMap

  1. The entries in TreeMap are sorted in the natural ordering of its keys.
  1. It does not allow null keys, however there can be null values.
  1. The TreeMap is not thread-safe, although it can be made thread-safe using the synchronizedMap() method of the Collections class.
 
notion image
 

LinkedHashMap

Store the elements in a Map in insertion order
  1. It does not allow duplicate keys.
  1. It may have one null key and multiple null values.
  1. It is non-synchronized.
notion image
 
Java
// LinkedHashMap with specified order
LinkedHashMap<Key, Value> numbers2 = new LinkedHashMap<>(capacity, loadFactor, accessOrder);
Setting accessOrder to false means that the storage order is based on insertion order rather than access order. This is the default value, which means that the order in which items are stored in the LinkedHashMap is sorted based on the order in which put method is called.
 
Insert Elements to LinkedHashMap
  • put()¬†- inserts the specified key/value mapping to the map
  • putAll()¬†- inserts all the entries from the specified map to this map
  • putIfAbsent()¬†- inserts the specified key/value mapping to the map if the specified key is not present in the map
Using entrySet(), keySet() and values()
  • entrySet()¬†- returns a set of all the key/value mapping of the map
  • keySet()¬†- returns a set of all the keys of the map
  • values()¬†- returns a set of all the values of the map
 
Here, accessOrder is a boolean value. Its default value is false. In this case entries in the linked hashmap are ordered on the basis of their insertion order. However, if true is passed as accessOrder, entries in the linked hashmap will be ordered from least-recently accessed to most-recently accessed.
 
ConcurrentHashMap vs SynchronizedMap.
  1. In a SynchronizedMap, the entire Map is locked. So every read/write operation needs to acquire a lock, which makes it very slow. On the other hand in a ConcurrentHashMap, only a segment of the Map is locked. Two parallel threads can access or update elements in a different segment, so it performs better.
  1. SynchronizedMap returns Iterator, which fails fast on concurrent modification. ConcurrentHashMap doesn’t throw a ConcurrentModificationException if one thread tries to modify it while another is iterating over it.
  1. ConcurrentHashMap does not allow null keys or null values while SynchronizedMap allows one null key.
 
IdentityHashMap are:
  1. The IdentityHashMap stores the elements in random order.
  1. The IdentityHashMap allows a single null key.
  1. The IdentityHashMap is not thread-safe.
 
The differences between a HashMap and IdentityHashMap.
  1. IdentityHashMap uses reference equality to compare keys and values while HashMap uses object equality to compare keys and values.
  1. IdentityHashMap does not use the hashCode() method. Instead it uses System.identityHashCode() to find the bucket location.
  1. IdentityHashMap does not require keys to be immutable as it does not rely on the equals() and hashCode() methods. To safely store the object in HashMap, keys must be immutable.
  1. The default initial capacity of HashMap is 16; whereas, for IdentityHashMap, it is 32.
 
EnumMap
  1. EnumMap does not allow null keys, but it allows null values.
  1. The keys are stored in their natural order. In the case of an Enum, the natural order of keys means the order where enum constant is declared inside Enum type.
  1. The EnumMap is not synchronized.
  1. All keys of each EnumMap instance must be keys of a single Enum type.
  1. Iterators returned by the collection views are inconsistent. They will never throw ConcurrentModificationException, and they may or may not show the effects of any modifications to the map that occur while the iteration is in progress.
  1. Java EnumMap implementation provides constant-time performance for the basic operations (like get and put).
 
The binarySearch() method are:
  1. The array that is passed to the method should be sorted. If the array is not sorted, then the result is undefined.
  1. This method returns the index where the element is present in the array. If the element is not present in the array, then the index of the first element greater than the key is returned.
  1. If the array contains multiple elements with the specified value, there is no guarantee which one will be found.
  1. ClassCastException is thrown if the search key is not comparable to the elements of the array.

Collections

Following is the list of methods available to make Collections unmodifiable:
  1. unmodifiableList(List<? extends T> list)
  1. unmodifiableSet(Set<? extends T> s)
  1. unmodifiableMap(Map<? extends K, ? extends V> m)
  1. unmodifiableCollection(Collection<? extends T> c)
  1. unmodifiableSortedMap(SortedMap<K,? extends V> m)
  1. unmodifiableSortedSet(SortedSet<T> s)
 
The Collections class provides us with the following methods that can be used to make our existing collection thread-safe.
  1. synchronizedCollection(Collection<T> c)
  1. synchronizedList(List<T> list)
  1. synchronizedMap(Map<K,V> m)
  1. synchronizedSet(Set<T> s)
  1. synchronizedSortedMap(SortedMap<K,V> m)
  1. synchronizedSortedSet(SortedSet<T> s)
 
not taken any argument(Supplier), not returned any value(Consumer), or returned only a boolean(Predicate).
notion image
 
A non-final, local variable whose value is never changed after initialization is known as effectively final.

Exception

notion image
Checked exceptions
checked by the compiler during compilation
  • IOException
  • ClassNotFoundException
Unchecked exceptions
  • ArithmeticException
  • ArrayIndexOutOfBounds
  • NumberFormatException
  • ArithmeticException
  • NullPointerException
 
For checked exception(like IOException), we should require the client to check it and provide recovery actions if possible.
For unchecked exception (service unavailable), doesn’t require to check, and we can implement retry actions.
 

Generate random number

Java
ThreadLocalRandom.current().nextInt();
 

Parallel programming

Create process

  1. The Runtime.exec() method can be used to create a new process in Java. It takes the command and arguments as input and returns the Process object.
Java
Runtime runtime = Runtime.getRuntime();
Process process = runtime.exec("notepad.exe");
2. The ProcessBuilder class. First, create a List containing the command and any arguments for the process. Then, create a new ProcessBuilder object, passing the List as a parameter to the constructor. Finally, call the start() method on the ProcessBuilder object to start the new process.
Here is an example:
Java
List<String> command = new ArrayList<>();
command.add("ls");
command.add("-la");

ProcessBuilder builder = new ProcessBuilder(command);
Process process = builder.start();
This example creates a new process that runs the ls -la command in the terminal.

Create Thread

  1. Create a new thread by extending the Thread class and overriding the run() method. Then, create an instance of the new class and call the start() method.
Java
class MyThread extends Thread {
    @Override
    public void run() {
        // code to be executed in this thread
    }
}

MyThread thread = new MyThread();
thread.start();
  1. Implement the Runnable interface and pass an instance of the new class to the Thread constructor. Then, call the start() method.
Java
class MyRunnable implements Runnable {
    public void run() {
        // code to be executed in this thread
				Thread.currentThread(); // Get current executing thread
    }
}

MyRunnable runnable = new MyRunnable();
Thread thread = new Thread(runnable);
thread.start();
 

ThreadPool

Java
ExecutorService executorService = Executors.newFixedThreadPool(100);
executorService.execute(() -> System.out.println("aaaa"));
executorService.shutdown();
 

schedular tasks

Java
import java.util.Timer;
import java.util.TimerTask;

public class Main {
    public static void main(String[] args) {
        Timer timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("Hello, I am a well-behaved task");
            }
        }, 3000);
    }
}
Java
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class Main {
    public static void main(String[] args) {
        ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
        executor.schedule(() -> System.out.println("Hello, I am a well-behaved task"), 3, TimeUnit.SECONDS);
        executor.shutdown();
    }
}
 

Require the returned value

Java
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ExecutionException;

public class Main {

    public static void main(String[] args) {
        final int n = 10;
        Callable<Integer> sumTask = new Callable<Integer>() {
            public Integer call() throws Exception {
                int sum = 0;
                for (int i = 1; i <= n; i++)
                    sum += i;
                return sum;
            }
        };

        ExecutorService executor = Executors.newSingleThreadExecutor();
        Future<Integer> result = executor.submit(sumTask);

        try {
            System.out.println("The sum is: " + result.get());
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        } finally {
            executor.shutdown();
        }
    }
}
 

Ensure multithread safe

Thread-safe data structures or collections
ConcurrentHashMap and CopyOnWriteArrayList. These data structures are designed to be used in multi-threaded environments and can handle concurrent access to their elements without any issues.
 
synchronized
To ensure multithread safe, we can use the synchronized keyword to mark a method or a code block as critical section. This will ensure that only one thread can access the critical section at a time, preventing race conditions and other concurrency problems.
Java
class MyThread extends Thread {
    private Object lock;

    public MyThread(Object lock) {
        this.lock = lock;
    }

    @Override
    public void run() {
        synchronized (lock) {
            // critical section
        }
    }
}
 
Lock
Java
class Student implements Runnable {
        static private int atomicInteger = 100;
        static private final Lock lock = new ReentrantLock();
        @Override
        public void run() {
            lock.lock();
            try{
                if (atomicInteger > 0) {
                    System.out.println(Thread.currentThread().getId() + "eat one " + atomicInteger--);
                }
            }finally {
                lock.unlock();
            }
        }
    }
 
Condition
condition.await , condition.signal can only used with lock.
Java
class FooBar {
    private int n;
    private Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();
    private boolean isFirst = true;

    public FooBar(int n) {
        this.n = n;
    }

    public void foo(Runnable printFoo) throws InterruptedException {
        
        for (int i = 0; i < n; i++) {
            lock.lock();
            while(!isFirst){
                condition.await();
            }
	        	printFoo.run();
            isFirst = false;
             condition.signal();
            lock.unlock();
        }
    }

    public void bar(Runnable printBar) throws InterruptedException {
        
        for (int i = 0; i < n; i++) {
            lock.lock();
            while(isFirst){
                condition.await();
            }
	        	printBar.run();
            isFirst = true;
            condition.signal();
            lock.unlock();
        }
    }
}
 
wait/notify
To use wait() and notify() to coordinate between threads, we need to have a shared object as the lock: it must use with synchronized .
Java
class WaitingThread extends Thread {
    private final Object sharedLock;

    public WaitingThread(Object sharedLock) {
        this.sharedLock = sharedLock;
    }

    @Override
    public void run() {
        synchronized (sharedLock) {
            try {
                System.out.println(Thread.currentThread().getName() + " is waiting.");
                // wait for another thread to notify
                sharedLock.wait();
                // continue execution
                System.out.println(Thread.currentThread().getName() + " has been notified and is resuming execution.");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

public class Main {
    public static void main(String[] args) throws InterruptedException {
        // create shared lock object
        Object sharedLock = new Object();

        // create threads
        WaitingThread t1 = new WaitingThread(sharedLock);
        WaitingThread t2 = new WaitingThread(sharedLock);

        // set thread names
        t1.setName("Thread 1");
        t2.setName("Thread 2");

        // start threads
        t1.start();
        t2.start();

        // wait for threads to be in waiting state
        Thread.sleep(1000);

        // notify waiting threads
        notifyAllWaitingThreads(sharedLock);
    }

    private static void notifyAllWaitingThreads(Object sharedLock) {
        synchronized (sharedLock) {
            System.out.println("Notifying all waiting threads.");
            sharedLock.notifyAll();
        }
    }
}
CountDownLatch
CountDownLatch is used in scenarios where the main thread needs to wait for other business threads to complete before it can proceed with execution. The general usage is as follows:
  1. Initialize with a specified integer (number of counters).
  1. The main thread executes the latch.await(); method, waiting for other threads to complete. Other threads must call latch.countDown(); to decrement the counter.
  1. The main thread's code following latch.await(); can only continue executing when the counter value reaches 0.
Java
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class CountDownLatchDemo {

    public static void main(String[] args) {
        final CountDownLatch latch = new CountDownLatch(5);
        ExecutorService executorService = new ThreadPoolExecutor(6, 10, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(10), Executors.defaultThreadFactory(), new ThreadPoolExecutor.DiscardPolicy());

        for (int i = 0; i < 6; i++) {
            final int num = i + 1;
            Runnable runnable = () -> {
                try {
                    Thread.sleep(num * 1000);
                    System.out.println("The athlete " + num + " has arrived at the finish line.");
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                } finally {
                    latch.countDown();
                }
            };
            executorService.submit(runnable);
        }

        try {
            System.out.println("Referee commands: On your marks, get set, go!");
            latch.await();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("Main thread ended.");

        executorService.shutdown();
    }
}
 
CyclicBarrier
CyclicBarrier is a synchronization utility class in the Java Concurrency package. It allows a group of threads to wait for each other until all threads reach a specific barrier point, and then continue executing together. Its constructor takes an integer as a parameter, which represents the number of threads that need to wait.
 
Semaphore
The function of Semaphore is "flow control", which is used to "allow only a certain number of threads to execute simultaneously at the same time".
When initializing Semaphore, an integer needs to be specified to represent the number of threads that can be executed simultaneously at the same time; Before each thread starts executing, it needs to call semaphore.acquire(); to obtain a permit; After each thread ends, it needs to call semaphore.release(); to release a permit.
Java
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.Semaphore;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class SemaphoreDemo {
    private static Semaphore semaphore = new Semaphore(2);

    public static void main(String[] args) {
        ExecutorService executorService = new ThreadPoolExecutor(6, 10, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(10), Executors.defaultThreadFactory(), new ThreadPoolExecutor.DiscardPolicy());
        for (int i = 0; i < 10; i++) {
            executorService.execute(new Shopping(semaphore, "Classmate " + i));
        }
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        executorService.shutdown();
    }

    public static class Shopping implements Runnable {

        private Semaphore semaphore;

        private String name;

        public Shopping(Semaphore semaphore, String name) {
            this.semaphore = semaphore;
            this.name = name;
        }

        @Override
        public void run() {
            try {
                semaphore.acquire();
                System.out.println(name + " is shopping...");
                Thread.sleep(3000);

                System.out.println(name + " left the mall");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                semaphore.release();
            }
        }
    }
}
 
Read/Write Lock
ReentrantReadWriteLock
It allows multiple threads to read a shared resource concurrently, but restricts access to only one thread when writing. It can improve performance in scenarios where read operations are much more frequent than write operations.
Java
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.HashMap;
import java.util.Map;

public class SharedResource {
    private final Map<String, String> data = new HashMap<>();
    private final ReadWriteLock lock = new ReentrantReadWriteLock();

    public String readData(String key) {
        lock.readLock().lock();
        try {
            return data.get(key);
        } finally {
            lock.readLock().unlock();
        }
    }

    public void writeData(String key, String value) {
        lock.writeLock().lock();
        try {
            data.put(key, value);
        } finally {
            lock.writeLock().unlock();
        }
    }

    public static void main(String[] args) {
        SharedResource sharedResource = new SharedResource();

        // Writing data
        sharedResource.writeData("key1", "value1");
        sharedResource.writeData("key2", "value2");

        // Reading data
        System.out.println("key1: " + sharedResource.readData("key1"));
        System.out.println("key2: " + sharedResource.readData("key2"));
    }
}

/*
+-------------------+----------------+----------------+----------+
|   Current Lock    | Requested Lock | Compatibility  | Allowed? |
+-------------------+----------------+----------------+----------+
| Read Lock (T1)    | Read Lock (T2)  | Compatible     | Yes      |
+-------------------+----------------+----------------+----------+
| Read Lock (T1)    | Write Lock (T2)| Incompatible   | No       |
+-------------------+----------------+----------------+----------+
| Write Lock (T1)   | Read Lock (T2)  | Compatible   | Yes       |
+-------------------+----------------+----------------+----------+
| Write Lock (T1)   | Write Lock (T2)| Incompatible   | No       |
+-------------------+----------------+----------------+----------+
*/
 
Queue
Java
+----------------+-----------------+------------------+------------+------------+---------------+
| Data Structure |   Operation     | Return Type      | Insertion  | Deletion   | Retrieval     |
+----------------+-----------------+------------------+------------+------------+---------------+
| Queue          | add             | boolean          | Throws     |            |               |
|                |                 |                  | exception  |            |               |
|                | offer           | boolean          | Returns    |            |               |
|                |                 |                  | boolean    |            |               |
|                | remove          | E                |            | Throws     |               |
|                |                 |                  |            | exception  |               |
|                | poll            | E                |            | Returns    |               |
|                |                 |                  |            | null       |               |
|                | element         | E                |            |            | Throws        |
|                |                 |                  |            |            | exception     |
|                | peek            | E                |            |            | Returns       |
|                |                 |                  |            |            | null          |
+----------------+-----------------+------------------+------------+------------+---------------+
| Deque          | addFirst        | boolean          | Throws     |            |               |
|                | addLast         | boolean          | Throws     |            |               |
|                | offerFirst      | boolean          | Returns    |            |               |
|                | offerLast       | boolean          | Returns    |            |               |
|                | removeFirst     | E                |            | Throws     |               |
|                | removeLast      | E                |            | Throws     |               |
|                | pollFirst       | E                |            | Returns    |               |
|                | pollLast        | E                |            | Returns    |               |
|                | getFirst        | E                |            |            | Throws        |
|                | getLast         | E                |            |            | Throws        |
|                | peekFirst       | E                |            |            | Returns       |
|                | peekLast        | E                |            |            | Returns       |
+----------------+-----------------+------------------+------------+------------+---------------+
| BlockingQueue  | put             | void             | Blocks     |            |               |
|                |                 |                  | until      |            |               |
|                |                 |                  | space      |            |               |
|                | take            | E                |            | Blocks     |               |
|                |                 |                  |            | until      |               |
|                |                 |                  |            | element    |               |
+----------------+-----------------+------------------+------------+------------+---------------+
| BlockingDeque  | putFirst        | void             | Blocks     |            |               |
|                | putLast         | void             | Blocks     |            |               |
|                |                 |                  | until      |            |               |
|                |                 |                  | space      |            |               |
|                | takeFirst       | E                |            | Blocks     |               |
|                | takeLast        | E                |            | Blocks     |               |
|                |                 |                  |            | until      |               |
|                |                 |                  |            | element    |               |
+----------------+-----------------+------------------+------------+------------+---------------+
 
Method
Throw Exception
Return Special Value
Blocking
Timeout Exit
Insert
boolean add(E e)
boolean offer(E e)
void put(E e) throws InterruptedException
boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException
Remove
void remove()
poll()
E take() throws InterruptedException
E poll(long timeout, TimeUnit unit) throws InterruptedException
Examine
element()
peek()
Not available
Not available
 
What is the difference between ArrayBlockingQueue and LinkedBlockingQueue?
  • ArrayBlockingQueue is a bounded blocking queue that is internally implemented as a fixed-length array. When created, the capacity of the queue must be specified. Once the queue is full, further enqueue operations will be blocked until elements are removed from the queue.
  • LinkedBlockingQueue is an unbounded blocking queue that is internally implemented as a linked list. If no capacity is specified when creating a LinkedBlockingQueue, an unbounded queue is created, meaning the queue's capacity can grow indefinitely. If a capacity is specified, a bounded queue is created, meaning the queue's capacity is fixed and cannot exceed the specified capacity. If an attempt is made to add an element to a full bounded queue, the operation will block until space becomes available.
  • Dequeue operations are blocked when the queue is empty and enqueue operations are blocked when the queue is full.
 
How does a blocking queue avoid thread safety issues?
A blocking queue avoids thread safety issues through internal synchronization mechanisms such as ReentrantLock and Condition. These mechanisms ensure that enqueue and dequeue operations can only be performed by one thread when the queue is full or empty, thus avoiding potential race condition issues that arise when multiple threads access the queue concurrently.
 
Check dead lock
  1. Find the Java process ID: Run jps -l in the terminal. This command lists all the Java processes running on your system with their respective process IDs (PIDs) and fully qualified class names. Look for the process related to your application and note its PID.
  1. Generate a thread dump: Run jstack <PID> in the terminal, replacing <PID> with the process ID you found in step 1. This command will generate a thread dump for the specified Java process, which contains information about the state of all the threads in that process. You can redirect the output to a file by using the following command:
    1. Plain Text
      jstack <PID> > thread_dump.txt
  1. Search for deadlocks: A deadlock occurs when two or more threads are waiting for each other to release resources, causing them to be stuck indefinitely. In the thread dump, you can look for threads in the BLOCKED state and their associated lock objects.
    1. To search for deadlocks, you can use the grep command:
      Plain Text
      grep -i "BLOCKED" thread_dump.txt
      If you find any blocked threads, examine their stack traces in the thread dump to identify the resources they are waiting for and the threads holding those resources.