Concurrency using Java APIs

by | Dec 5, 2021 | code fussion

Concurrency is a difficult subject to grasp, and most developers prefer to just choose a library and use it without knowing the underlying logic. Nearly every computer nowadays has at least eight CPUs. This implies you can run eight tasks at once. But are we writing programs that are concurrent?

What is concurrency

Concurrency refers to the ability to run many threads and processes at the same time. A thread is a single unit of execution. Of course, with a single-core CPU system, only one task is actually executing at a given time. Even in multicore or multi-CPU systems, there are often far more threads than CPU processors available. The operating systems use a thread scheduler to determine which threads should be currently executing.

Why do we use Concurrency?

Computers are capable of reading and writing data to external resources. Unfortunately, as compared to CPU operations, these disk/network operations tend to be extremely slow—so slow, in fact, that if your computer’s operating system were to stop and wait for every disk or network operation to finish, your computer would appear to freeze or lock up constantly.

Concurrency is not restricted to disk or network operations; any activity that takes a long time and is independent can be done on its own thread.

The value of a multithreaded environment is best understood in contrast to its counterpart. Single-threaded systems use an approach called an event loop with polling. In this model, a single thread of control runs in an infinite loop, polling a single event queue to decide what to do next. Once this polling mechanism returns with, say, a signal that a network file is ready to be read, then the event loop dispatches control to the appropriate event handler. Until this event handler returns, nothing else can happen in the program. This wastes CPU time. It can also result in one part of a program dominating the system and preventing any other events from being processed.

The benefit of Java’s multithreading is that the main loop/polling mechanism is eliminated. One thread can pause without stopping other parts of your program. For example, the idle time created when a thread reads data from a network or waits for user input can be utilized elsewhere. Multithreading allows animation loops to sleep for a second between each frame without causing the whole system to pause. When a thread blocks in a Java program, only the single thread that is blocked pauses. All other threads continue to run.

Multithreading

A multithreaded program is made up of two or more components that can execute at the same time. A thread is a component of such a program, and each thread defines a distinct execution path. As a result, multithreading is a subset of multitasking.

Process-based and thread-based multitasking are the two forms of multitasking.

Process-based multitasking is a feature that allows you to run two or more programs at the same time on your computer. Process-based multitasking, for example, allows you to run the Java compiler while also using a text editor or browsing the internet. A program is the smallest unit of code that the scheduler may dispatch in process-based multitasking.

The thread is the smallest unit of dispatchable code in a thread-based multitasking system. This means that a single software may handle several jobs at the same time. For example, a text editor can format text while it is printing, as long as the two activities are carried out by two different threads. Process-based multitasking is concerned with the “big picture,” whereas thread-based multitasking is concerned with the intricacies.

Creating Threads with the Concurrency API

The Concurrency API includes the ExecutorService interface, which defines services that create and manage threads for you.

You first obtain an instance of an ExecutorService interface, and then you send the service tasks to be processed. The framework includes numerous useful features, such as thread pooling and scheduling.

ExecutorSevice service = Executors.newSingleThreadExecutor();

Submitting a task

There are about 4 methods you can use to submit a task

The submit() method has the obvious advantage of doing the same thing execute() does, but with a return object that can be used to track the result. Because of this advantage and the fact that execute() does not support Callable expressions, we tend to prefer to submit() over execute(),

Waiting for Results

We use a future object to determine the state of a task. The Future Object has several methods that help us achieve this.

Introducing Callable

The java.util.concurrent.The callable functional interface is similar to Runnable except that its call() method returns a value and can throw a checked exception. You use callable objects while submitting tasks.

@FunctionalInterface public interface Callable<V> {
V call() throws Exception;
}

Putting it all together

import java.util.concurrent.*;
public class AddData {
public static void main(String[] args) throws Exception {
ExecutorService service = null;
try {
service = Executors.newSingleThreadExecutor();
Future<Integer> result = service.submit(() -> 30 + 11);
System.out.println(result.get()); // 41
} finally {
if(service != null) service.shutdown();
}
}
}

Scheduling Tasks

Like ExecutorService, we obtain an instance of ScheduledExecutorService using a factory method in the Executors class, as shown in the following snippet:

ScheduledExecutorService service= Executors.newSingleThreadScheduledExecutor();

The scheduleAtFixedRate() method is useful for tasks that need to be run at specific intervals. On the other hand, the scheduleWithFixedDelay() method creates a new task only after the previous task has finished.

ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor();
Runnable task1 = () -> System.out.println("Hello Zoo");
Callable<String> task2 = () -> "Monkey";
ScheduledFuture<?> r1 = service.schedule(task1, 10,TimeUnit.SECONDS);
ScheduledFuture<?> r2 = service.schedule(task2, 8, TimeUnit.MINUTES);

Increasing Concurrency with Pools

The difference between a single-thread and a pooled-thread executor is what happens when a task is already running. While a single-thread executor will wait for a thread to become available before running the next task, a pooled-thread executor can execute the next task concurrently. If the pool runs out of available threads, the task will be queued by the thread executor and wait to be completed.

In practice, choosing an appropriate pool size requires some thought. In general, you want at least a handful more threads than you think you will ever possibly need. On the other hand, you don’t want to choose so many threads that your application uses up too many resources or too much CPU processing power. Oftentimes, the number of CPUs

Runtime.getRuntime().availableProcessors()

Writing Thread-Safe Code

Thread safety is the property of an object that guarantees safe execution by multiple threads at the same time. Since threads run in a shared environment and memory space, how do we prevent two threads from interfering with each other? We must organize access to data so that we don’t end up with invalid or unexpected results.

Protecting Data with Atomic Classes

Atomic is the property of an operation to be carried out as a single unit of execution without any interference by another thread.

Each class includes numerous methods that are equivalent to many of the primitive built-in operators that we use on primitives, such as the assignment operator (=) and the increment operators (++).

private AtomicInteger sheepCount = new AtomicInteger(0);
private void incrementAndReport() {
System.out.print(sheepCount.incrementAndGet()+" ");
}

Improving Access with Synchronized Blocks

While atomic classes are great at protecting single variables, they aren’t particularly useful if you need to execute a series of commands or call a method.

The most common technique is to use a monitor, also called a lock, to synchronize access. A monitor is a structure that supports mutual exclusion, which is the property that at most one thread is executing a particular segment of code at a given time.

private void incrementAndReport() {
synchronized(this) {
System.out.print((++sheepCount)+" ");
}
} private synchronized void incrementAndReport() {
System.out.print((++sheepCount)+" ");
}

Using the Lock interface

To review, the ReentrantLock class supports the same features as a synchronized block, while adding a number of improvements.

  • Ability to request a lock without blocking
  • Ability to request a lock while blocking for a specified amount of time
  • A lock can be created with a fairness property,
Lock lock = new ReentrantLock();
if(lock.tryLock()) {
try {
lock.lock();
System.out.println("Lock obtained, entering protected code");
} finally {
lock.unlock();
}
}

0 Comments

Submit a Comment

Written by Lee N

Lee N is a Certified System Architect, Certified Cloud Engineer, Certified Oracle Database Programmer, and RedHat Administrator II Expert with 5 years of experience in designing, developing, and monitoring web-based systems.

We are Experts in Data Modeling & Intelligent Systems

We develop fast, secure, & reliable systems to simplify business transactions and model data to discover useful information for business decision making. read more

Related Articles

Sealed Classes and Interfaces

Sealed Classes and Interfaces

Sealed classes and interfaces are most applicable to developers of API libraries in which subclasses and subinterfaces must be strictly controlled.

read more

Stay Up to Date With The Latest Technology Updates

Lenhac Limited

Developing world-class software solutions designed to meet your specialized needs to streamline your business operations.

Join Our Newsletter

Stay updated on Ways to Leverage Your Network for Rapid Business Growth using technology

Follow Us

Let’s get social

×