JAVA Basics-Chapter 16 Thread Pool, Lambda Expression

main content

Waiting and waking up case
Thread pool
Lambda expression

teaching objectives

Able to understand the concept of thread communication
Able to understand the mechanism of waiting to wake up
Able to describe the operating principle of thread pools in Java
Able to understand the advantages of functional programming over object-oriented
Able to master the standard format of Lambda expressions
Able to use the standard format of Lambda Use Runnable and Comparator interface
to master Lambda The format and rules of expression omission
can use Lambda omission format, use Runnable and Comparator interface,
can use Lambda standard format to use custom interface (with one abstract method),
can use Lambda omission format to use custom interface (with And there is only one abstract method)
can clarify the two premises of Lambda

Chapter 1 Waiting for the wake-up mechanism

1.1 Communication between threads

Concept: multiple threads are processing the same resource, but the processing actions (tasks of the threads) are different.

For example, thread A is used to generate buns, and thread B is used to eat buns. Buns can be understood as the same resource. The actions processed by thread A and thread B are one for production and the other for consumption. Then between thread A and thread B There is a thread communication problem.

Why deal with inter-thread communication:

When multiple threads execute concurrently, the CPU switches threads randomly by default. When we need multiple threads to complete a task together, and we want them to execute regularly, then some coordination and communication between multiple threads are required. To help us achieve multi-threaded co-operation of a piece of data.

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

When multiple threads are processing the same resource and different tasks, thread communication is needed to help solve the use or operation of the same variable between threads. That is, when multiple threads operate on the same piece of data, they avoid contention for the same shared variable. That is, we need to use certain means to enable each thread to effectively use resources. And this method is-waiting for the wake-up mechanism.

1.2 Waiting for wake-up mechanism

What is waiting for wake up mechanism

This is a cooperative mechanism among multiple threads. When it comes to threads, we often think of races between threads, such as competing for locks, but this is not
the whole story. There will also be cooperation mechanisms between threads. Just like you and your colleagues in the company, you may compete for promotion, but more often
you work together to complete certain tasks.

That is, after a thread performs a prescribed operation, it enters the waiting state (wait()), and waits for other threads to execute their designated code before
waking it up (notify()); when there are multiple threads waiting, if If necessary, notifyAll() can be used to wake up all waiting threads.

wait/notify is a cooperation mechanism between threads.

Waiting to wake up

The wait for wake-up mechanism is used to solve the communication problem between threads. The meanings of the three methods used are as follows:

1. wait:线程不再活动,不再参与调度,进入 wait set 中,因此不会浪费 CPU 资源,也不会去竞争锁了,这时
的线程状态即是 WAITING。它还要等着别的线程执行一个特别的动作,也即是“通知(notify)”在这个对象
上等待的线程从wait set 中释放出来,重新进入到调度队列(ready queue)中
2. notify:则选取所通知对象的 wait set 中的一个线程释放;例如,餐馆有空位置后,等候就餐最久的顾客最先
入座。
3. notifyAll:则释放所通知对象的 wait set 上的全部线程。
注意:
哪怕只通知了一个等待的线程,被通知线程也不能立即恢复执行,因为它当初中断的地方是在同步块内,而
此刻它已经不持有锁,所以她需要再次尝试去获取锁(很可能面临其它线程的竞争),成功后才能在当初调
用 wait 方法之后的地方恢复执行。
总结如下:
如果能获取锁,线程就从 WAITING 状态变成 RUNNABLE 状态;
否则,从 wait set 出来,又进入 entry set,线程就从 WAITING 状态又变成 BLOCKED 状态

The details that need to be paid attention to when calling wait and notify methods

1. wait方法与notify方法必须要由同一个锁对象调用。因为:对应的锁对象可以通过notify唤醒使用同一个锁对
象调用的wait方法后的线程。
2. wait方法与notify方法是属于Object类的方法的。因为:锁对象可以是任意对象,而任意对象的所属类都是继
承了Object类的。
3. wait方法与notify方法必须要在同步代码块或者是同步函数中使用。因为:必须要通过锁对象调用这 2 个方
法。

1.3 Producer and consumer issues

1.3 Producer and consumer issues

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

Take the production of steamed buns and the consumption of steamed steamed buns as an example.

Code demo:

Bun resource class:

Food thread class:

Baozipu thread class:

包子铺线程生产包子,吃货线程消费包子。当包子没有时(包子状态为false),吃货线程等待,包子铺线程生产包子
(即包子状态为true),并通知吃货线程(解除吃货的等待状态),因为已经有包子了,那么包子铺线程进入等待状态。
接下来,吃货线程能否进一步执行则取决于锁的获取情况。如果吃货获取到锁,那么就执行吃包子动作,包子吃完(包
子状态为false),并通知包子铺线程(解除包子铺的等待状态),吃货线程进入等待。包子铺线程能否进一步执行则取
决于锁的获取情况。
public class BaoZi {
String pier ;
String xianer ;
boolean flag = false ;//包子资源 是否存在 包子资源状态
}
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){//没包子
try {
bz.wait();
                    } catch (InterruptedException e) {
e.printStackTrace();
                    }
                }
System.out.println("吃货正在吃"+bz.pier+bz.xianer+"包子");
bz.flag = false;
bz.notify();
            }
        }
    }
}

Test category:

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 ;
//造包子
while(true){
//同步
synchronized (bz){
if(bz.flag == true){//包子资源 存在
try {
bz.wait();
                    } catch (InterruptedException e) {
e.printStackTrace();
                    }
                }
// 没有包子 造包子
System.out.println("包子铺开始做包子");
if(count% 2  ==  0 ){
// 冰皮 五仁
bz.pier = "冰皮";
bz.xianer = "五仁";
                }else{
// 薄皮 牛肉大葱
bz.pier = "薄皮";
bz.xianer = "牛肉大葱";
                }
count++;
bz.flag=true;
System.out.println("包子造好了:"+bz.pier+bz.xianer);
System.out.println("吃货来吃吧");
//唤醒等待线程 (吃货)
bz.notify();
            }
        }
    }
}

Execution effect:

Chapter 2 Thread Pool

2.1 Overview of thread pool ideas

public class Demo {
public static void main(String[] args) {
//等待唤醒案例
BaoZi bz = new BaoZi();
ChiHuo ch = new ChiHuo("吃货",bz);
BaoZiPu bzp = new BaoZiPu("包子铺",bz);
ch.start();
bzp.start();
    }
}

Baozipu started making buns

The bun is made: ice skin and five kernels

Come and eat

The foodie is eating ice-skin five-ren buns

Baozipu started making buns

The buns are made: thin-skinned beef and green onions

Come and eat

The foodie is eating thin-skin beef and green onion buns

Baozipu started making buns

The bun is made: ice skin and five kernels

Come and eat

The foodie is eating ice-skin five-ren buns

When we use threads, we create a thread. This is very easy to implement, but there will be a problem:

If there are a large number of concurrent threads, and each thread executes a task for a short time, it ends, so the frequent creation of threads will be greatly reduced

The efficiency of the system, because it takes time to create and destroy threads frequently.

So is there a way to make threads reusable, that is, after executing a task, it will not be destroyed, but can continue to perform other tasks?

In Java, this effect can be achieved through a thread pool. Today we will explain in detail Java thread pool.

2.2 The concept of thread pool

Thread pool: In fact, it is a container that holds multiple threads. The threads can be used repeatedly, eliminating the need for frequent creation of thread objects.

No need to repeatedly create threads and consume too many resources.

Since many operations in the thread pool are related to optimizing resources, we won't go into details here. We use a picture to understand the working principle of the thread pool

Reason:

Reasonable use of thread pools can bring three benefits:

1. Reduce resource consumption. The number of times of creating and destroying threads is reduced, and each worker thread can be reused to perform multiple tasks.

2. Improve response speed. When the task arrives, the task can be executed immediately without waiting until the thread is created.

3. Improve the manageability of threads. The number of worker threads in the thread pool can be adjusted according to the system’s affordability to prevent excessive internal consumption.

Save the server and put the server down (each thread needs about 1MB of memory, the more threads are opened, the more memory is consumed, and finally it crashes).

2.3 The use of thread pool

The top-level interface of thread pool in Java is java.util.concurrent.Executor, but in a strict sense, Executor is not a thread
pool, but a tool for executing threads. The real thread pool interface is java.util.concurrent.ExecutorService.

To configure a thread pool is more complicated, especially when the principle of the thread pool is not very clear, it is very likely that the configured thread pool is not optimal
, so it is provided in the java.util.concurrent.Executors thread factory class Some static factories are created to generate some commonly used thread pools. The official
recommendation is to use the Executors engineering class to create thread pool objects.

There is a method to create a thread pool in the Executors class as follows:

public static ExecutorService newFixedThreadPool(int nThreads):返回线程池对象。(创建的是有界线
程池,也就是池中的线程个数可以指定最大数量)

I got a thread pool ExecutorService object, then how to use it, here is a method to use the thread pool object is defined as follows:

public Future<?> submit(Runnable task):获取线程池中的某一个线程对象,并执行
Future接口:用来记录线程任务执行完毕后产生的结果。线程池创建与使用。

Steps to use thread objects in the thread pool:

1. 创建线程池对象。
2. 创建Runnable接口子类对象。(task)
3. 提交Runnable接口子类对象。(take task)
4. 关闭线程池(一般不做)。

Runnable implementation class code:

Thread pool test class:

Chapter 3 Lambda Expression

3.1 Overview of functional programming ideas

public class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("我要一个教练");
try {
Thread.sleep( 2000 );
        } catch (InterruptedException e) {
e.printStackTrace();
        }
System.out.println("教练来了: " + Thread.currentThread().getName());
System.out.println("教我游泳,交完后,教练回到了游泳池");
    }
}
public class ThreadPoolDemo {
public static void main(String[] args) {
// 创建线程池对象
ExecutorService service = Executors.newFixedThreadPool( 2 );//包含 2 个线程对象
// 创建Runnable实例对象
MyRunnable r = new MyRunnable();
//自己创建线程对象的方式
// Thread t = new Thread(r);
// t.start(); ‐‐‐> 调用MyRunnable中的run()
// 从线程池中获取线程对象,然后调用MyRunnable中的run()
service.submit(r);
// 再获取个线程对象,调用MyRunnable中的run()
service.submit(r);
service.submit(r);
// 注意:submit方法调用结束后,程序并不终止,是因为线程池控制了线程的关闭。
// 将使用完的线程又归还到了线程池中
// 关闭线程池
//service.shutdown();
    }
}

In mathematics, a function is a set of calculation schemes with input and output, that is, "do something with something". Relatively speaking, object-oriented

The points emphasize "must do things in the form of objects", while functional thinking tries to ignore the object-oriented complex syntax-emphasizing what to do instead of

What form to do.

Object-oriented thinking:

Do one thing, find an object that can solve the problem, call the object's method, and complete the thing.

Functional programming ideas:

As long as the results can be obtained, it doesn't matter who did it and how it did it, it doesn't matter who does it, it pays attention to the result, not the process

3.2 Redundant Runnable code

Traditional writing

When a thread needs to be started to complete a task, the task content is usually defined through the java.lang.Runnable interface, and the
java.lang.Thread class is used to start the thread. code show as below:

Based on the idea of ​​"everything is an object", this approach is understandable: first create an anonymous inner class object of Runnable interface to specify the task
content, and then give it to a thread to start.

Code analysis

For the use of Runnable's anonymous inner class, several points can be analyzed:

Thread类需要Runnable接口作为参数,其中的抽象run方法是用来指定线程任务内容的核心;
public class Demo01Runnable {
public static void main(String[] args) {
// 匿名内部类
Runnable task = new Runnable() {
@Override
public void run() { // 覆盖重写抽象方法
System.out.println("多线程任务执行!");
}
};
new Thread(task).start(); // 启动线程
}
}
为了指定run的方法体,不得不需要Runnable接口的实现类;
为了省去定义一个RunnableImpl实现类的麻烦,不得不使用匿名内部类;
必须覆盖重写抽象run方法,所以方法名称、方法参数、方法返回值不得不再写一遍,且不能写错;
而实际上,似乎只有方法体才是关键所在。

3.3 Conversion of programming ideas

What to do, not how

Do we really want to create an anonymous inner class object? Do not. We just have to create an object to do this. We really want to do

The thing is: pass the code in the run method body to the Thread class to know.

Pass a piece of code-this is our real purpose. The creation of objects is only a method that has to be adopted due to the limitation of object-oriented syntax.
So, is there an easier way? If we return the focus from "how to" to the essence of "what to do," we will find that as long as we can better achieve
the goal, the process and form are not really important.

Life example

When we need to travel from Beijing to Shanghai, we can choose high-speed rail, car, cycling or walking. Our real purpose is to reach Shanghai, but how can we get there

The format of Shanghai is not important, so we have been exploring whether there is a better way than high-speed rail-by plane.

And now this kind of airplane (or even a spaceship) has been born: Java 8 (JDK 1.8) released by Oracle in March 2014 added the
heavyweight new features of Lambda expression , opening the door to a new world for us.

3.4 Experience the better writing of Lambda

With the help of Java 8's new syntax, the anonymous inner class writing of the above Runnable interface can be equivalent through simpler Lambda expressions:

This code is exactly the same as the execution effect just now, and it can be passed under the 1.8 or higher compilation level. It can be seen from the semantics of the code: we

A thread is started, and the content of the thread task is specified in a more concise form.

There is no longer the restriction of "have to create interface objects", and the burden of "abstract method coverage and rewriting", it's that simple!

3.5 Review of anonymous inner classes

How does Lambda defeat object-oriented? In the above example, the core code is actually just the following:

In order to understand the semantics of Lambda, we need to start with traditional code.

Use implementation class

To start a thread, you need to create an object of the Thread class and call the start method. In order to specify the content of the thread execution, you need to call
the constructor of the Thread class:

public Thread(Runnable target)

In order to obtain the implementation object of the Runnable interface, an implementation class RunnableImpl can be defined for the interface:

Then create an object of the implementation class as the construction parameter of the Thread class:

Use anonymous inner classes

This RunnableImpl class exists only to implement the Runnable interface, and it is used only once, so the use of the anonymous inner class
syntax can save the separate definition of this class, that is, the anonymous inner class:

public class Demo02LambdaRunnable {
public static void main(String[] args) {
new Thread(() ‐> System.out.println("多线程任务执行!")).start(); // 启动线程
}
}
() ‐> System.out.println("多线程任务执行!")
public class RunnableImpl implements Runnable {
@Override
public void run() {
System.out.println("多线程任务执行!");
}
}
public class Demo03ThreadInitParam {
public static void main(String[] args) {
Runnable task = new RunnableImpl();
new Thread(task).start();
}
}

Pros and cons of anonymous inner classes

On the one hand, anonymous inner classes can help us save the definition of implementation classes; on the other hand, the syntax of anonymous inner classes is really too complicated!

Semantic Analysis

Carefully analyze the semantics of the code, the Runnable interface has only one definition of run method:

public abstract void run();

That is, a plan to do things (actually a function) is formulated:

无参数:不需要任何条件即可执行该方案。
无返回值:该方案不产生任何结果。
代码块(方法体):该方案的具体执行步骤。

The same semantics is reflected in Lambda syntax, which is simpler:

前面的一对小括号即run方法的参数(无),代表不需要任何条件;
中间的一个箭头代表将前面的参数传递给后面的代码;
后面的输出语句即业务逻辑代码。

3.6 Lambda standard format

Lambda eliminates object-oriented rules and regulations, and the format consists of 3 parts:

一些参数
一个箭头
一段代码

The standard format of lambda expression is:

Format description:

The syntax in the parentheses is consistent with the traditional method parameter list: if there is no parameter, leave it blank; multiple parameters are separated by commas.

-> is a newly introduced grammatical format, which stands for pointing action.

The grammar in the braces is basically the same as the traditional method.

3.7 Exercise: Use Lambda standard format (no parameters and no return)

public class Demo04ThreadNameless {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("多线程任务执行!");
}
}).start();
}
}
() ‐> System.out.println("多线程任务执行!")

(Parameter type parameter name) -> {code statement}

3.7 Exercise: Use Lambda standard format (no parameters and no return)

topic

Given a cook interface, it contains the only abstract method makeFood, and has no parameters and no return value. as follows:

In the following code, please use Lambda's standard format to call the invokeCook method and print out the words "Eating!":

answer

备注:小括号代表Cook接口makeFood抽象方法的参数为空,大括号代表makeFood的方法体。

3.8 Lambda parameters and return values

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

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

When you need to sort an array of objects, the Arrays.sort method requires an instance of the Comparator interface to specify the sorting rules. Suppose there is
a Person class with two member variables: String name and int age:

public interface Cook {
void makeFood();
}
public class Demo05InvokeCook {
public static void main(String[] args) {
// TODO 请在此使用Lambda【标准格式】调用invokeCook方法
    }
private static void invokeCook(Cook cook) {
cook.makeFood();
    }
}
public static void main(String[] args) {
invokeCook(() ‐> {
System.out.println("吃饭啦!");
    });
}

demand:

使用数组存储多个Person对象
对数组中的Person对象使用Arrays的sort方法通过年龄进行升序排序

Traditional writing

If you use traditional code to sort the Person[] array, the writing method is as follows:

This approach seems to be "a matter of course" in object-oriented thinking. The instance of the Comparator interface (using the anonymous inner class) represents
the sorting rule of "from youngest to oldest".

Code analysis

Let's figure out what the above code really does.

为了排序,Arrays.sort方法需要排序规则,即Comparator接口的实例,抽象方法compare是关键;
为了指定compare的方法体,不得不需要Comparator接口的实现类;
为了省去定义一个ComparatorImpl实现类的麻烦,不得不使用匿名内部类;
必须覆盖重写抽象compare方法,所以方法名称、方法参数、方法返回值不得不再写一遍,且不能写错;
实际上,只有参数和方法体才是关键。

Lambda writing

public class Person {
private String name;
private int age;
// 省略构造器、toString方法与Getter Setter
}
import java.util.Arrays;
import java.util.Comparator;
public class Demo06Comparator {
public static void main(String[] args) {
// 本来年龄乱序的对象数组
Person[] array = {
new Person("古力娜扎",  19 ),
new Person("迪丽热巴",  18 ),
new Person("马尔扎哈",  20 ) };
// 匿名内部类
Comparator<Person> comp = new Comparator<Person>() {
@Override
public int compare(Person o1, Person o2) {
return o1.getAge() ‐ o2.getAge();
            }
        };
Arrays.sort(array, comp); // 第二个参数为排序规则,即Comparator接口实例
for (Person person : array) {
System.out.println(person);
        }
    }
}

3.9 Exercise: Use Lambda standard format (with participation and return)

topic

Given a Calculator interface, it contains the abstract method calc to add two int numbers to get the sum:

In the following code, please use Lambda's standard format to call the invokeCalc method to complete the addition calculation of 120 and 130:

answer

import java.util.Arrays;
public class Demo07ComparatorLambda {
public static void main(String[] args) {
Person[] array = {
new Person("古力娜扎",  19 ),
new Person("迪丽热巴",  18 ),
new Person("马尔扎哈",  20 ) };
Arrays.sort(array, (Person a, Person b) ‐> {
return a.getAge() ‐ b.getAge();
        });
for (Person person : array) {
System.out.println(person);
        }
    }
}
public interface Calculator {
int calc(int a, int b);
}
public class Demo08InvokeCalc {
public static void main(String[] args) {
// TODO 请在此使用Lambda【标准格式】调用invokeCalc方法来计算120+130的结果ß
    }
private static void invokeCalc(int a, int b, Calculator calculator) {
int result = calculator.calc(a, b);
System.out.println("结果是:" + result);
    }
}
public static void main(String[] args) {
invokeCalc( 120 ,  130 , (int a, int b) ‐> {
return a + b;
    });
}
备注:小括号代表Calculator接口calc抽象方法的参数,大括号代表calc的方法体。

3.10 Lambda omitted format

Can be derived or omitted

Lambda emphasizes "what" rather than "how", so any information that can be derived from the context can be omitted. For example, the above example can also
use the omission of Lambda:

Omission rule

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

1. 小括号内参数的类型可以省略;
2. 如果小括号内有且仅有一个参,则小括号可以省略;
3. 如果大括号内有且仅有一个语句,则无论是否有返回值,都可以省略大括号、return关键字及语句分号。
备注:掌握这些省略规则后,请对应地回顾本章开头的多线程案例。

3.11 Exercise: Use Lambda to omit the format

topic

Still using the previous cook interface that contains the only abstract method of makeFood, in the following code, please use Lambda's omitted format to call the
invokeCook method, and print out the words "Eating!":

answer

3.12 Prerequisites for using Lambda

public static void main(String[] args) {
invokeCalc( 120 ,  130 , (a, b) ‐> a + b);
}
public class Demo09InvokeCook {
public static void main(String[] args) {
// TODO 请在此使用Lambda【省略格式】调用invokeCook方法
    }
private static void invokeCook(Cook cook) {
cook.makeFood();
    }
}
public static void main(String[] args) {
invokeCook(() ‐> System.out.println("吃饭啦!"));
}

Lambda's syntax is very concise, without the constraints of object-oriented complexity. However, there are several problems that need special attention when using:

1. 使用Lambda必须具有接口,且要求接口中有且仅有一个抽象方法。
无论是JDK内置的Runnable、Comparator接口还是自定义的接口,只有当接口中的抽象方法存在且唯一
时,才可以使用Lambda。
2. 使用Lambda必须具有上下文推断。
也就是方法的参数或局部变量类型必须为Lambda对应的接口类型,才能使用Lambda作为该接口的实例。
备注:有且仅有一个抽象方法的接口,称为“函数式接口”。

Guess you like

Origin blog.csdn.net/weixin_43419256/article/details/108230718