Important points to remember or refresh before for core java interview
Foundations of OOP
• Encapsulation: Combining data and the functions operating on it as
a single unit.
• Abstraction: Hiding lower-level details and exposing only the
essential and relevant details tothe users.
• Inheritance: Creating hierarchical relationships between related
classes.
• Polymorphism: Interpreting the same message (i.e., method call)
with different meaningsdepending on the context.
Class
Foundations
• A “class” is a template (or blueprint) and an “object” is an
instance of a class.
• A constructor does not have a return type.
• You cannot access the private methods of the base class in
the derived class.
• You can access the protected method either from a class in
the same package (just like packageprivate or default) as well as from a
derived class.
• You can also access a method with a default access modifier
if it is in the same package.
• You can access public methods of a class from any other
class.
Overloading
• Method overloading: Creating methods with same name but
different types and/or numbers of parameters.
• You can have overloaded constructors. You can call a
constructor of the same class in another constructor usingthe this keyword.
• Overload resolution is the process by which the compiler
looks to resolve a call when overloaded definitions of a method are available.
Inheritance
• Inheritance is also called an “is-a” relationship.
• Resolving a method call based on the dynamic type of the
object is referred to as runtimepolymorphism.
• In overriding, the name of the method, number of arguments,
types of arguments, and returntype should match exactly.
• In covariant return types, you can provide the derived class
of the return type in the overriding method.
• You use the super keyword to call base class methods.
• Overloading is an example of static polymorphism (early
binding) while overriding is anexample of dynamic polymorphism (late binding).
• You don’t need to do an explicit cast for doing an upcast.
An upcast will always succeed.
• You need to do an explicit cast for doing a downcast. A
downcast may fail. So you can use theinstanceof operator to see if a downcast
is valid.
Java
Packages
• A package is a scoping construct to categorize your classes
and to provide namespacemanagement.
Abstract
Classes
• An abstraction specifying functionality supported without
disclosing finer level details.
• You cannot create instances of an abstract class.
• Abstract classes enable runtime polymorphism, and runtime
polymorphism in turn enables loose coupling.
Using
the “final” Keyword
• A final class is a non-inheritable class (i.e., you cannot
inherit from a final class).
• A final method is a non-overridable method (i.e., subclasses
cannot override a final method).
• All methods of a final class are implicitly final (i.e.,
non-overridable).
• A final variable can be assigned only once.
Using
the “static” Keyword
• There are two types of member variables: c lass variables
and instance variables. All variablesthat require an instance (object) of the
class to access them are known as instance variables.
All variables that are shared among all instances and are
associated with a class rather than an object are referred to as class
variables (declared using the static keyword).
• All static members do not require an instance to call/access
them. You can directly call/access them using the class name.
• A static member can call/access only a static member.
Flavors
of Nested Classes
• Java supports four types of nested classes: static nested
classes, inner classes, local inner classes, and anonymous inner classes.
• Static nested classes may have static members, whereas the
other flavors of nested classes can’t.
• Static nested classes and inner classes can access members
of an outer class (even private members). However, static nested classes can
access only static members of outer class.
• Local classes (both local inner classes and anonymous inner
classes) can access all variables declared in the outer scope (whether a
method, constructor, or a statement block).
Enums
• Enums are a typesafe way to achieve restricted input from
users.
• You cannot use new with enums, even inside the enum
definition.
• Enum classes are by default final classes.
• All enum classes are implicitly derived from java.lang.Enum.
Interfaces
• An interface is a set of abstract methods that defines a
protocol.
• An interface cannot be instantiated; however, an interface
can extend another interface.
• All methods declared in an interface are implicitly
considered to be abstract.
• Abstract class and interface are quite similar concepts.
However, you should be careful to use the appropriate construct based on the
context.
Object
Composition
• Inheritance implies is-a, interface implies is-like-a, and
composition implies has-a relationships.
• Favor composition over inheritance whenever feasible.
• Program to an interface, not to an implementation.
Design
Patterns
• Design patterns are reusable solutions of frequently
recurring design problems.
• The observer design pattern improves loose coupling between
subject and observers.
• The singleton design pattern ensures that only one instance
of the class is created.
• Making sure that an intended singleton implementation is
indeed singleton is a non-trivial task, especially in a multi-threaded
environment.
• The factory design pattern “manufactures” the required type
of product on demand.
• You should consider using the abstract factory design
pattern when you have a family of objects to be created.
• A DAO design pattern essentially separates your core
business logic from your persistence logic.
Generics
• Generics will ensure that any attempts to add elements of
types other than the specified type(s) will be caught at compile time itself. Hence,
generics offer generic implementation with type safety.
• Java 7 introduced diamond syntax where the type parameters
(after new operator and class name) can be omitted. The compiler will infer the
types from the type declaration.
• Generics are not covariant. That is, subtyping doesn’t work
with generics; you cannot assign a derived generic type parameter to a base
type parameter.
• The <?> specifies an unknown type in generics and is
known as a wildcard. For example, List<?> refers to list of unknowns.
• Wildcards can be bounded. For example, <? extends
Runnable> specifies that ? can match any type as long as it is Runnable or
any of its derived types. Note that extends is inclusive, so you can replace X
in ? extends X. However, in <? super Runnable> , ? would match only the
super types of Runnable, and Runnable itself will not match (i.e., it is an
exclusive clause).
• You use the extends keyword for both class type as well as
an interface when specifying bounded types in generics. For specifying multiple
base types, you use the & symbol. For example, in List<? extends X &
Y>, ? will match types, extending both the types X and Y.
Collections
Framework
• Avoid mixing raw types with generic types. In other cases,
make sure of the type safety manually.
• The terms Collection, Collections, and collection are
different.
Collection— java.util.Collection<E>—is the root
interface in the collection hierarchy.
Collections—java.util.Collections—is a utility class that
contains only static methods. The general term collection(s) refers to
containers like map, stack, queue, etc.
• The container classes store references to objects, so you
cannot use primitive types with any of the collection classes.
• The methods hashCode() and equals() need to be consistent
for a class. For practical purposes, ensure that you follow this one rule: the
hashCode() method should return the same hash value for two objects if the
equals() method returns true for them.
• If you’re using an object in containers like HashSet or
HashMap, make sure you override the hashCode() and equals() methods correctly.
• The Map interface does not extend the Collection interface.
• It is not recommended that you store null as an argument,
since there are methods in the Deque interface that return null, and it would
be difficult for you to distinguish between the success or failure of the
method call.
• Implement the Comparable interface for your classes where a
natural order is possible. If you want to compare the objects other than the
natural order or if there is no natural ordering present for your class type,
then create separate classes implementing the Comparator interface. Also, if
you have multiple alternative ways to decide the order, then go for the
Comparator interface.
Searching,
Parsing, and Building Strings
• You can use the overloaded versions of the method indexOf()
in the String class for forward searching in a string, lastIndexOf() for
backward searching a string, and regionMatches() for comparing a “region” of
text within a string.
• To convert from a primitive type value to String type
object, you can make use of the overloaded valueOf() method, which takes a
primitive type value as an argument and returns the String object. To convert
from the String type object to a primitive type value, you can make use of the
parse methods available for primitive types in the corresponding wrapper types
of the primitive types.
• For parsing a string, you can use the split() method
available in the String class. It takes a delimiter as an argument, and this
argument is a regular expression.
Regular
Expressions
• A regular expression defines a search pattern that can be
used to execute operations such as string search and string manipulation.
• Use the Pattern and Matcher classes whenever you are performing
search or replace on strings heavily; they are more efficient and faster than
any other way to perform search/replace in Java.
• You can form groups within a regex. These groups can be used
to specify quantifiers on a desired subset of the whole regex. These groups can
also be used to specify back reference.
String
Formatting
• The method printf() (and the method format() in the String
class) uses string formatting flags to format strings.
• Each format specifier starts with the % sign; followed by
flags, width, and precision information; and ending with a data type specifier.
In this string, the flags, width, and precision information are optional but
the % sign and data type specifier are mandatory.
Reading
and Writing Data to Console
• You can obtain a reference to the console using the
System.console() method; if the JVM is not associated with any console, this
method will fail and return null.
• Many methods are provided in Console-support formatted I/O.
You can use the printf() and format() methods available in the Console class to
print formatted text; the overloaded readLine() and readPassword() methods take
format strings as arguments.
• Use the readPassword() method for reading secure strings such as
passwords. It is recommended to use Array’s fill() method to “empty” the
password read into the character array (to avoid malicious access to the typed
passwords).
• The methods in the Console class have better support for special
characters compared to printing text through PrintStreams.
Read
and Write to Files with Streams
• The java.io package has classes supporting both character
streams and byte streams.
• You can use character streams for text-based I/O. Byte
streams are used for data-based I/O.
• Character streams for reading and writing are called readers
and writers respectively (represented by the abstract classes of Reader and
Writer).
• Byte streams for reading and writing are called input
streams and output streams respectively (represented by the abstract classes of
InputStream and OutputStream).
• You should only use character streams for processing text
files (or human-readable files), and byte streams for data files. If you try
using one type of stream instead of another, your program won’t work as you
would expect; even if it works by chance, you’ll get nasty bugs. So don’t mix
up streams, and use the right stream for a given task at hand.
• For both byte and character streams, you can use buffering.
The buffer classes are provided as wrapper classes for the underlying streams.
Using buffering will speed up the I/O when performing bulk I/O operations.
• For processing data with primitive data types and strings,
you can use data streams.
• Serialization: The process of converting the objects in
memory into a series of bytes.
• Persistence: The mechanism of storing objects in memory into
files.
• You can use object streams for object persistence (i.e.,
reading and writing objects in memory to files and vice versa).
Working
with the Path Class
• A Path object is a programming abstraction to represent a
path of a file/directory.
• You can get an instance of Path using the get() method of
the Paths class.
• Path provides two methods to use to compare Path objects:
equals() and compareTo(). Even if two Path objects point to the same file/directory,
it is not guaranteed that you will get true from the equals() method.
Performing
Operations on Files/Directories
• You can check the existence of a file using the exists()
method of the Files class.
• The Files class provides the methods isReadable(),
isWriteable(), and isExecutable() to check the ability of the program to read,
write, or execute programmatically.
• You can retrieve attributes of a file using the
getAttributes() method.
• You can use the readAttributes() method of the Files class
to read attributes of a file in bulk.
• The method copy() can be used to copy a file from one
location to another. Similarly, the method move() can be used to move a file
from one location to another.
• While copying, all the directories (except the last one if
you are copying a directory) in the specified path must exist to avoid
NoSuchFileException.
• Use the delete() method to delete a file; use the
deleteIfExists() method to delete a file only if it exists.
Walking
a File Tree
• The Files class provides two flavors of walkFileTree() to
enable you to walk through a file system.
• The FileVisitor interface allows you to perform certain
operations at certain key junctures.
• If you do not want to implement all four methods in the
FileVisitor interface, you can simply extend your implementation from the
SimpleFileVisitor class.
Finding
a File
• The PathMatcher interface is useful when you want to find a
file satisfying a certain pattern.
You can specify the pattern using glob or regex. Watching a Directory
for Changes
• Java 7 offers a directory watch service that can notify you
when the file you are working on is changed by some other program.
• You can register a Path object using a watch service along
with certain event types. Whenever any file in the specified directory changes,
an event is sent to the registered program.
Define
the Layout of the JDBC API
• JDBC (Java Database Connectivity) APIs provided by Java are
meant for programmatic access to DataBase Management Systems (DBMSs).
• JDBC hides all the heterogeneity of all the DBMSs and offers
a single set of APIs to interact with all types of databases.
• The complexity of heterogeneous interactions is delegated to
JDBC driver manager and
JDBC drivers; hence all the details and complications are
hidden by the JDBC API from the application developer.
• There are four types of drivers:
• Type 1 (JDBC-ODBC bridge drivers): JDBC driver calls ODBC (Open
Database Connectivity) native calls using the Java Native Interface (JNI).
• Type 2 (Native-API drivers): These drivers use client-side
libraries of a specific database and convert JDBC calls to native database
calls.
• Type 3 (Network-protocol drivers): These drivers call database
middleware and the middleware actually converts JDBC calls to database-specific
native calls.
• Type 4 (Native-protocol drivers): The driver directly makes
database-specific calls over the network without any support of additional
client-side libraries.
Connect
to a Database by Using a JDBC driver
• The java.sql.Connection interface provides a channel through
which the application and the database communicate.
• The getConnection() method in the DriverManager class takes
three arguments: the URL string, username string, and password string.
• The syntax of the URL (which needs to be specified to get
the Connection object) is
<protocol>:<subprotocol>://<server>:<port>/. An example
of a URL string is
jdbc:mysql://localhost:3306/. The <protocol> jdbc is
the same for all DBMSs;
<subprotocol> will differ for each DBMS, <server>
depends on the location in which you host the database, and each DBMS uses a
specific <port> number.
• If the JDBC API is not able to locate the JDBC driver, it
will throw a SQLException. If there are jars for the drivers available, they
need to be included in the classpath to enable the JDBC API to locate the
driver.
• Prior to JDBC 4.0, you would have to explicitly load the
JDBC driver using the
Class.forName() statement; with JDBC 4.0 and above, this
statement is not needed and the
JDBC API will load the driver from the details given in the
URL string.
Update
and Query a Database
• JDBC supports two interfaces for querying and updating:
Statement and Resultset.
• A Statement is a SQL statement that can be used to
communicate a SQL statement to the connected database and receive results from
the database. There are three types of Statements:
• Statement: You need to use Statement when you need to send a
SQL statement to the database without any parameter.
• PreparedStatement: Represents a precompiled SQL statement
that can be customized using IN parameters.
• CallableStatement: Used to execute stored procedures; can
handle IN as well as OUT and INOUT parameters.
• A ResultSet is a table with column heading and associated
values requested by the query.
• A ResultSet object maintains a cursor pointing to the
current row. Initially, the cursor is set to just before the first row; calling
the next() method advances the cursor position by one row.
• The column index in the ResultSet object starts from 1 (not from
0).
• You need to call updateRow() after modifying the row contents in a
ResultSet; otherwise changes made to the ResultSet object will be lost.
• By calling the getMetaData() method in the Connection interface,
you can examine the capabilities of the underlying database.
Customize
the Transaction Behavior of JDBC and Commit Transactions
• A transaction is a set of SQL operations that needs to be
either executed all successfully or not at all.
• Transaction-related methods are supported in the Connection
interface.
• By default auto-commit mode is set to true, so all changes
you make through the connection are committed automatically to the database.
• You can use setAutoCommit(false); to enable manual commits.
With auto-commit not enabled, you need to explicitly commit or rollback
transactions.
• If the commit() method does not execute in manual commit
mode, there will be no change in the database.
• You can divide a big transaction into multiple milestones.
These milestones are referred to as savepoints. This way you may save the
changes to a database up to a milestone once the milestone is achieved.
Use
the JDBC 4.1 RowSetProvider, RowSetFactory, and RowSet Interfaces
• RowSet is a special ResultSet that supports the JavaBean
component model.
• JdbcRowSet is a connected RowSet while other subinterfaces
of RowSet (i.e., JoinRowSet, CachedRowSet, WebRowSet, and FilteredRowSet) are
disconnected RowSets.
• RowSetProvider provides APIs to get a RowSetFactory
implementation, which can in turn be used to instantiate a relevant RowSet
implementation.
Introduction
to Exception Handling
• When an exception is thrown from a try block, the JVM looks
for a matching catch handler from the list of catch handlers in the method
call-chain. If no matching handler is found, that unhandled exception will
result in crashing the application.
• While providing multiple exception handlers (stacked catch
handlers), specific exception handlers should be provided before general
exception handlers. Providing base exception handlers before the derived
handlers will result in a compiler error.
• You can programmatically access the stack trace using the
methods such as printStackTrace() and getStackTrace(), which can be called on
any exception object.
• A try block can have multiple catch handlers. If the cause
of two or more exceptions is similar, and the handling code is also similar,
you can consider combining the handlers and make it into a multi-catch block.
• The code inside a finally block will be executed
irrespective of whether a try block has successfully executed or resulted in an
exception. This makes a finally block the most suitable place to release
resources, such as file handles, data base handles, network streams, etc.
Try-with-Resources
• Forgetting to release resources by explicitly calling the
close() method is a common mistake.
You can use a try-with-resources statement to simplify your
code and auto-close resources.
For a resource to be usable in a try-with-resources
statement, the class of that resource must implement the
java.lang.AutoCloseable interface and define the close() method.
• You can auto-close multiple resources within a
try-with-resources statement. These resources need to be separated by
semicolons in the try-with-resources statement header.
• Because you can use multiple resources within a
try-with-resources statement, the possibility of more than one exception
getting thrown from the try block and the finally block is high.
If a try block throws an exception, and a finally block also
throws exception(s), then the exceptions thrown in the finally block will be
added as suppressed exceptions to the exception that gets thrown out of the try
block to the caller.
Exception
Types
• The class Throwable is the root class of the exception
hierarchy. Only Throwable and its derived classes can be used with Java
exception handling keywords such as try, catch, and throws.
• The Exception class (except its sub-hierarchy of the
RuntimeException class) and its derived classes are known as checked
exceptions. These exceptions represent exceptional conditions that can be
reasonably expected to occur when the program executes, hence they must be
handled. A method that contains some code segment that can throw a checked
exception must either provide a catch handler to handle it or declare that
exception in its throws clause.
• The RuntimeException and Error classes and derived classes are
known as unchecked exceptions. They can be thrown anywhere in the program
(without being declared that the segment of code can throw these exceptions).
• The RuntimeException classes and derived classes represent
programming mistakes (logical mistakes) and are not generally expected to be
caught and handled in the program. However, in some cases, it is meaningful to
handle these exceptions in catch blocks.
• The Error classes and derived classes represent exceptions that
arise because of JVM errors; either the JVM has detected a serious abnormal
condition or has run out of resources. When an Error occurs, the typical best
course of action is to terminate the program.
• A catch block should either handle the exception or rethrow it. To
hide or swallow an exception by catching an exception and doing nothing is
really a bad practice.
Throws
Clause
• The throws clause for a method is meant for listing the
checked exceptions that the method body can throw.
• Static initialization blocks cannot throw any checked
exceptions. Non-static initialization blocks can throw checked exceptions;
however, all the constructors should declare that exception in their throws
clause.
• A method’s throws clause is part of the contract that its
overriding methods in derived classes should obey. An overriding method can
provide the same throw clause as the base method’s throws clause or a more
specific throws clause than the base method’s throws clause. The overriding
method cannot provide a more general throws clause or declare to throw
additional checked exceptions when compared to the base method’s throws clause.
Custom
Exceptions
• You can define your own exception classes (known as custom
exceptions) in your programs.
• It is recommended that you derive custom exceptions from
either the Exception or RuntimeException class. Creation of custom exceptions
by extending the Throwable class (too generic) or the Error class (exceptions
of this type are reserved for JVM and the Java APIs to throw) is not
recommended.
• You can wrap one exception and throw it as another
exception. These two exceptions become chained exceptions. From the thrown
exception, you can get the cause of the exception.
Assertions
• Assertions are condition checks in the program and are meant
to be used for explicitly checking the assumptions you make while writing
programs.
• The assert statement is of two forms: one that takes a
Boolean argument and one that takes an additional string argument.
• If the Boolean condition given in the assert argument fails
(i.e., evaluates to false), the program will terminate after throwing an
AssertionError. It is not advisable to catch and recover from when an
AssertionError is thrown by the program.
• By default, assertions are disabled at runtime. You can use
the command-line arguments of –ea (for enabling asserts) and –da (for disabling
asserts) and their variants when you invoke the JVM.
Read
and Set the Locale Using the Locale Object
• A locale represents a language, culture, or country; the
Locale class in Java provides an abstraction for this concept.
• Each locale can have three entries: the language, country,
and variant. You can use standard codes available for language and country to
form locale tags. There are no standard tags for variants; you can provide
variant strings based on your need.
• The getter methods in the Locale class—such as
getLanguage(), getCountry(), and getVariant()—return codes; whereas the similar
methods of getDisplayCountry(), getDisplayLanguage(), and getDisplayVariant()
return names.
• The getDefault() method in Locale returns the default locale
set in the JVM. You can change this default locale to another locale by using
the setDefault() method.
• There are many ways to create or get a Locale object
corresponding to a locale:
• Use the constructor of the Locale class.
• Use the forLanguageTag(String languageTag) method in the
Locale class.
• Build a Locale object by instantiating Locale.Builder and
then call setLanguageTag() from that object.
• Use the predefined static final constants for locales in the
Locale class.
Build
a Resource Bundle for Each Locale
• A resource bundle is a set of classes or property files that
help define a set of keys and map those keys to locale-specific values.
• The class ResourceBundle has two derived classes:
PropertyResourceBundle and ListResourceBundle. You can use
ResourceBundle.getBundle() to automatically load a bundle for a given locale.
• The PropertyResourceBundle class provides support for multiple
locales in the form of property files. For each locale, you specify the keys
and values in a property file for that locale.
You can use only Strings as keys and values.
• To add support for a new locale, you can extend the
ListResourceBundle class. In this derived class, you have to override the
Object [][] getContents() method. The returned array must have the list of keys
and values. The keys must be Strings, and values can be any objects.
• When passing the key string to the getObject() method to fetch the
matching value in the resource bundle, make sure that the passed keys and the
key in the resource bundle exactly match (the keyname is case sensitive). If
they don’t match, you’ll get a MissingResourceException.
• The naming convention for a fully qualified resource bundle name
is packagequalifier.bundlename + "_" + language + "_" +
country + "_" + (variant + "_#" | "#") + script +
"-" + extensions.
Load a
Resource Bundle in an Application
• The process of finding a matching resource bundle is same
for classes extended from ListResourceBundles as for property files defined for
PropertyResourceBundles.
• Here is the search sequence to look for a matching resource
bundle. Search starts from Step
1. If at any step the search finds a match, the resource
bundle is loaded. Otherwise, the search proceeds to the next step.
• Step 1: The search starts by looking for an exact match for
the resource bundle with the full name.
• Step 2: The last component (the part separated by _) is
dropped and the search is repeated with the resulting shorter name. This
process is repeated till the last locale modifier is left.
• Step 3: The search is restarted using the full name of the
bundle for the default locale.
• Step 4: Search for the resource bundle with just the name of
the bundle.
• Step 5: The search fails, throwing a MissingBundleException.
• The getBundle() method takes a ResourceBundle.Control object
as an additional parameter.
By extending this ResourceBundle.Control class and passing
that object, you can control or customize the resource bundle searching and
loading process.
Format
Text for Localization Using NumberFormat and DateFormat
• To handle date and time, numbers, and currencies in a culture-sensitive
way, you can use the
java.text.Format class and its two main derived classes
NumberFormat and DateFormat for that.
• The NumberFormat class provides support locale-sensitive
handling of numbers relating to how thousands are separated, treating a number
as a currency value, etc.
• The NumberFormat class provides methods to format or parse
numbers. “Formatting” means converting a numeric value to a textual form
suitable for displaying to users; “parsing” means converting a number back to
numeric form for use in the program. The parse() method returns a Number if
successful—otherwise it throws ParseException (a checked exception).
• NumberFormat has many factory methods: getInstance(),
getCurrencyInstance(),
getIntegerInstance(), and getPercentInstance().
• The Currency class provides support for handling currency values
in a locale-sensitive way.
• The DateFormat class provides support for processing date and time
in a locale-sensitive manner.
• The DateFormat has three overloaded factory methods—getDateInstance(),
getTimeInstance(), and getDateTimeInstance()—that return DateFormat
instances for processing date, time, and both date and time, respectively.
• SimpleDateFormat (derived from DateFormat) uses the concept of a
pattern string to support custom formats for date and time.
• You encode the format of the date or time using case-sensitive
letters to form a date or time pattern string.
Introduction
to Concurrent Programming
• You can create c lasses that are capable of multi-threading
by implementing the Runnable interface or by extending the Thread class.
• Always implement the run() method. The default run() method
in Thread does nothing.
• Call the start() method and not the run() method directly in
code. (Leave it to the JVM to call the run() method.)
• Every thread has a thread name, priority, and thread-group
associated with it; the default toString() method implementation in Thread
prints them.
• If you call the sleep() method of a thread, the thread does
not release the lock and it holds on to the lock.
• You can use the join() method to wait for another thread to
terminate.
• In general, if you are not using the “interrupt” feature in
threads, it is safe to ignore
InterruptedException; however it’s better still to log or
print the stack trace if that exception occurs.
• Threads execute asynchronously; you cannot predict the order
in which the threads run.
• Threads are also non-deterministic: in many cases, you
cannot reproduce problems like deadlocks or data races every time.
Thread
States
• There are three basic thread states: new, runnable, and
terminated. When a thread is just created, it is in a new state; when it is
ready to run or running, it is in a runnable state. When the thread dies, it’s
in terminated state.
• The runnable state has two states internally (at the OS
level): ready and running states.
• A thread will be in the blocked state when waiting to
acquire a lock. The thread will be in the timed_waiting state when a timeout is
given for calls like wait. The thread will be in the waiting state when, for
example, wait() is called (without a time out value).
• You will get an IllegalThreadStateException if your
operations result in invalid thread state transitions.
Concurrent
Access Problems
• Concurrent reads and writes to resources may lead to the
data race problem.
• You must use thread synchronization (i.e., locks) to access
shared values and avoid data races. Java provides thread synchronization
features to provide protected access to shared resources—namely, synchronized
blocks and synchronized methods.
• Using locks can introduce problems such as deadlocks. When a
deadlock happens, the process will hang and will never terminate.
• A deadlock typically happens when two threads acquire locks
in opposite order. When one thread has acquired one lock and waits for another
lock, another thread has acquired that other lock and waits for the first lock
to be released. So, no progress is made and the program deadlocks.
• To avoid deadlocks, it is better to avoid acquiring multiple
locks. When you have to acquire such multiple locks, ensure that they are
acquired in the same order in all places in the program.
The
Wait/Notify Mechanism
• When a thread has to wait for a particular condition or
event to be satisfied by another thread, you can use a wait/notify mechanism as
a communication mechanism between threads.
• When a thread needs to wait for a particular
condition/event, you can either call wait() with or without a timeout value
specified.
• To a oid notifications getting lost, it is better to always
use notifyAll() instead of notify().
Using
java.util.concurrent Collections
• A semaphore controls access to shared resources. A semaphore
maintains a counter to specify number of resources that the semaphore controls.
• CountDownLatch allows one or more threads to wait for a
countdown to complete.
• The Exchanger class is meant for exchanging data between two
threads. This class is useful when two threads need to synchronize between each
other and continuously exchange data.
• CyclicBarrier helps provide a synchronization point where
threads may need to wait at a predefined execution point until all other
threads reach that point.
• Phaser is a useful feature when few independent threads have
to work in phases to complete a task.
Applying
Atomic Variables and Locks
• Java pr ovides an efficient alternative in the form of atomic
variables where one needs to acquire and release a lock just to carry out
primitive operations on variables.
• A lock ensures that only one thread accesses a shared
resource at a time.
• A Condition supports thread notification mechanism. When a
certain condition is not satisfied, a thread can wait for another thread to
satisfy that condition; that other thread could notify once the condition is
met.
Using
Executors and ThreadPools
• The Executors hierarchy abstracts the lower-level details of
multi-threaded programming and offers high-level user-friendly concurrency
constructs.
• The Callable interface represents a task that needs to be completed
by a thread. Once the task completes, the call() method of a Callable
implementation returns a value.
• A thread pool is a collection of threads that can execute
tasks.
• Future represents objects that contain a value that is
returned by a thread in the future.
• ThreadFactory is an interface that is meant for creating
threads instead of explicitly creating threads by calling a new Thread().
Using
the Parallel Fork/Join Framework
• The Fork/Join framework is a portable means of executing a
program with decent parallelism.
• The framework is an implementation of the ExecutorService
interface and provides an easy-to-use concurrent platform in order to exploit
multiple processors.
• This framework is very useful for modeling
divide-and-conquer problems.
• The Fork/Join framework uses the work-stealing algorithm:
when a worker thread completes its work and is free, it takes (or “steals”)
work from other threads that are still busy doing some work.
• The work-stealing technique results in decent load balancing
thread management with minimal synchronization cost.
• ForkJoinPool is the most important class in the Fork/Join
framework. It is a thread pool for running fork/join tasks—it executes an
instance of ForkJoinTask. It executes tasks and manages their lifecycles.
• ForkJoinTask<V> is a lightweight thread-like entity
representing a task that defines methods such as fork() and join().