Jump to content

Java concurrency

From Alfawiki
Java
IndustrySoftware, Programming languages
Founded1995; 31 years ago (1995)
FounderJames Gosling, Mike Sheridan, Patrick Naughton
Headquarters
Mountain View, California
,
United States
ProductsJava programming language, Java Virtual Machine (JVM)
OwnerOracle Corporation
Websitewww.oracle.com/java/

The Java language along with the Java virtual machine (JVM) are engineered to facilitate concurrent programming. Execution takes place inside threads, which have access to shared resources and objects. To maintain thread safety and avoid data corruption, developers must synchronize read and write operations. Java provides native constructs to assist in managing this synchronization.

Processes and threads

Typically, JVMs operate within a single process. In Java, concurrency is mainly achieved through threads, which are lightweight processes. Running multiple processes necessitates launching several JVM instances.

Thread objects

Threads within the same process share resources such as memory and file handles, allowing efficient communication but increasing complexity. Every Java program begins execution with a primary thread, which can spawn additional threads by implementing Template:Javadoc:SE or Template:Javadoc:SE interfaces. The Callable interface is similar to Runnable, but it supports returning values and throwing checked exceptions.

Threads may be assigned to different CPU cores or time-shared across one or more processors. The manner in which Java threads correspond to native operating system threads varies among JVM implementations.

Each thread is associated with an instance of the Thread class and can be controlled either directly or through higher-level abstractions such as Executors or Tasks.

Starting a Thread

There are two common approaches to initiate a Thread:

Supplying a runnable object

<syntaxhighlight lang="java"> public class HelloRunnable implements Runnable {

   @Override
   public void run() {
       System.out.println("Hello from thread!");
   }
   public static void main(String[] args) {
       (new Thread(new HelloRunnable())).start();
   }

} </syntaxhighlight>

Extending the Thread class

<syntaxhighlight lang="java"> public class HelloThread extends Thread {

   @Override
   public void run() {
       System.out.println("Hello from thread!");
   }
   public static void main(String[] args) {
       (new HelloThread()).start();
   }

} </syntaxhighlight>

Interrupts

An interrupt is a signal sent to a thread to request it stop its current operation. This is done by invoking the interrupt() method on the target Thread object, which sets an internal flag known as the "interrupted status." When methods that throw InterruptedException exit, they clear this flag; however, it can be set again if another interrupt occurs.

Joins

Using the join() method, one Thread can pause its execution until another thread has finished running.

Exceptions

If an exception goes uncaught within a thread, that thread will terminate. While exceptions on the main thread are printed to standard output by default, user-created threads require an explicitly registered handler to process or log these exceptions.

Memory model

The Java memory model defines the rules for thread interactions through shared memory. To enhance performance, contemporary systems reorder instruction execution at various levels, including the compiler, CPU, and memory architecture. Java does not ensure linearizability or sequential consistency when multiple threads access shared objects, allowing optimizations such as register allocation and elimination of redundant reads by reordering memory operations.

Synchronization

Threads mainly coordinate by sharing access to object fields and their referenced data. Although this approach is efficient, it can lead to two main issues: thread interference and memory consistency errors. Proper synchronization is necessary to avoid these problems.

In multithreaded programs lacking adequate synchronization, reorderings may cause threads to perceive variable accesses in an order differing from the intended or executed sequence. Typically, threads run independently, but synchronization becomes critical when their actions must be coordinated.

Java employs monitors for thread synchronization, permitting only a single thread to execute within a synchronized section at any moment. Every object possesses an associated lock that governs access control.

Synchronization extends beyond mere mutual exclusion—ensuring exclusive ownership of a monitor by one thread at a time. It also enforces memory visibility guarantees: updates performed before or during a synchronized block become reliably visible to other threads synchronizing on the same monitor. Exiting such a block releases the monitor and flushes cached data to main memory, while acquiring it invalidates local caches to refresh variables with the latest values.

When a field is declared volatile or accessed under a unique lock held by all readers and writers, its reads and writes are considered linearizable.

Locks and synchronized blocks

Mutual exclusion is implemented either by entering a synchronized method or block that implicitly acquires an object’s lock, or by explicitly obtaining a lock such as ReentrantLock from the package. Both approaches ensure consistent memory semantics. If all accesses to a variable are guarded by the same lock, read and write operations become atomic and linearizable.

Volatile fields

The Java volatile modifier guarantees:

  1. (Applicable to all Java versions) A global ordering of reads and writes on a volatile variable, ensuring that each thread observes the latest value rather than a cached copy. However, it does not enforce ordering between volatile accesses and ordinary reads/writes, which limits its threading effectiveness.
  2. (Since Java 5) Reads and writes of volatile variables establish a happens-before relationship, analogous to acquiring and releasing locks, ensuring that memory updates performed by one action are visible to subsequent actions.

Fields marked with volatile behave in a linearizable manner: reading one invalidates the thread’s working memory and reloads from main memory (similar to lock acquisition), while writing immediately commits changes to main memory (similar to lock release).

Final fields

A final field cannot be modified once it has been initialized, which typically takes place within the constructor. Assuming the this reference is not exposed during construction, other threads can reliably observe the correct final values without requiring synchronization.

History

The Java collections framework has been part of Java since JDK 1.2.

Doug Lea, a principal architect behind the collections framework, created a concurrency-focused package that included synchronization primitives and collection classes designed for concurrent use. This package entered maintenance status with J2SE 5.0 as its elements were standardized and enhanced within java.util.concurrent. This progression was guided by JSR 166, led by Lea.

With the release of JDK 5.0, significant improvements to the Java concurrency model were introduced, incorporating JSR 166 concurrency APIs directly into the JDK. The JSR 133 specification established clearly defined atomic operations suitable for multithreaded programming.

Later versions, including Java SE 6 and Java SE 7, refined the JSR 166 APIs further and introduced additional concurrency capabilities.

See also

Bibliography

External links