Java Concurrent Programming

Keywords: Java Eclipse

Learning multithreading is to better optimize the program and improve the overall running efficiency of the program

Inherit Thread class

Thread class

Inherit the Thread class of Java, implement the Thread class, and override the run() method of the parent class

public class Person extends Thread {
    @Override
    public void run() {
        try {
            System.out.println(getName() + " Start withdrawing money");
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(getName() + " Withdrawal completed");
    }
}

The function of thread class is to complete a relatively independent task. Here, Thread.sleep(200) is used to simulate the withdrawal process. The sleep() method is to make the thread sleep, temporarily stop executing, hand over the CPU, and let the CPU perform other tasks. The sleep method parameter is the number of milliseconds

Run thread

The thread needs to call the start() method to start

public class Bank {
    public static void main(String[] args) {
        Person thread1 = new Person();
        thread1.setName("Zhang San");

        Person thread2 = new Person();
        thread2.setName("Li Si");

        thread1.start();
        thread2.start();
    }
}

The Thread parent class has a name attribute, but it is private, so you can call the setName() method to set the name of the Thread. You can know which Thread is running through getName()

The run() method of thread class is executed after the system calls start(). Programming does not need to call the run method, but it is impossible to know when the system calls it

Implement Runnable interface

Because Java is a single inheritance, the program scalability is greatly reduced after inheriting the Thread class, so priority is given to the implementation of java.lang.Runnable

public class Person implements Runnable {
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public void run() {
        try {
            System.out.println(name + " Start withdrawing money");
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(name + " Withdrawal completed");
    }
}

Whether it is the Runnable interface or the Thread class, the run() method is automatically executed by the system in a timely manner

The Thread class that implements the Runnable interface also needs to be wrapped in the instance of the Thread class

public class Bank {
    public static void main(String[] args) {
        Person person1 = new Person();
        person1.setName("Zhang San");
        Thread thread1 = new Thread(person1);

        Person person2 = new Person();
        person2.setName("Li Si");
        Thread thread2 = new Thread(person2);

        thread1.start();
        thread2.start();
    }
}

Thread instance (new Thread(person1)) is equivalent to scheduler, which triggers thread task execution. Tasks cannot be started by themselves and need to be scheduled

Thread safety

Thread.currentThread() returns the instance object of the currently running thread.

When multiple threads operate on the same resource, a conflict occurs, which is called thread insecurity

Use the synchronized keyword in Java to solve the problem of disordered margin:

public class Ticket {
    public synchronized void sell() {
        count--;
        System.out.println(Thread.currentThread().getName() + ": Sell one and there's still one left " + count + " Ticket");
    }
}

synchronized is also called thread synchronization lock, which means that this method is locked. Only one thread can execute this method at the same time. The thread is waiting for the next lock at a time

synchronized is equivalent to protecting key methods. It is not allowed to execute at the same time. One must be executed

synchronized usage scenarios

Using the synchronized method means that the safety features of two threads are met:

  1. Atomicity: all methods are executed and the execution process will not be interrupted by any factors
  2. Visibility: when multiple threads access the same variable, one thread modifies the value of the variable, and other threads can immediately see the modified value

synchronized usage scenarios:

  • Scenarios for writing operations, such as modifying personal information, liking, collecting, placing orders, etc
  • Lock the smallest code block as accurately as possible, and abstract the most critical write operations into independent methods for locking

Pessimistic lock and optimistic lock

java.util.concurrent is a concurrent programming package provided by the java system

import java.util.concurrent.atomic.AtomicInteger;

public class Ticket {
    //Set the integer of the instance to 30
    private AtomicInteger count = new AtomicInteger(30);

    public synchronized void sell() {
        int newCount = 0;
        if (count.get() > 0) {
        //count.decrementAndGet() replaces count--
            newCount = count.decrementAndGet();
        }
        System.out.println(Thread.currentThread().getName() + ": Remaining " + newCount + " Ticket");
    }

    public int getCount() {
        return count.get();
    }
}

sell() here is no longer locked, which solves the problem of duplicate ticket allowance. Although AtomicInteger is a class, it is equivalent to an integer. You can specify any integer value when calling the new AtomicInteger() constructor to instantiate the object

AtomicInteger provides a method to ensure the atomicity of data operations without using synchronized, such as decrementAndGet():


It is a combination of three operations. In the case of multithreading, there will be no error of repeated values, which ensures the correctness of the data

The subtraction of one between threads is based on the latest results, so it will not be repeated. This is the embodiment of visibility

However, because the sell method does not lock, multiple statements may be interrupted by other threads during execution, so it is necessary to add synchronized to the whole of sell() to ensure the atomicity of multiple statements as a whole

incrementAndGet() is also a method that ensures the atomicity of the operation without using synchronized:

Although increasing and decreasing are multiple steps, in a multithreaded scenario, other threads will not wait, but will judge whether other threads have modified the data when the data changes. If so, they will modify it according to the latest value, which is optimistic lock

Optimistic lock is not locked. It is always guaranteed to update based on the latest data. Because it is not locked, the performance is improved. For example, the incrementAndGet() and decrementAndGet() methods of AtomicInteger class are optimistic locks

Assuming that other threads will modify the data, be careful in advance. The idea of locking is pessimistic, so it is called pessimistic locking

difference

Optimistic lock:
It is not applicable to scenarios where multiple pieces of data need to be modified and the overall sequence of multiple operations is very strict. Optimistic lock is applicable to application scenarios with a larger proportion of read data

Pessimistic lock:
It is suitable for application scenarios with a larger proportion of write data

Concurrent container

Scenario: multiple tasks have a sequence, but subsequent tasks do not have to wait for all the preceding tasks to be executed after they are completed. Instead, after each preceding task is completed, the corresponding subsequent task is automatically executed.

In this scenario, it is suitable to use the completable future feature

CompletableFuture

When a method is called, you need to wait for the return to get the return value, which is synchronous, and continuing to execute the task without waiting is asynchronous. Asynchronous mode can support parallel execution of multiple tasks. This mechanism is called concurrency

The completabilefuture. Supplyasync () method runs an asynchronous task and returns the result, so the method inside must have a return value

The entire () - > reg. Regid (s) expression statement of the supplyAsync() method parameter is wrapped in another object, which implements the Runnable interface

CompletableFuture.supplyAsync(
          // Each student is registered with a student number
          () -> reg.regId(s)
        )
         // After the student number is registered, print the welcome message
        .thenAccept(student -> {
          System.out.println("Hello " + student.getName() + ", Welcome to the big family of Chunlei middle school");
        });

Function of supplyAsync() method: execute REG and regid (s) statements in a single thread, which is essentially multithreaded programming

After registration, use the thenAccept() method to complete the steps of subsequent tasks. The parameter (sutdent) of the thenAccept() method is the result returned by the predecessor task. The student - > {} subsequent tasks are automatically executed. The subsequent tasks are also executed in a multithreaded manner. The thenAccept() method is usually used at the end of the task chain

Multistep task

supplyAsync() is used at the beginning, thenAccept() is used at the end, and can be called once respectively. There are multiple steps in the middle, and thenApply() can be called multiple times. Since the previous instance object is also used at the end, the thenApply method in the middle always needs to return the instance. Otherwise, the next step cannot be obtained:

  • Multiple tasks are parallel, and multiple threads are used to execute multiple tasks at the same time
  • Multiple steps of a task are serial. The previous step must be executed before the next step can be executed

Return value

supplyAsync(),thenApply(),thenAccept() returns the completabilefuture instance object. You can set the return value:

CompletableFuture<Void> cf = CompletableFuture.supplyAsync(() -> reg.regId(s))
  .thenApply(student -> {
    return dis.assignClasses(student);
  })
  .thenAccept(student -> {
     System.out.println("full name:" + student.getName() + ",Student No.:" + student.getId() + ",Class number:" + student.getClassId());
  });

The type of the defined variable is completable future. You can use the generic completable future < > to indicate the specific data type contained in it

If the end of the thenAccept() method is not called

CompletableFuture.supplyAsync(() -> reg.regId(s))
  .thenApply(student -> {
    return dis.assignClasses(student);
  });

Because the thenApply()lambda expression returns a Student object, the completabilefuture instance object contains Student data, so the generic return is completabilefuture < Student >

The data type of the completable future instance returned by these methods depends on the type of the returned value of the Lambda expression

Problems with the main() method

List<CompletableFuture> cfs = new ArrayList<>();
studentList.forEach(s -> {
  CompletableFuture<Void> cf = CompletableFuture.supplyAsync(() -> reg.regId(s))
    .thenApply(student -> {
        return dis.assignClasses(student);
    }).thenAccept(student -> {
        System.out.println("full name:" + student.getName() + ",Student No.:" + student.getId() + ",Class number:" + student.getClassId());
    });

  cfs.add(cf);
});

try {
  // Wait for all threads to complete execution
  CompletableFuture.allOf(cfs.toArray(new CompletableFuture[] {})).get();
} catch (Exception e) {
  e.printStackTrace();
}

Completabilefuture. allOf() is a static method used to collect all task instance objects. allOf() method only supports arrays and does not support collections. Therefore, to convert cfs.toarray (New completabilefuture [] {}), and then call the class method get(), the function is to wait for all task threads (allOf() Collection) to execute

When servers such as SpringBoot run supplyAsync() asynchronous task choreography, it is not necessary to use the get() method to wait for all thread tasks to be executed. The server is often a resident program, rather than exiting the program after the main() method is executed

The get() method causes the main() method to wait, so it is synchronous. The tasks arranged through completable future will not cause the main() method to wait, which is asynchronous

Wrapper class for security Booleans

java.util.concurrent.atomic.AtomicBoolean can manipulate Boolean values atomically

  • new AtomicBooolean(true) is true
  • new AtomicBooolean(false) is false

Get Boolean value using get() method:

AtomicBoolean ab = new AtomicBoolean(true);
boolean value = ab.get();

The instance object can modify the value atomically by calling the compareAndSet() method
compareAndSet(true,false) determines that the current value is true, modifies it to false, and then returns success or failure. These are three steps

  • If the modification is successful, return true
  • If modification fails, false is returned

The return value of compareAndSet() method is independent of the parameter value

Thread pool

In order to optimize the reuse of Thread objects, it is not necessary to create new objects every time. You can use java thread pool

As the name suggests, a thread pool is like a pool full of threads. Threads can be reused. A thread can execute a task or B task. Threads do not have to be created and destroyed frequently

The thread pool is also limited, the number of threads is also limited, and the number of tasks that can run at the same time is also limited


Core code for creating thread pool:

import org.apache.commons.lang3.concurrent.BasicThreadFactory;

import java.util.concurrent.*;

public class StudentIDTest {

  // Thread factory
  private static final ThreadFactory namedThreadFactory = new BasicThreadFactory.Builder()
    .namingPattern("studentReg-pool-%d")
    .daemon(true)
    .build();

  // Waiting queue
  private static final BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<Runnable>(1024);

  // Thread pool service
  private static final ThreadPoolExecutor EXECUTOR_SERVICE = new ThreadPoolExecutor(
        20,
        200,
        30,
        TimeUnit.SECONDS,
        workQueue,
        namedThreadFactory,
        new ThreadPoolExecutor.AbortPolicy()
      );

  public static void main(String[] args) {

  }
}

The BasicThreadFactory here requires a dependent Library:

<dependency>
  <groupId>org.apache.commons</groupId>
  <artifactId>commons-lang3</artifactId>
  <version>3.10</version>
</dependency>

Creating a thread pool is a fixed writing method

Create thread factory

new BasicThreadFactory.Builder()
  .namingPattern("studentReg-pool-%d")
  .daemon(true)
  .build();

The namingPattern() method defines the format of the thread name, which is equivalent to the thread name template. The studentReg here is the task name

Create thread wait queue instance

The performance is good. There are many CPU cores, and the memory queue can be larger: the new linkedblockingqueue < runnable > (2048) parameter indicates the number of tasks that can be queued

Queues with fewer CPU s are smaller: new linkedblockingqueue < runnable > (512)

Create thread pool instance

Multithreaded programming can adopt the idea of batch processing to prevent the system from crashing due to too many threads

Execute the execute() method of the thread pool object and pass in the instance object that implements the Runnable interface

// Pass in the Runnable object to run the task
  EXECUTOR_SERVICE.execute(register);

Thread pool and concurrency container

Completable future also uses a thread pool internally. In fact, it puts tasks into the internal default thread pool. Similarly, completable future can also specify a thread pool to run tasks:

CompletableFuture.supplyAsync(
    () -> reg.regId(s),
    EXECUTOR_SERVICE
  )

The supplyAsync() method can have a second parameter passed into the thread pool object built by itself

Specifies the scenario for the thread pool

Generally, the default thread pool of completable future is used. When the task execution is slow and the task accumulates, it is necessary to specify the thread pool and adjust the thread pool parameters

Posted by PHPisFUN on Thu, 07 Oct 2021 18:06:21 -0700