Files
ReadLines
InputFile in = new InputFile("Cleanup.java"); //need to close in.nextLine()
ReadFile and convert to stream
// 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
Integer[] arr = {1,2,3,4,5}; Stream.of(arr); Arrays.stream(arr);
- Group employees of a company and group them by their facility
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); }
- Join string
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](/_next/image?url=https%3A%2F%2Fwww.notion.so%2Fimage%2Fhttps%253A%252F%252Fs3-us-west-2.amazonaws.com%252Fsecure.notion-static.com%252F177adbec-0137-4f4d-af22-31c25068af2d%252FUntitled.png%3Ftable%3Dblock%26id%3Df8649002-cbb5-407f-b70e-291a525fed83%26cache%3Dv2&w=3840&q=75)
Once the stream has been ended, we can’t execute it again.
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 |
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
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); }
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. |
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. |
- Check a string is between two length
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
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 |
![notion image](/_next/image?url=https%3A%2F%2Fwww.notion.so%2Fimage%2Fhttps%253A%252F%252Fs3-us-west-2.amazonaws.com%252Fsecure.notion-static.com%252F720ea124-1acb-42cc-a825-e214f85cfd06%252FUntitled.png%3Ftable%3Dblock%26id%3De4a5a15e-275c-4a70-b404-410e47499051%26cache%3Dv2&w=3840&q=75)
Difference between Collection and Collections
The differences between a Collection and Collections are given below.
- A Collection is an interface, whereas Collections is a class.
- 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.
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
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
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
- Need to override compareTo() method to
- Custom Comparator
import java.util.Comparator; public class BrandComparator implements Comparator<Vehicle> { @Override public int compare(Vehicle o1, Vehicle o2) { return o1.brand.compareTo(o2.brand); } }
- Lambda function
LinkedList
![notion image](/_next/image?url=https%3A%2F%2Fwww.notion.so%2Fimage%2Fhttps%253A%252F%252Fs3-us-west-2.amazonaws.com%252Fsecure.notion-static.com%252F3ff5ccfb-f149-4a00-8d0a-bce38e065a0e%252FUntitled.png%3Ftable%3Dblock%26id%3Dc741f8af-2d2e-4319-a195-3ad904a5f4af%26cache%3Dv2&w=3840&q=75)
Internal implementation of linkedList
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
List list = new CopyOnWriteArrayList();
Difference between a HashSet and TreeSet
- The HashSet allows one null element, whereas a TreeSet does not allow a null element.
- The elements are stored in random order in a HashSet, whereas it is stored in sorted order in TreeSet.
- HashSet is faster than Treeset for the operations like add, remove, contains, size, etc.
// 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](/_next/image?url=https%3A%2F%2Fwww.notion.so%2Fimage%2Fhttps%253A%252F%252Fs3-us-west-2.amazonaws.com%252Fsecure.notion-static.com%252F28168c21-ca42-45b5-9653-3d36515f1812%252FUntitled.png%3Ftable%3Dblock%26id%3D41cf8605-1fdb-4990-a3c6-8ae182b7aad0%26cache%3Dv2&w=3840&q=75)
the class that is being used as a key must override both
equals()
and hashcode()
methods.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
- The entries in TreeMap are sorted in the natural ordering of its keys.
- It does not allow null keys, however there can be null values.
- The TreeMap is not thread-safe, although it can be made thread-safe using the
synchronizedMap()
method of the Collections class.
![notion image](/_next/image?url=https%3A%2F%2Fwww.notion.so%2Fimage%2Fhttps%253A%252F%252Fs3-us-west-2.amazonaws.com%252Fsecure.notion-static.com%252F0eaa997f-0d6b-4884-899a-20284624cae5%252FUntitled.png%3Ftable%3Dblock%26id%3D840deb45-5aeb-4c81-bb4d-4a8b3904bc21%26cache%3Dv2&w=3840&q=75)
LinkedHashMap
Store the elements in a Map in insertion order
- It does not allow duplicate keys.
- It may have one null key and multiple null values.
- It is non-synchronized.
![notion image](/_next/image?url=https%3A%2F%2Fwww.notion.so%2Fimage%2Fhttps%253A%252F%252Fs3-us-west-2.amazonaws.com%252Fsecure.notion-static.com%252F19ac0bdb-3e77-44cb-889a-80441861536a%252FUntitled.png%3Ftable%3Dblock%26id%3D8d7df00d-6a3d-41be-814e-6a2de3f55845%26cache%3Dv2&w=3840&q=75)
// 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
.- 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 aConcurrentHashMap
, only a segment of the Map is locked. Two parallel threads can access or update elements in a different segment, so it performs better.
SynchronizedMap
returns Iterator, which fails fast on concurrent modification.ConcurrentHashMap
doesn’t throw aConcurrentModificationException
if one thread tries to modify it while another is iterating over it.
ConcurrentHashMap
does not allow null keys or null values whileSynchronizedMap
allows one null key.
IdentityHashMap
are:- The
IdentityHashMap
stores the elements in random order.
- The
IdentityHashMap
allows a single null key.
- The
IdentityHashMap
is not thread-safe.
The differences between a
HashMap
and IdentityHashMap
.IdentityHashMap
uses reference equality to compare keys and values while HashMap uses object equality to compare keys and values.
IdentityHashMap
does not use thehashCode()
method. Instead it usesSystem.identityHashCode()
to find the bucket location.
- IdentityHashMap does not require keys to be immutable as it does not rely on the
equals()
andhashCode()
methods. To safely store the object inHashMap
, keys must be immutable.
- The default initial capacity of
HashMap
is 16; whereas, for IdentityHashMap, it is 32.
EnumMap
EnumMap
does not allow null keys, but it allows null values.
- 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.
- The
EnumMap
is not synchronized.
- All keys of each
EnumMap
instance must be keys of a single Enum type.
- 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.
- Java
EnumMap
implementation provides constant-time performance for the basic operations (like get and put).
The
binarySearch()
method are:- The array that is passed to the method should be sorted. If the array is not sorted, then the result is undefined.
- 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.
- If the array contains multiple elements with the specified value, there is no guarantee which one will be found.
- 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:
unmodifiableList(List<? extends T> list)
unmodifiableSet(Set<? extends T> s)
unmodifiableMap(Map<? extends K, ? extends V> m)
unmodifiableCollection(Collection<? extends T> c)
unmodifiableSortedMap(SortedMap<K,? extends V> m)
unmodifiableSortedSet(SortedSet<T> s)
The Collections class provides us with the following methods that can be used to make our existing collection thread-safe.
synchronizedCollection(Collection<T> c)
synchronizedList(List<T> list)
synchronizedMap(Map<K,V> m)
synchronizedSet(Set<T> s)
synchronizedSortedMap(SortedMap<K,V> m)
synchronizedSortedSet(SortedSet<T> s)
not taken any argument(
Supplier
), not returned any value(Consumer
), or returned only a boolean(Predicate
).![notion image](/_next/image?url=https%3A%2F%2Fwww.notion.so%2Fimage%2Fhttps%253A%252F%252Fs3-us-west-2.amazonaws.com%252Fsecure.notion-static.com%252F7d823e74-ab9d-4918-bb24-6fecf6296fd2%252FUntitled.png%3Ftable%3Dblock%26id%3D6e559f39-1b51-4356-bf6a-29372b395738%26cache%3Dv2&w=3840&q=75)
A non-final, local variable whose value is never changed after initialization is known as effectively final.
Exception
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
ThreadLocalRandom.current().nextInt();
Parallel programming
Create process
- The
Runtime.exec()
method can be used to create a new process in Java. It takes the command and arguments as input and returns theProcess
object.
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:
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
- Create a new thread by extending the
Thread
class and overriding therun()
method. Then, create an instance of the new class and call thestart()
method.
class MyThread extends Thread { @Override public void run() { // code to be executed in this thread } } MyThread thread = new MyThread(); thread.start();
- Implement the
Runnable
interface and pass an instance of the new class to theThread
constructor. Then, call thestart()
method.
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
ExecutorService executorService = Executors.newFixedThreadPool(100); executorService.execute(() -> System.out.println("aaaa")); executorService.shutdown();
schedular tasks
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); } }
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
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. class MyThread extends Thread { private Object lock; public MyThread(Object lock) { this.lock = lock; } @Override public void run() { synchronized (lock) { // critical section } } }
Lock
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. 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
.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:
- Initialize with a specified integer (number of counters).
- The main thread executes the latch.await(); method, waiting for other threads to complete. Other threads must call latch.countDown(); to decrement the counter.
- The main thread's code following latch.await(); can only continue executing when the counter value reaches 0.
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.
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.
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
+----------------+-----------------+------------------+------------+------------+---------------+ | 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
- 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.
- 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:
jstack <PID> > thread_dump.txt
- 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.
To search for deadlocks, you can use the
grep
command: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.
Reflection
- Get the declared methods of a class
Class<?> listClass = Class.forName("java.util.ArrayList"); Method[] methods = listClass.getMethods(); for(Method method : methods) { System.out.println(method.getName()); }
- Initialize an object
Class<?> listClass = Class.forName("java.util.ArrayList"); ArrayList<String> list = (ArrayList<String>) listClass.getConstructor().newInstance(); list.add("a"); list.add("b");
- invoke methods
Class<?> listClass = Class.forName("java.util.ArrayList"); ArrayList<String> list = (ArrayList<String>) listClass.getConstructor().newInstance(); Method addMethod = listClass.getMethod("add", Object.class); addMethod.invoke(list, "a"); addMethod.invoke(list, "b");
- access and change field
Class<?> listClass = Class.forName("java.util.ArrayList"); ArrayList<String> list = (ArrayList<String>) listClass.getConstructor().newInstance(); Field sizeField = listClass.getDeclaredField("size"); sizeField.setAccessible(true); //Not work after java 1.8 System.out.println(sizeField.getInt(list));