线程池:其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多资源。
由于线程池中有很多操作都是与优化资源相关的,我们在这里就不多赘述。我们通过一张图来了解线程池的工作原理:
合理利用线程池能够带来三个好处:
在JDK1.5的时候java提供了线程池
java.util.concurrent.Executors类:线程池的工厂类,用来生产线程池
静态方法:
**java.util.concurrent.ExecutorService:**线程池
使用线程池中线程对象的步骤:
public static void main(String[] args) {
//1.使用线程池工厂类Executors提供的静态方法newFixedThreadPool生产一个指定线程数量的线程池
ExecutorService ex = Executors.newFixedThreadPool(2);
//2.调用线程池ExecutorService中的方法submit,传递线程任务,执行线程任务
//new Thread(new Runnable(){}).start();
ex.submit(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"线程任务1执行了!");//pool-1-thread-2线程任务执行了!
}
});
ex.submit(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"线程任务2执行了!");//pool-1-thread-1线程任务执行了!
}
});
//void shutdown() 用于销毁线程池,一般不建议使用
ex.shutdown();
//线程池销毁之后,就在内存中消失了,就不能在执行线程任务了
ex.submit(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"线程任务2执行了!");//RejectedExecutionException
}
});
}
Callable测试代码:
public static void main(String[] args) throws ExecutionException, InterruptedException {
//1.使用线程池工厂类Executors提供的静态方法newFixedThreadPool生产一个指定线程数量的线程池
ExecutorService ex = Executors.newFixedThreadPool(2);
//2.调用线程池ExecutorService中的方法submit,传递线程任务,执行线程任务,接收线程任务的返回值
Future<Double> f1 = ex.submit(new Callable<Double>() {
@Override
public Double call() throws Exception {
return 1.1;
}
});
System.out.println(f1);//地址值java.util.concurrent.FutureTask@7006c658
//使用Future接口中的方法V get()获取线程任务的返回值
Double d = f1.get();
System.out.println(d);
Future<Integer> f2 = ex.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
//返回一个0-100之间的随机数 [0,100)
return new Random().nextInt(100);
}
});
System.out.println(f2.get());
}
**Callable测试代码:**扩展
public static void main(String[] args) throws ExecutionException, InterruptedException {
//1.使用线程池工厂类Executors提供的静态方法newFixedThreadPool生产一个指定线程数量的线程池
ExecutorService ex = Executors.newFixedThreadPool(2);
//使用Scanner获取一个整数
System.out.println("请输入一个整数");
int i = new Scanner(System.in).nextInt();
//2.调用线程池ExecutorService中的方法submit,传递线程任务,执行线程任务,接收线程任务的返回值
Future<Integer> f = ex.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
int sum = 0;
//计算1-i之间的和
for (int j = 1; j <= i; j++) {
sum += j;
}
return sum;
}
});
System.out.println(f.get());
}
当需要启动一个线程去完成任务时,通常会通过 java.lang.Runnable 接口来定义任务内容,并使用
java.lang.Thread 类来启动该线程。
传统写法,代码如下:
public class Demo01ThreadNameless {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("多线程任务执行!");
}
}).start();
}
}
Lambda表达式写法,代码如下:
借助Java 8的全新语法,上述 Runnable 接口的匿名内部类写法可以通过更简单的Lambda表达式达到等效:
public class Demo02LambdaRunnable {
public static void main(String[] args) {
new Thread(() ‐> System.out.println("多线程任务执行!")).start(); // 启动线程
}
}
这段代码和刚才的执行效果是完全一样的,可以在1.8或更高的编译级别下通过。从代码的语义中可以看出:我们启动了一个线程,而线程任务的内容以一种更加简洁的形式被指定。
不再有“不得不创建接口对象”的束缚,不再有“抽象方法覆盖重写”的负担,就是这么简单!
Lambda省去面向对象的条条框框,格式由3个部分组成:
一些参数
一个箭头
一段代码
public static void main(String[] args) {
//使用匿名内部类的方式,实现多线程程序
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"-->执行了线程任务");
}
}).start();
//使用lambda表达式实现多线程程序
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"-->执行了线程任务");
}).start();
//使用Lambda的省略格式
new Thread(()->System.out.println(Thread.currentThread().getName()+"-->执行了线程任务")).start();
}
Lambda表达式的标准格式为:
(参数类型 参数名称) ‐> { 代码语句 }
格式说明:
小括号内的语法与传统方法参数列表一致:无参数则留空;多个参数则用逗号分隔。
-> 是新引入的语法格式,代表指向动作。
大括号内的语法与传统方法体要求基本一致。
匿名内部类与lambda对比:
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("多线程任务执行!");
}
}).start();
仔细分析该代码中, Runnable 接口只有一个 run 方法的定义:
public abstract void run();
即制定了一种做事情的方案(其实就是一个方法):
同样的语义体现在 Lambda 语法中,要更加简单:
() ‐> System.out.println("多线程任务执行!")
下面举例演示 java.util.Comparator 接口的使用场景代码,其中的抽象方法定义为:
创建一个数组,类型使用Person,存储Person对象
public class Person {
private String name;
private int age;
public Person() {}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", 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;}
}
使用Arrays数组工具类中的方法sort对象Person按照年龄进行降序排序
java.util.Arrays:static void sort(T[] a, Comparator super T> c)
public static void main(String[] args) {
//创建一个数组,类型使用Person.存储Person对象
Person[] arr = {
new Person("老王",18),
new Person("小明",17),
new Person("张三",30),
};
//使用Arrays数组工具类中的方法sort对象Person按照年龄进行降序排序
Arrays.sort(arr, new Comparator<Person>() {
@Override
public int compare(Person o1, Person o2) {
// o1-o2升序,o2-o1降序
return o2.getAge()-o1.getAge();
}
});
//使用Lambda表达式,简化匿名Comparator内部类
Arrays.sort(arr,(Person o1, Person o2)->{
return o2.getAge()-o1.getAge();
});
//使用Lambda表达式的简化格式
Arrays.sort(arr,(o1,o2)->o2.getAge()-o1.getAge());
//遍历数组
for (Person p : arr) {
System.out.println(p);
}
}
**省略规则:**在Lambda标准格式的基础上,使用省略写法的规则为:
**可推导即可省略:**Lambda强调的是“做什么”而不是“怎么做”,所以凡是可以根据上下文推导得知的信息,都可以省略。例如上例还可以使用Lambda的省略写法:
Runnable接口简化:
1. () ‐> System.out.println("多线程任务执行!")
Comparator接口简化:
2. Arrays.sort(array, (a, b) ‐> a.getAge() ‐ b.getAge());
只要确保接口中有且仅有一个抽象方法即可:
修饰符 interface 接口名称 {
public abstract 返回值类型 方法名称(可选参数信息);
// 其他非抽象方法内容
}
由于接口当中抽象方法的 public abstract 是可以省略的,所以定义一个函数式接口很简单:
public interface MyFunctionalInterface {
void myMethod();
}
对于刚刚定义好的 MyFunctionalInterface 函数式接口,典型使用场景就是作为方法的参数:
public class Demo09FunctionalInterface {
// 使用自定义的函数式接口作为方法参数
private static void doSomething(MyFunctionalInterface inter) {
inter.myMethod(); // 调用自定义的函数式接口方法
}
public static void main(String[] args) {
// 调用使用函数式接口的方法
doSomething(() ‐> System.out.println("Lambda执行啦!"));
}
}
与 @Override 注解的作用类似,Java 8中专门为函数式接口引入了一个新的注解: @FunctionalInterface 。该注解可用于一个接口的定义上:
@FunctionalInterface
public interface MyFunctionalInterface {
void myMethod();
}
一旦使用该注解来定义接口,编译器将会强制检查该接口是否确实有且仅有一个抽象方法,否则将会报错。不过,即使不使用该注解,只要满足函数式接口的定义,这仍然是一个函数式接口,使用起来都一样。
java.util.function.Supplier 接口,它意味着"供给" , 对应的Lambda表达式需要“对外提供”一个符合泛型类型的对象数据。
抽象方法 : get
仅包含一个无参的方法: T get() 。用来获取一个泛型参数指定类型的对象数据。
public class Demo08Supplier {
private static String getString(Supplier<String> function) {
return function.get();
}
public static void main(String[] args) {
String msgA = "Hello";
String msgB = "World";
System.out.println(getString(() ‐> msgA + msgB));
}
}
求数组元素最大值
使用 Supplier 接口作为方法参数类型,通过Lambda表达式求出int数组中的最大值。提示:接口的泛型请使用java.lang.Integer 类。
public class DemoIntArray {
public static void main(String[] args) {
int[] array = { 10, 20, 100, 30, 40, 50 };
printMax(() ‐> {
int max = array[0];
for (int i = 1; i < array.length; i++) {
if (array[i] > max) {
max = array[i];
}
}
return max;
});
}
private static void printMax(Supplier<Integer> supplier) {
int max = supplier.get();
System.out.println(max);
}
}
java.util.function.Consumer 接口则正好相反,它不是生产一个数据,而是消费一个数据,其数据类型由泛型参数决定。
抽象方法:accept
Consumer 接口中包含抽象方法 void accept(T t) ,意为消费一个指定泛型的数据。基本使用如:
import java.util.function.Consumer;
public class Demo09Consumer {
private static void consumeString(Consumer<String> function , String str) {
function.accept(str);
}
public static void main(String[] args) {
consumeString(s ‐> System.out.println(s));
}
}
默认方法:andThen
如果一个方法的参数和返回值全都是 Consumer 类型,那么就可以实现效果:消费一个数据的时候,首先做一个操作,然后再做一个操作,实现组合。而这个方法就是 Consumer 接口中的default方法 andThen 。下面是JDK的源代码:
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) ‐> { accept(t); after.accept(t); };
}
java.util.Objects 的 requireNonNull 静态方法将会在参数为null时主动抛出NullPointerException 异常。这省去了重复编写if语句和抛出空指针异常的麻烦。
要想实现组合,需要两个或多个Lambda表达式即可,而 andThen 的语义正是“一步接一步”操作。例如两个步骤组合的情况:
public class Demo10ConsumerAndThen {
private static void consumeString(Consumer<String> one, Consumer<String> two,String str) {
one.andThen(two).accept(str);
}
public static void main(String[] args) {
consumeString(
s ‐> System.out.println(s.toUpperCase()),
s ‐> System.out.println(s.toLowerCase()),
"HeLLo");
}
}
运行结果将会首先打印完全大写的HELLO,然后打印完全小写的hello。当然,通过链式写法可以实现更多步骤的组合。
java.util.function.Function
抽象方法:apply
Function 接口中最主要的抽象方法为: R apply(T t) ,根据类型T的参数获取类型R的结果。使用的场景例如:将 String 类型转换为 Integer 类型。
public class Demo11FunctionApply {
private static void method(Function<String, Integer> function, Str str) {
int num = function.apply(str);
System.out.println(num + 20);
}
public static void main(String[] args) {
method(s ‐> Integer.parseInt(s) , "10");
}
}
默认方法:andThen
Function 接口中有一个默认的 andThen 方法,用来进行组合操作。JDK源代码如:
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) ‐> after.apply(apply(t));
}
该方法同样用于“先做什么,再做什么”的场景,和 Consumer 中的 andThen 差不多:
public class Demo12FunctionAndThen {
private static void method(Function<String, Integer> one, Function<Integer, Integer> two,
String str) {
int num = one.andThen(two).apply(str);
System.out.println(num + 20);
}
public static void main(String[] args) {
method(s ‐> Integer.parseInt(s), i ‐> i *= 10, "10");
}
}
第一个操作是将字符串解析成为int数字,第二个操作是乘以10。两个操作通过 andThen 按照前后顺序组合到了一起。
请注意,Function的前置条件泛型和后置条件泛型可以相同。
有时候我们需要对某种类型的数据进行判断,从而得到一个boolean值结果。这时可以使用
java.util.function.Predicate 接口。
抽象方法:test
Predicate 接口中包含一个抽象方法: boolean test(T t) 。用于条件判断的场景:
public class Demo15PredicateTest {
private static void method(Predicate<String> predicate,String str) {
boolean veryLong = predicate.test(str);
System.out.println("字符串很长吗:" + veryLong);
}
public static void main(String[] args) {
method(s ‐> s.length() > 5, "HelloWorld");
}
}
//条件判断的标准是传入的Lambda表达式逻辑,只要字符串长度大于5则认为很长。
默认方法:and
既然是条件判断,就会存在与、或、非三种常见的逻辑关系。其中将两个 Predicate 条件使用“与”逻辑连接起来实现“并且”的效果时,可以使用default方法 and 。其JDK源码为:
default Predicate<T> and(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) ‐> test(t) && other.test(t);
}
如果要判断一个字符串既要包含大写“H”,又要包含大写“W”,那么:
public class Demo16PredicateAnd {
private static void method(Predicate<String> one, Predicate<String> two,String str) {
boolean isValid = one.and(two).test(str);
System.out.println("字符串符合要求吗:" + isValid);
}
public static void main(String[] args) {
method(s ‐> s.contains("H"), s ‐> s.contains("W"),"Helloworld");
}
}
默认方法:or
与 and 的“与”类似,默认方法 or 实现逻辑关系中的“或”。JDK源码为:
default Predicate<T> or(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) ‐> test(t) || other.test(t);
}
如果希望实现逻辑“字符串包含大写H或者包含大写W”,那么代码只需要将“and”修改为“or”名称即可,其他都不变:
public class Demo16PredicateAnd {
private static void method(Predicate<String> one, Predicate<String> two,String str) {
boolean isValid = one.or(two).test(str);
System.out.println("字符串符合要求吗:" + isValid);
}
public static void main(String[] args) {
method(s ‐> s.contains("H"), s ‐> s.contains("W"),"Helloworld");
}
}
默认方法:negate
“与”、“或”已经了解了,剩下的“非”(取反)也会简单。默认方法 negate 的JDK源代码为:
default Predicate<T> negate() {
return (t) ‐> !test(t);
}
从实现中很容易看出,它是执行了test方法之后,对结果boolean值进行“!”取反而已。一定要在 test 方法调用之前调用 negate 方法,正如 and 和 or 方法一样:
import java.util.function.Predicate;
public class Demo17PredicateNegate {
private static void method(Predicate<String> predicate,String str) {
boolean veryLong = predicate.negate().test(str);
System.out.println("字符串很长吗:" + veryLong);
}
public static void main(String[] args) {
method(s ‐> s.length() < 5, "Helloworld");
}
}