线程池、Lambda表达式
等待唤醒机制
线程间的通信
概念:多个线程在处理同一个资源,但是处理的动作(线程任务)却不相同,需要线程通信来解决线程之间对同一个变量的使用或操作,即是多个线程在操作同一份数据时, 避免对同一共享变量的争夺。(通过等待唤醒机制使线程有效利用资源)
等待唤醒机制
多个线程间的一种协作机制
就是在一个线程进行了规定操作后,就进入等待状态(wait()), 等待其他线程执行完他们的指定代码过后 再将 其唤醒(notify());在有多个线程进行等待时, 如果需要,可以使用 notifyAll()来唤醒所有的等待线程。
wait/notify 就是线程间的一种协作机制。
等待唤醒中的方法
1.wait:线程不再活动,不参与调度.进入wait set中(不会浪费CPU资源,也不会竞争锁),此时线程状态是 Waiting.等待其他线程执行notify,通知当前对象等待的线程从wait set中释放,重新进入到调度队列中
2. notify:选取所通知对象的 wait set 中的一个线程释放 (等待最久的先释放)
3.notifyAll:释放所通知对象的 wait set 上的全部线程。
//就算通知了等待的线程,其线程也不能立即恢复执行,由于当初中断地方在同步块内,所有它已经不持有锁,需要再次获得锁(可能面临其他线程竞争),才能在之前调用wait方法之后的地方执行
-
如果能获取锁,线程就从 WAITING 状态变成 RUNNABLE 状态;
-
否则,从 wait set 出来,又进入 entry set,线程就从 WAITING 状态又变成 BLOCKED 状态
调用wait和notify方法要注意的细节
1.wait方法与notify方法必须由同一个锁对象调用 //因为对于的锁对象可以通过notify唤醒使用同一个锁对象调用的wait方法后的线程
2.wait方法与notify方法是属于Object类的方法的 //锁对象可以是任意对象,任意对象所属类都是继承Object类
3. wait方法与notify方法必须要在同步代码块或者是同步函数中使用 //必须通过锁对象调用这两个方法
线程池
线程池概念
线程池::一个可以容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程和销毁线程的操作,无需消耗过多资源
线程池的好处:
1.降低资源消耗
2.提高响应速度
3.提高线程的可管理性
线程池的使用
Java里面线程池的顶级接口是 java.util.concurrent.Executor
,但是严格意义上讲 Executor 并不是一个线程 池,而只是一个执行线程的工具。真正的线程池接口是 java.util.concurrent.ExecutorService
。
Executors类中有个创建线程池的方法:
public static ExecutorService newFixedThreadPool(int nThreads) :返回线程池对象 //创建的是有界线程池,线程个数可以指定最大数量
获取到了一个线程池ExecutorService 对象,定义一个使用线程池对象的方法:
public Future<?> submit(Runnable task) :获取线程池中的某一个线程对象,并执行
使用线程池中线程对象的步骤:
1.创建线程对象
//使用线程池工厂类Executors里提供的静态方法newFixedThreadPool生产一个指定线程数量的线程池
2.创建Runnable接口子类对象
//创建一个类,实现Runnable接口,重写run方法,设置线程任务
3.提交Runnable接口子类对象
//调用ExecutorService中的方法,传递线程任务(实现类),开启线程,执行run方法
4.关闭线程(一般不做)
//调用ExecutorService中的方法shutdown销毁线程(不建议执行)
Runnable实现类代码:
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("教LOVE后,返回办公室");
}
}
测试类代码:
public class ThreadPoolDemo {
public static void main(String[] args) {
// 创建线程池对象
ExecutorService service = Executors.newFixedThreadPool(2);//包含2个线程对象
// 创建Runnable实例对象
MyRunnable r = new MyRunnable();
// 从线程池中获取线程对象,然后调用MyRunnable中的run()
service.submit(r);
// 再获取个线程对象,调用MyRunnable中的run()
service.submit(r);
service.submit(r);
// 注意:submit方法调用结束后,程序并不终止,是因为线程池控制了线程的关闭。
// 将使用完的线程又归还到了线程池中
// 关闭线程池
//service.shutdown();
}
}
Lambda表达式
java8的全新语法,使Runnable接口的匿名内部类写法可以更简单,通过Lambda表达式
传统代码
使用实现类
1.启动一个线程,需要创建Thread类的对象并调用start方法。为了指定线程内容,需要调用Thread类的构造方法:
public Thread(Runnable target)
2.为了获取Runnable接口的实现对象,该接口定义一个实现类RunnableImpl
:
public class RunnableImpl implements Runnable {
@Override
public void run() {
System.out.println("多线程任务执行!");
}
}
3.创建实现类对象作为Thread类的构造参数:
public class DemoThreadInitParam {
public static void main(String[] args) {
Runnable task = new RunnableImpl();
new Thread(task).start();
}
使用匿名内部类
RunnableImpl 类只是为了实现 Runnable 接口而存在的,进被使用一次,所以使用匿名内部类
public class Demo01ThreadNameless {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("多线程任务执行!");
}
}).start();
}
}
语义分析
Runnable接口只有一个run方法定义:
public abstract void run();
,限定了规格(函数规格)
- 无参数:不需要任何条件就可执行
- 无返回值:不产生任何结果
- 代码块:方案的具体执行步骤
同样语义体现在Lambda语法
() ‐> System.out.println("多线程任务执行!")
/*1.前面一对小括号表示:run方法的参数(无),即不需要条件
*2.中间的一个箭头表示:将前面的参数传递给后面的代码
*3.后面的输出语句即:业务逻辑代码
*/
Lambda标准格式
由3部分组成
- 一些参数
- 一个箭头
- 一段代码
标准格式:
(参数类型 参数名称) ‐> { 代码语句 }
/*
小括号内的语法与传统方法参数列表一致:无参数则留空;多个参数则用逗号分隔。
-> 是新引入的语法格式,代表指向动作。
大括号内的语法与传统方法体要求基本一致。
*/
使用Lambda标准格式(无参无返回)
举例:
定义一个学生Student
接口,含有唯一的抽象方法learnEnglish
,无参数,无返回值
public interface Student{
void learnEnglish();
}
在下面的代码中,请使用Lambda的标准格式调用 study方法,打印输出“小罗赶紧学英语”
public class Demo05InvokeCook {
public static void main(String[] args) {
}
private static void study(Student student {
Student.learnEnglish();
}
}
Lambda表达
public static void main(String[] args) {
study(() ‐> {
System.out.println("小罗赶紧学英语");
}
Lambda的参数和返回值
举例:
需求:
使用数组存储多个Person对象
对数组中的Person对象使用Arrays的sort方法通过年龄进行升序排序
//假设有一个 Person 类,含有 String name 和 String age 两个变量
// 当需要对一个对象数组进行排序时, Arrays.sort 方法需要一个 Comparator 接口实例来指定排序的规则。
java.util.Comparator
:
抽象方法定义:public abstract int compare(T o1, T o2);
person类:
public class Person {
private String name;
private int age;
// 省略构造器、toString方法与Getter Setter
}
传统
测试类:
import java.util.Arrays;
import java.util.Comparator;
public class Demo01Comparator {
public static void main(String[] args) {
// 本来年龄乱序的对象数组
Person[] array = {
new Person("小罗", 22),
new Person("小月", 18),
new Person("小轩", 24) };
// 匿名内部类
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);
}
}
}
Lambda写法:
import java.util.Arrays;
public class Demo02ComparatorLambda {
public static void main(String[] args) {
Person[] array = {
new Person("小罗", 22),
new Person("小月", 18),
new Person("小轩", 24) };
Arrays.sort(array, (Person a, Person b) ‐> {
return a.getAge() ‐ b.getAge();
});
for (Person person : array) {
System.out.println(person);
}
}
Lambda省略格式
规则:
1. 小括号内参数的类型可以省略;
2. 如果小括号内有且仅有一个参,则小括号可以省略;
3. 如果大括号内有且仅有一个语句,则无论是否有返回值,都可以省略大括号、return关键字及语句分号。
Lambda使用前提
1. 使用Lambda必须具有接口,且要求接口中有且仅有一个抽象方法。
//有且仅有一个抽象方法的接口,称为“函数式接口”。
2. 使用Lambda必须具有上下文推断,即是方法的参数或局部变量类型必须为Lambda对应的接口类型,才能使用Lambda作为该接口的实例。
tem.out.println(person);
}
}
#### Lambda省略格式
**规则**:
```java
1. 小括号内参数的类型可以省略;
2. 如果小括号内有且仅有一个参,则小括号可以省略;
3. 如果大括号内有且仅有一个语句,则无论是否有返回值,都可以省略大括号、return关键字及语句分号。
Lambda使用前提
1. 使用Lambda必须具有接口,且要求接口中有且仅有一个抽象方法。
//有且仅有一个抽象方法的接口,称为“函数式接口”。
2. 使用Lambda必须具有上下文推断,即是方法的参数或局部变量类型必须为Lambda对应的接口类型,才能使用Lambda作为该接口的实例。