线程池
概述
- 线程池:其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多资源。
线程池使用
- 线程池:JDK1.5之后提供
java.util.concurrent.Executors
:线程池的工厂类,用来生成线程池- Executors类中的静态方法:
public static ExecutorService newFixedThreadPool(int nThreads)
:返回线程池对象。(创建的是有界线程池,也就是池中的线程个数可以指定最大数量)
返回值:ExecutorService接口,返回的是ExecutorService接口的实现类对象,可以使用ExecutorService接收 java.util.concurrent.ExecutorService
:线程池接口,用来从线程池中获取线程,调用start方法,执行线程任务
public Future<?> submit(Runnable task)
:获取线程池中的某一个线程对象,并执行(提交一个Runnable任务用于执行)
Future接口:用来记录线程任务执行完毕后产生的结果。线程池创建与使用。
void shutdown()
:关闭、销毁线程池的方法- 线程池的使用步骤;
1)创建线程池对象
2)创建Runnable接口子类对象。(task)
3)使用submit方法,提交Runnable接口子类对象。(take task)
4)关闭线程池(一般不做)
Runnable实现类代码:
public class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"线程池");
}
}
测试类:
public class ThreadPool {
public static void main(String[] args) {
//创建线程池对象
ExecutorService es= Executors.newFixedThreadPool(2);
//创建Runnable实例对象
Runnable r=new MyRunnable();
//从线程池中获取线程对象,然后调用MyRunnable()中的run()方法
//线程池会一直开启,使用完了线程,会自动把线程归还给线程池,继续使用
es.submit(r);//pool-1-thread-1线程池
// 再获取个线程对象,调用MyRunnable中的run()
es.submit(r);//pool-1-thread-2线程池
//关闭线程池
es.shutdown();
}
}
Lambda表达式
函数式编程思想
- 面向对象的思想:找一个能解决这件事的对象,调用对象的方法,完成事情;
- 函数式编程思想:只要能获取到结果,谁去做的,怎么做的,都不重要,重视的是结果,不重视过程;
- 面向对象过分强调“必须通过对象的形式来做事情”,而函数式思想则尽量忽略面向对象的复杂语法——强调做什么,而不是以什么形式做。
冗余的Runnable代码
传统写法
当需要启动一个线程去完成任务时,通常会通过java.lang.Runnable
接口来定义任务内容,并使用java.lang.Thread
类来启动该线程。代码如下:
public class Demo01Runnable {
public static void main(String[] args) {
// 匿名内部类
Runnable task = new Runnable() {
@Override
public void run() { // 覆盖重写抽象方法
System.out.println("多线程任务执行!");
}
};
new Thread(task).start(); // 启动线程
}
}
本着“一切皆对象”的思想,这种做法是无可厚非的:首先创建一个Runnable
接口的匿名内部类对象来指定任务内容,再将其交给一个线程来启动。
代码分析
对于Runnable
的匿名内部类用法,可以分析出几点内容:
Thread
类需要Runnable
接口作为参数,其中的抽象run
方法是用来指定线程任务内容的核心;- 为了指定
run
的方法体,不得不需要Runnable
接口的实现类; - 为了省去定义一个
RunnableImpl
实现类的麻烦,不得不使用匿名内部类; - 必须覆盖重写抽象
run
方法,所以方法名称、方法参数、方法返回值不得不再写一遍,且不能写错; - 而实际上,似乎只有方法体才是关键所在。
编程思想转换
做什么,而不是怎么做
我们真的希望创建一个匿名内部类对象吗?不。我们只是为了做这件事情而不得不创建一个对象。我们真正希望做的事情是:将run
方法体内的代码传递给Thread
类知晓。
传递一段代码——这才是我们真正的目的。而创建对象只是受限于面向对象语法而不得不采取的一种手段方式。那,有没有更加简单的办法?如果我们将关注点从“怎么做”回归到“做什么”的本质上,就会发现只要能够更好地达到目的,过程与形式其实并不重要。
Lambda标准格式
Lambda省去面向对象的条条框框,格式由3个部分组成:
- 一些参数
- 一个箭头
- 一段代码
Lambda表达式的标准格式为:
(参数类型 参数名称) -> { 代码语句 }
格式说明:
()
:接口中抽象方法的参数列表,没有参数就空着;有参数就写出参数,多个参数使用逗号分隔;->
:是新引入的语法格式,把参数传递给方法体{ }{ }
:重写接口的抽象方法的方法体
public class Demo04ThreadNameless {
public static void main(String[] args) {
//使用匿名内部类的方式实现多线程
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("多线程任务执行!");
}
}).start();
//使用Lambda表达式实现多线程
new Thread(()->{
System.out.println("多线程任务执行!");
}
).start();
//使用Lambda省略格式
new Thread(()->System.out.println("多线程任务执行!")).start();//省略{}和;
}
}
//定义一个接口,内含无参方法
public interface Cook {
void makeFood();
}
//测试类
public class InvokeCook {
public static void invokecook(Cook cook){
cook.makeFood();
}
public static void main(String[] args) {
//匿名内部类实现
invokecook(new Cook() {
@Override
public void makeFood() {
System.out.println("吃饭了");
}
});
//Lambda实现方式
invokecook(()->{
System.out.println("吃饭了");
});
//Lambda省略格式
invokecook(()->System.out.println("吃饭了"));//省略分号、{}
}
}
Lambda的参数和返回值
需求:
使用数组存储多个Person对象
对数组中的Person对象使用Arrays的sort方法通过年龄进行升序排序
public class Person {
String name;
int age;
public Person(String name,int age){
this.age=age;
this.name=name;
}
}
//测试类
public class DemoComparator {
public static void main(String[] args) {
// 本来年龄乱序的对象数组
Person[] array = {
new Person("古力娜扎", 19),
new Person("迪丽热巴", 18),
new Person("马尔扎吨", 20) };
//对数组对象进行升序排序(前边减去后边)
Arrays.sort(array,new Comparator<Person>() {
@Override
public int compare(Person o1, Person o2) {
return o1.age-o2.age;
}
} );
//使用Lambda表达式
Arrays.sort(array,(Person o1,Person o2)->{return o1.age-o2.age});
//Lambda省略格式
Arrays.sort(array,(o1,o2)->o1.age-o2.age);//省略类型、return、分号、{}
}
}
Lambda省略格式
- Lambda表达式:可推导,可省略
凡是根据上下文推导出来的内容,都可以省略书写; - 可以省略的内容:
- 小括号内参数的类型可以省略;
- 如果小括号内有且仅有一个参,则小括号可以省略;
- 如果大括号内有且仅有一个语句,则无论是否有返回值,都可以省略大括号、return关键字及语句分号。
注意:要省略{}、return、分号必须一起省略
Lambda的使用前提
Lambda的语法非常简洁,完全没有面向对象复杂的束缚。但是使用时有几个问题需要特别注意:
- 使用Lambda必须具有接口,且要求接口中有且仅有一个抽象方法。
无论是JDK内置的Runnable
、Comparator
接口还是自定义的接口,只有当接口中的抽象方法存在且唯一时,才可以使用Lambda。 - 使用Lambda必须具有上下文推断。
也就是方法的参数或局部变量类型必须为Lambda对应的接口类型,才能使用Lambda作为该接口的实例。
备注:有且仅有一个抽象方法的接口,称为“函数式接口”。