文章目录
等待与唤醒、线程池、Lambda表达式
一、等待与唤醒
1. 线程状态概述
2. 等待唤醒案例
-
状态
-
计时等待
-
锁阻塞
-
无限等待:一个正在无限等待另一个线程执行一个特别的(唤醒)动作的线程处于这一状态。
-
-
分析
- 创建一个顾客线程:告知老板要的包子种类和数量,调用
wait
方法,放弃cpu
的执行,进入到WAITING
状态(无限等待)。 - 创建一个老板线程:花了5秒做包子,做好之后,调用
notify
方法,唤醒顾客吃包子。
- 创建一个顾客线程:告知老板要的包子种类和数量,调用
-
实现
- 注意:
- 顾客与老板线程必须使用同步代码块包裹起来,保证等待与唤醒只能有一个在执行。
- 同步使用的锁对象必须保证唯一。
- 只有锁对象才能调用
wait
和notify
方法。
Object
类中的方法:void wait()
在其他线程调用此对象的notify()或notifyAll()
方法前,导致当前线程等待。void notify()
唤醒此对象监视器上等待的单个线程。会执行wait()
方法之后的代码。
public class BaoZi{ public static void main(String[] args) { //创建锁对象 Object obj = new Object(); //顾客线程 new Thread(){ @Override public void run() { while (true) { synchronized (obj) { System.out.println("我想吃包子!"); try { obj.wait(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("我吃饱了,谢谢款待!"); } } } }.start(); //老板线程 new Thread(){ @Override public void run() { while (true) { try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (obj) { System.out.println("包子做好了,快吃吧!"); obj.notify(); } } } }.start(); } }
- 注意:
3. Object类中wait
带参方法和notifyAll
方法
- 进入
TimeWaiting
(计时等待)有两种方式:- 使用
sleep(long m)
方法,在毫秒值结束之后,线程睡醒进入到Runnable/Blocked
状态。 - 使用
wait(long m)
方法,wait
方法若在毫秒值结束之后,还没有被notify
唤醒,就会自动醒来,线程睡醒进入到Runnable/Blocked
状态。
- 使用
4. 线程间通信
- 概念:多个线程在处理同一个资源,但是处理的动作(线程的任务)不相同。
二、线程池
1. 线程池的概念与原理
- 线程池:就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多资源。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-C1E29ozS-1589461245253)(https://raw.githubusercontent.com/zhugulii/picBed/master/%E7%BA%BF%E7%A8%8B%E6%B1%A0%E5%8E%9F%E7%90%86.bmp)]
2. 线程池的代码实现
- 线程池:
JDK1.5
之后提供的。 java.util.concurrent.Executors
:线程池的工厂类,用来生成线程池。Executors
类中的静态方法:public static ExecutorService newFixedThreadPool(int nThreads)
:创建一个可重用固定线程数的线程池。- 参数:
int nThreads
:创建线程池中包含的线程数量; - 返回值:
ExecutorService接口
:返回的是ExecutorService
接口的实现类对象,我们可以使用ExecutorService
接口接收(面向接口编程)。
- 参数:
java.util.concurrent.ExecutorService
:线程池接口submit(Runnable task)
:用来从线程池中获取线程,调用start
方法,执行线程任务。- 关闭/销毁线程池的方法:
void shutdown()
- 线程池的使用步骤:
- 使用线程池的工厂类
Executors
里边提供的静态方法newFixedThreadPool
生产一个指定线程数量的线程池; - 创建一个类,实现
Runnable
接口,重写run
方法,设置线程任务; - 调用
ExecutorService
中的方法submit
,传递线程任务(实现类),开启线程,执行run
方法。 - 调用
ExecutorService
中的方法shutdown
销毁线程池(不建议执行)。
- 使用线程池的工厂类
public class SubRunnable implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "正在执行");
}
}
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Test {
public static void main(String[] args) {
ExecutorService es = Executors.newFixedThreadPool(2);
es.submit(new SubRunnable()); // pool-1-thread-1正在执行
es.submit(new SubRunnable()); // pool-1-thread-2正在执行
es.submit(new SubRunnable()); // pool-1-thread-1正在执行
}
}
三、Lambda表达式
1. 函数式编程思想概述
- 面向对象的思想:做一件事情,找一个能解决这个事情的对象,调用对象的方法,完成事情。
- 函数式编程思想:只要能获取到结果,谁去做的,怎么做的都不重要,重视的是结果,不重视过程。
2. Lambda表达式的标准格式
- 标准格式由三部分组成:
- 一些参数
- 一个箭头
- 一段代码
- 格式:
(参数列表)->{一些重写方法的代码};
()
:接口中抽象方法的参数列表;->
:传递的意思,把参数传递给方法体;{}
:重写接口的抽象方法的方法体。
3. 使用Lambda标准格式:无参无返回
/*
给定一个厨子Cook接口,内含唯一的抽象方法makeFood,且无参数,无返回值。
使用Lambda的标准格式调用invokeCook方法,打印输出”吃饭啦!“字样。
*/
public interface Cook {
public abstract void makeFood();
}
public class Test {
public static void main(String[] args) {
//使用匿名内部类
invokedCook(new Cook() {
@Override
public void makeFood() {
System.out.println("吃饭啦!");
}
});
//使用lambda表达式
invokedCook(()->{
System.out.println("吃饭啦!");
});
}
public static void invokedCook(Cook cook) {
cook.makeFood();
}
}
4. Lambda的参数和返回值
/*
使用数组存储多个Person对象
对数组中的Person对象使用Arrays的sort方法通过年龄进行升序排序
*/
public class Person {
private String name;
private int age;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
import java.util.Arrays;
import java.util.Comparator;
public class Test {
public static void main(String[] args) {
Person[] people = {
new Person("朱古力", 20),
new Person("猪猪侠",22),
new Person("猪猪猪",18)
};
//使用匿名内部类
/*Arrays.sort(people, new Comparator<Person>() {
@Override
public int compare(Person o1, Person o2) {
return o1.getAge() - o2.getAge();
}
});*/
//使用Lambda表达式
Arrays.sort(people,(Person o1, Person o2) -> {
return o1.getAge() - o2.getAge();
});
for (Person person : people) {
System.out.println(person);
}
}
}
5. Lambda表达式:自定义接口
/*
给定一个计算器Calculator接口,内含抽象方法calc可以将两个int数字相加得到和值
使用Lambda的标准格式调用invokeCalc方法,完成120与130的相加计算
*/
public interface Calculator {
public abstract int calc(int a, int b);
}
public class Test {
public static void main(String[] args) {
//使用匿名内部类
invokeCalc(120, 130, new Calculator() {
@Override
public int calc(int a, int b) {
return a + b;
}
});
//使用lambda表达式
invokeCalc(12,13,(int a, int b)->{
return a + b;
});
}
public static void invokeCalc(int a, int b, Calculator c){
System.out.println(c.calc(a, b));
}
}
6. Lambda省略格式与使用前提
- 省略规则:
- 小括号内参数的类型可以省略;
- 如果小括号内有且仅有一个参,则小括号可以省略;
- 如果大括号内有且仅有一个语句,则无论是否有返回值,都可以省略大括号、return关键字及语句分号。
- 注意:大括号、return关键字及语句分号必须同时一起省略。
- 使用前提:
- 使用 Lambda 必须具有接口,且要求接口中有且仅有一个抽象方法。
无论是JDK
内置的Runnable
、Comparator
接口还是自定义的接口,只有当接口中的抽象方法存在且唯一时,才可以使用 Lambda。 - 使用 Lambda 必须具有上下文推断。
也就是方法的参数或局部变量类型必须为 Lambda 对应的接口类型,才能使用 Lambda 作为该接口的实例。
- 使用 Lambda 必须具有接口,且要求接口中有且仅有一个抽象方法。