Thread pool, Lambda expression

Thread pool, Lambda expression

primary coverage

  • Waiting and Waking Cases
  • Thread Pool
  • Lambda expression

Chapter I Waiting for Wake-up Mechanism

1.1 Interthread Communication

**Concept: ** Multiple threads are working on the same resource, but the actions (tasks of threads) are different.

For example: Thread A is used to generate buns, Thread B is used to eat buns. The buns can be understood as the same resource. Thread A and Thread B process actions, one is production and the other is consumption. Thread communication problems exist between Thread A and Thread B.

Why handle inter-thread communication:

When multiple threads execute concurrently, by default the CPU switches threads randomly. When we need multiple threads to complete a task together and we want them to execute regularly, some coordinated communication is needed between the threads to help us achieve multi-threaded joint operation of a piece of data.

How to ensure efficient use of resources for inter-thread communication:

Threads need to communicate with each other to help resolve the use or operation of the same variable between threads when working on the same resource and tasks are different. It means that multiple threads avoid competing for the same shared variable when working on the same data. That is, we need to use resources effectively by certain means for each thread. This means waiting for the wake-up mechanism.

1.2 Waiting for wake-up mechanism

What is Waiting to Wake-up Mechanism

This is a collaboration mechanism between multiple threads. When it comes to threads, we often think about race s, like fighting for locks, but that's not the whole story. There's also a mechanism for collaboration between threads. Like you and your colleagues in the company, you may be competing for promotions, but more often you work together to accomplish certain tasks.

It is after a thread has performed a specified operation that it enters a wait() state and waits for other threads to execute their specified code before waking up (notify()); When there are multiple threads waiting, you can use notifyAll() to wake up all waiting threads if needed.

wait/notify is a collaboration mechanism between threads.

Method Waiting to Wake Up

Waiting for wake-up mechanism is used to solve the problem of communication between threads. The three methods used have the following meanings:

  1. Wait: The thread is no longer active, no longer participating in scheduling, and enters the wait set, so it does not waste CPU resources or compete for locks. The state of the thread is WAITING. It also waits for other threads to perform a special action, that is, "notify". The threads waiting on this object are released from the wait set and re-entered into the ready queue.
  2. notify: a thread in the wait set of the notified object is selected to release; For example, when a restaurant has a free place, the customer who waits for the longest meal first seats.
  3. notifyAll: Releases all threads on the wait set of the notified object.

Be careful:

Even if only one waiting thread is notified, the notified thread cannot immediately resume execution, because it originally interrupted in a synchronization block and now it no longer holds a lock, so she needs to try again to acquire the lock (which is likely to be competing with other threads) before execution can resume where the wait method was originally called.

The summary is as follows:

  • If a lock can be acquired, the thread changes from WAITING state to RUNNABLE state;
  • Otherwise, from wait set to entry set, the thread changes from WAITING state to BLOCKED state

Details to note when calling wait and notify methods

  1. The wait method and the notify method must be called by the same lock object. Because: the corresponding lock object can wake up the thread after the wait method called with the same lock object through notify.
  2. The wait method and the notify method are methods belonging to the Object class. Because: a lock object can be any object, and the class to which any object belongs inherits the Object class.
  3. The wait and notify methods must be used in the synchronization block or function. Because: these two methods must be called through the lock object.

1.3 Producer and Consumer Issues

Waiting for wake-up mechanism is actually a classic "producer and consumer" problem.

Take the production of stuffed buns and consumption of stuffed buns as an example of how the awakening mechanism can effectively utilize resources:

The bun shop thread produces buns and the food thread consumes buns. When there is no bun (bun status is false),The feeding thread waits, and the bun shop thread produces the bun (that is, the bun state is true),And notify the feeding thread (to remove the waiting state for feeding),Because there are already buns, bun shop threads enter a waiting state. Next, whether the feeding thread can execute further depends on the acquisition of the lock. If the lock is acquired by eating, then the action of eating buns is performed and the buns are finished (the status of the buns is false),And notify the bun shop thread (to remove the waiting state of the bun shop),The feeding thread entered the wait. The further execution of bun laying threads depends on the acquisition of locks.

Code demonstration:

Package resource class:

public class BaoZi {
     String  pier ;
     String  xianer ;
     boolean  flag = false ;//Is there a bundle resource status for the bundle resource
}

Food Thread Class:

public class ChiHuo extends Thread{
    private BaoZi bz;

    public ChiHuo(String name,BaoZi bz){
        super(name);
        this.bz = bz;
    }
    @Override
    public void run() {
        while(true){
            synchronized (bz){
                if(bz.flag == false){//No buns
                    try {
                        bz.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println("Eating"+bz.pier+bz.xianer+"Steamed stuffed bun");
                bz.flag = false;
                bz.notify();
            }
        }
    }
}

Package Packing Thread Class:

public class BaoZiPu extends Thread {

    private BaoZi bz;

    public BaoZiPu(String name,BaoZi bz){
        super(name);
        this.bz = bz;
    }

    @Override
    public void run() {
        int count = 0;
        //Make buns
        while(true){
            //synchronization
            synchronized (bz){
                if(bz.flag == true){//Package Resource Exists
                    try {

                        bz.wait();

                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

                // Make buns without buns
                System.out.println("The bun shop started making buns");
                if(count%2 == 0){
                    // Semen Pentaphyllum
                    bz.pier = "Ice Skin";
                    bz.xianer = "Five kernel";
                }else{
                    // Thin-skinned beef scallions
                    bz.pier = "Thin skin";
                    bz.xianer = "Beef scallion";
                }
                count++;

                bz.flag=true;
                System.out.println("The buns are made:"+bz.pier+bz.xianer);
                System.out.println("Eat your food");
                //Wake up waiting threads (eating)
                bz.notify();
            }
        }
    }
}

Test class:

public class Demo {
    public static void main(String[] args) {
        //Waiting for wake-up cases
        BaoZi bz = new BaoZi();

        ChiHuo ch = new ChiHuo("Foodie",bz);
        BaoZiPu bzp = new BaoZiPu("Bun shop",bz);

        ch.start();
        bzp.start();
    }
}

Execution effect:

The bun shop started making buns
 The buns are made: Five nuts in ice peel
 Eat your food
 Eating Ice crust buns
 The bun shop started making buns
 Steamed buns: thin-skinned beef and scallions
 Eat your food
 Eating thin-skinned beef and onion buns
 The bun shop started making buns
 The buns are made: Five nuts in ice peel
 Eat your food
 Eating Ice crust buns

Chapter II Thread Pools

2.1 Thread Pool Ideas Overview

It's easy to create a thread when we use it, but there's a problem:

If you have a large number of concurrent threads and each thread ends up performing a short task, creating threads frequently can greatly reduce the efficiency of the system, since it takes time to create and destroy threads frequently.

Is there a way to make threads reusable, that is, to complete a task and not be destroyed, but to continue with other tasks?

This can be achieved in Java through a thread pool. Today, let's take a closer look at Java's thread pool.

2.2 Thread Pool Concepts

  • **Thread pool: ** is actually a container for multiple threads, where threads can be used repeatedly, eliminating the need to create thread objects frequently and consuming too much resources without repeatedly creating threads.

Since many operations in a thread pool are related to optimizing resources, we won't go into much detail here. Here's a diagram to see how thread pools work:

Reasonable use of thread pools offers three benefits:

  1. Reduce resource consumption. Threads are created and destroyed less often, and each worker thread can be reused to perform multiple tasks.
  2. Increase response speed. When a task arrives, it can be executed immediately without waiting for a thread to be created.
  3. Improve thread manageability. You can adjust the number of workline threads in the thread pool based on the system's endurance to prevent the server from crashing because it consumes too much memory (each thread requires about 1 MB of memory, and the more threads open, the more memory it consumes and eventually crashes).

Use of 2.3 Thread Pool

The top-level interface for the thread pool in Java is java.util.concurrent.Executor, but Executor is not strictly a thread pool, it is just a tool for executing threads. The true thread pool interface is java.util.concurrent.ExecutorService.

Configuring a thread pool is complex, especially if the principles of the thread pool are not clear, and it is likely that the thread pool is not optimal, so it is in java.util.concurrent. The Executors Thread Factory class provides some static factories that generate some commonly used thread pools. The official recommendation is to use the Executors project class to create a thread pool object.

There is one way to create a thread pool in the Executors class:

  • public static ExecutorService newFixedThreadPool(int nThreads): Returns the thread pool object. (A bounded thread pool is created, that is, the maximum number of threads in the pool can be specified)

Get a thread pool ExecutorService object, then how to use it, here defines a method to use the thread pool object as follows:

  • Public Future<?> Submit (Runnable task): Gets a thread object in the thread pool and executes it

    Future interface: Used to record the results of a thread task after it has been executed. Thread pool creation and use.

Steps to use thread objects in the thread pool:

  1. Create a thread pool object.
  2. Create Runnable interface subclass object. (task)
  3. Submit Runnable interface subclass object. (take task)
  4. Close the thread pool (typically not).

Runnable implementation class code:

public class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("I want a coach");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Here comes the coach: " + Thread.currentThread().getName());
        System.out.println("Teach me to swim,After handing it in, the coach returned to the swimming pool");
    }
}

Thread pool test class:

public class ThreadPoolDemo {
    public static void main(String[] args) {
        // Create Thread Pool Object
        ExecutorService service = Executors.newFixedThreadPool(2);//Contains 2 Thread Objects
        // Create Runnable Instance Object
        MyRunnable r = new MyRunnable();

        //How to create thread objects yourself
        // Thread t = new Thread(r);
        // T.start(); ---> Call run() in MyRunnable

        // Get the thread object from the thread pool and call run() in MyRunnable
        service.submit(r);
        // Get another thread object and call run() in MyRunnable
        service.submit(r);
        service.submit(r);
        // Note: When the submit method call ends, the program does not terminate because the thread pool controls thread closure.
        // Return used threads to thread pool
        // Close Thread Pool
        //service.shutdown();
    }
}

Chapter III Lambda Expressions

3.1 Overview of Functional Programming Thought

In mathematics, a function is a set of calculation schemes with inputs and outputs, that is, what to do with something. Relatively, object-oriented places too much emphasis on "must do things in the form of objects", while functional thinking tries to ignore the complex object-oriented syntax, which emphasizes what to do rather than what to do in the form.

Object-oriented thinking:

Do one thing, find an object that can solve it, call the method of the object, and finish the thing.

Functional programming ideas:

As long as the results can be obtained, it doesn't matter who does it or what does it. It pays attention to the results and not to the process.

3.2 Redundant unnable code

Traditional Writing

When a thread needs to be started to complete a task, it is usually defined through the java.lang.Runnable interface and started using the java.lang.Thread class. The code is as follows:

public class Demo01Runnable {
	public static void main(String[] args) {
    	// Anonymous Inner Class
		Runnable task = new Runnable() {
			@Override
			public void run() { // Override override abstract methods
				System.out.println("Multithreaded task execution!");
			}
		};
		new Thread(task).start(); // Start Thread
	}
}

With the idea of "Everything is Object", this is justifiable: first create an anonymous internal class object for the Runnable interface to specify the task content, and then hand it over to a thread to start.

code analysis

For Runnable's anonymous internal class usage, there are a few things you can analyze:

  • The Thread class requires the Runnable interface as a parameter, and the abstract run method is the core of the thread task content specified.
  • In order to specify the body of run's method, the implementation class of the Runnable interface has to be required.
  • To avoid the hassle of defining a RunnableImpl implementation class, you have to use an anonymous internal class;
  • The override of the abstract run method must be overridden, so the method name, method parameters, method return values must be rewritten and cannot be written incorrectly.
  • In fact, it seems that only the method body is the key.

3.3 Conversion of Programming Ideas

What to do, not how to do it

Do we really want to create an anonymous internal class object? No We just have to create an object to do this. What we really want to do is pass code from the run method body to the Thread class.

Pass a piece of code - that's what we really want. Creating objects is only a means that has to be taken because of the object-oriented syntax. So, is there a simpler way? If we return our focus from how to do to what is essentially what to do, we will find that process and form are not really important as long as we can achieve our goals better.

Examples of life

When we need to go from Beijing to Shanghai, we can choose high-speed rail, car, bike or hiking. Our real goal is to get to Shanghai, and how we get there doesn't matter, so we've been exploring whether there's a better way than high-speed rail - by airplane.

Now this kind of airplane (or even a spaceship) has been born: Oracle's Java 8 (JDK 1.8) in March 2014 added the new weight feature of Lambda expression, opening the door to a new world.

3.4 Experience Lambda's Better Writing

With the new Java 8 syntax, the anonymous internal class notation of the above Runnable interface can be equivalent through simpler Lambda expressions:

public class Demo02LambdaRunnable {
	public static void main(String[] args) {
		new Thread(() -> System.out.println("Multithreaded task execution!")).start(); // Start Thread
	}
}

This code is exactly the same as what was just executed and can be passed at a compilation level of 1.8 or higher. You can see from the semantics of the code that we started a thread, and the content of the thread task was specified in a more concise form.

There is no longer the constraint of having to create interface objects and the burden of overriding overrides with abstract methods. It's that simple!

3.5 Review Anonymous Internal Classes

How does Lambda defeat object-oriented? In the example above, the core code is simply as follows:

() -> System.out.println("Multithreaded task execution!")

To understand Lambda's semantics, we need to start with traditional code.

Using implementation classes

To start a thread, you need to create an object of the Thread class and call the start method. To specify what the thread is executing, you need to call the Thread class's construction method:

  • public Thread(Runnable target)

To get an implementation object for a Runnable interface, you can define an implementation class, RunnableImpl, for that interface:

public class RunnableImpl implements Runnable {
	@Override
	public void run() {
		System.out.println("Multithreaded task execution!");
	}
}

The object of the implementation class is then created as a construction parameter for the Thread class:

public class Demo03ThreadInitParam {
	public static void main(String[] args) {
		Runnable task = new RunnableImpl();
		new Thread(task).start();
	}
}

Use anonymous internal classes

This RunnableImpl class only exists to implement the Runnable interface and is used only once, so using the syntax of an anonymous internal class eliminates the separate definition of the class, that is, an anonymous internal class:

public class Demo04ThreadNameless {
	public static void main(String[] args) {
		new Thread(new Runnable() {
			@Override
			public void run() {
				System.out.println("Multithreaded task execution!");
			}
		}).start();
	}
}

Benefits and disadvantages of anonymous internal classes

On the one hand, anonymous internal classes can help us eliminate the definition of implementation classes; On the other hand, the syntax of anonymous internal classes - it's really too complex!

semantic analysis

After careful analysis of the semantics in the code, the Runnable interface has only one definition of the run method:

  • public abstract void run();

That is, a way to do things (in fact, a function):

  • No parameters: No conditions are required to execute the program.
  • No return value: This scenario yields no results.
  • Code block (method body): the specific execution steps of the scheme.

The same semantics are reflected in Lambda syntax, much simpler:

() -> System.out.println("Multithreaded task execution!")
  • The first pair of parentheses, the parameter of the run method (none), indicates that no condition is required;
  • An arrow in the middle represents passing the previous parameter to the later code.
  • The subsequent output statement is the business logic code.

3.6 Lambda Standard Format

Lambda omits object-oriented bar boxes and the format consists of three parts:

  • Some parameters
  • An arrow
  • A piece of code

The standard format for Lambda expressions is:

(Parameter Type Parameter Name) -> { Code Statement }

Format description:

  • The syntax in parentheses is consistent with the traditional method parameter list: leave blank without parameters; Multiple parameters are separated by commas.
  • ->is a newly introduced grammar format that represents a pointing action.
  • The grammar within braces is essentially consistent with the traditional method body requirements.

Exercise 3.7: Use Lambda standard format (no parameters, no returns)

subject

Given a Cook cook interface, it contains the only abstract method, makeFood, with no parameters and no return value. The following:

public interface Cook {
    void makeFood();
}

In the code below, use Lambda's standard format to call the invokeCook method and print out "Eat!" Word:

public class Demo05InvokeCook {
    public static void main(String[] args) {
        // TODO Call invokeCook method here using Lambda [Standard Format]
    }

    private static void invokeCook(Cook cook) {
        cook.makeFood();
    }
}

Answer

public static void main(String[] args) {
    invokeCook(() -> {
      	System.out.println("Time to have a meal!");
    });
}

Note: The parentheses represent empty parameters for the Cook interface makeFood abstract method and the braces represent the body of the makeFood method.

3.8 Lambda parameters and return values

demand:
    Store multiple using arrays Person object
    In an array Person Object Usage Arrays Of sort Methods Sort ascending by age

The following example demonstrates the usage scenario code for the java.util.Comparator <T>interface, where the abstract method is defined as:

  • public abstract int compare(T o1, T o2);

Arrays. The sort method requires a Comparator interface instance to specify rules for sorting. Suppose you have a Person class with two member variables String name and int age:

public class Person { 
    private String name;
    private int age;
    
    // Omit Constructor, toString Method and Getter Setter 
}

Traditional Writing

If the Person[] array is sorted using traditional code, it is written as follows:

import java.util.Arrays;
import java.util.Comparator;

public class Demo06Comparator {
    public static void main(String[] args) {
      	// An array of objects that were originally out of order
        Person[] array = {
        	new Person("Gulinaza", 19),
        	new Person("Dili Reba", 18),
       		new Person("Malzahar", 20) };

      	// Anonymous Inner Class
        Comparator<Person> comp = new Comparator<Person>() {
            @Override
            public int compare(Person o1, Person o2) {
                return o1.getAge() - o2.getAge();
            }
        };
        Arrays.sort(array, comp); // The second parameter is the collation, which is the Comparator interface instance

        for (Person person : array) {
            System.out.println(person);
        }
    }
}

This seems to be "taken for granted" in object-oriented thinking. Instances of the Comparator interface (using anonymous internal classes) represent the "age by age" sorting rule.

code analysis

Let's see what the above code really does.

  • In order to sort, the Arrays.sort method requires a collation, which is an instance of the Comparator interface, with the abstract method compare being the key;
  • In order to specify the method body of compare, an implementation class of the Comparator interface has to be required.
  • To avoid the hassle of defining a ComparatorImpl implementation class, you have to use an anonymous internal class;
  • The override of the abstract compare method must be overridden, so the method name, method parameters, method return values must be rewritten and cannot be written incorrectly.
  • In fact, only parameters and method bodies are critical.

Lambda Writing

import java.util.Arrays;

public class Demo07ComparatorLambda {
    public static void main(String[] args) {
        Person[] array = {
          	new Person("Gulinaza", 19),
          	new Person("Dili Reba", 18),
          	new Person("Malzahar", 20) };

        Arrays.sort(array, (Person a, Person b) -> {
          	return a.getAge() - b.getAge();
        });

        for (Person person : array) {
            System.out.println(person);
        }
    }
}

Exercise 3.9: Use Lambda standard format (return if available)

subject

Given a calculator Calculator interface, the abstract method calc adds two ints to get the sum value:

public interface Calculator {
    int calc(int a, int b);
}

In the following code, invokeCalc method is called using Lambda's standard format to add 120 and 130:

public class Demo08InvokeCalc {
    public static void main(String[] args) {
        // TODO Please use Lambda [Standard Format] here to call invokeCalc method to calculate the result of 120+130
    }

    private static void invokeCalc(int a, int b, Calculator calculator) {
        int result = calculator.calc(a, b);
        System.out.println("The result is:" + result);
    }
}

Answer

public static void main(String[] args) {
    invokeCalc(120, 130, (int a, int b) -> {
      	return a + b;
    });
}

Note: The parentheses represent the parameters of the Calculator interface Calc Abstract method, and the braces represent the body of the calc method.

3.10 Lambda ellipsis format

Derivable to omit

Lambda emphasizes "what to do" rather than "how to do", so any information that can be inferred from the context can be omitted. For example, the above example can also use Lambda's ellipsis:

public static void main(String[] args) {
  	invokeCalc(120, 130, (a, b) -> a + b);
}

Omitting Rules

On the basis of Lambda standard format, the rules for using ellipsis are:

  1. Types of parameters within parentheses can be omitted;
  2. If there is only one parameter within the parentheses, the parentheses can be omitted.
  3. If there is only one statement within braces, the braces, return keywords, and statement semicolons can be omitted regardless of the return value.

Note: Once you have these omission rules, review the multithreaded cases at the beginning of this chapter appropriately.

3.11 Exercise: Use Lambda ellipsis format

subject

Still use the Cook interface with the only makeFood abstract method mentioned earlier. In the code below, use Lambda's ellipsis format to call the invokeCook method and print out "Eat!" Word:

public class Demo09InvokeCook {
    public static void main(String[] args) {
        // TODO Call invokeCook method here using Lambda [Omit Format]
    }

    private static void invokeCook(Cook cook) {
        cook.makeFood();
    }
}

Answer

public static void main(String[] args) {
  	invokeCook(() -> System.out.println("Time to have a meal!"));
}

3.12 Prerequisites for Lambda

Lambda's syntax is simple and completely free from the constraints of object-oriented complexity. However, there are several problems that require special attention when using:

  1. Lambda must have an interface and require that there be only one abstract method in the interface.
    Whether it's a Runable, Comparator interface built into the JDK, or a custom interface, Lambda can only be used if abstract methods in the interface exist and are unique.
  2. The use of Lambda must have context inference.
    That is, the parameter or local variable type of the method must be Lambda's corresponding interface type in order to use Lambda as an instance of the interface.

Note: An interface with only one abstract method is called a Functional Interface.

Tags: Java jvm programming language

Posted by phpion on Wed, 07 Sep 2022 21:58:45 +0530