当我们需要创建大量线程时,每次线程的创建和销毁都需要消耗一定性能,频繁创建和销毁肯定会降低系统效率
那么我们就可以事先创建好一堆线程,保存到某个容器中,需要使用线程时,从线程的容器中获取即可
线程池:其实就是一个容纳多个线程的容器,并且线程池中线程执行完一个任务之后,线程不销毁,从新返回到线程池中,以便下次使用
线程池的好处:
a.节省频繁创建和销毁线程的过程,提高了系统的性能
b.根据系统的配置,安排适当的线程数量,不会导致系统压力过大而宕机
c.由于事先已经创建好了很多线程,提高用户的响应速度
线程池最最最顶层的根接口:
java.util.concurrent.Executor
根接口的子接口:
java.util.concurrent.ExecutorService
工具类,专门帮助我们创建线程池对象:
java.util.concurrent.Executors
静态方法:
public static ExecutorService newFixedThreadPool(int nThreads); 创建一个线程数固定的线程池
有了线程池之后,我们只需要向线程池中提交任务即可!!!
public Future<?> submit(Runnable task);向线程池中提交一个任务,返回结果是null
public Future<?> submit(Callable<T> task);向线程池中提交一个任务,有返回值,返回Future对象
public class ThreadPoolDemo {
public static void main(String[] args) {
//1.创建一个线程池对象
ExecutorService service = Executors.newFixedThreadPool(3);
//2.提交无返回值的任务
for (int i = 0; i < 10; i++) {
service.submit(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "执行了...");
}
});
}
//3.提交有返回值的任务
Future<Integer> future = service.submit(new Callable<Integer>() {
@Override
public Integer call() throws InterruptedException {
int sum = 0;
for (int i = 1; i < 101; i++) {
Thread.sleep(50);
sum += i;
}
return sum;
}
});
System.out.println("future对象得到了..");
//通过future对象获取结果
Integer result = future.get();
System.out.println("从future对象中获取任务结果..");
System.out.println("执行结果:"+result);
}
}
}
两个线程,执行同一个任务,但是需要获取两次锁对象才能执行任务,
当线程1获取第一个锁,当线程2获取第二个锁,线程1会等待第二个锁,线程2会等待第一锁,此时的现象就称为死锁
a.至少2个两线程
b.至少2把锁对象
c.获取锁需要嵌套(反向嵌套)
public class DeadLockDemo {
public static void main(String[] args) {
//1.2个锁对象
Object obj1 = new Object();
Object obj2 = new Object();
//2.两个线程池
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
synchronized (obj1) {
System.out.println("线程1获取了锁对象obj1,等待获取锁对象obj2");
synchronized (obj2) {
System.out.println("线程1的任务执行了...");
}
}
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
synchronized (obj2) {
System.out.println("线程2获取了锁对象obj2,等待获取锁对象obj1");
synchronized (obj1) {
System.out.println("线程2的任务执行了...");
}
}
}
}
}).start();
}
}
注意:我们应该尽量避免死锁
新建状态(New)
刚刚创建的线程且没有调用start方法
可运行状态(Runnable)
处于新建状态的线程,调用start方法
受(锁)阻塞状态(Blocked)
当前线程需要锁对象,但是锁对象被其他线程持有
限时等待状态(Timed_waiting)
在线程任务中调用Thread.sleep(毫秒值)
无限等待状态(Waiting)
线程如何进入Waiting(无线等待状态)
a.当前线程必须先持有锁对象
b.调用锁对象.wait()方法,进入无限等待
c.进入无限等待之前,线程会自动释放锁
其他线程如何唤醒Waiting状态的线程
a.其他线程持有锁对象(必须是无限等待线程释放的那个锁对象)
b.调用锁对象.notify()方法,唤醒无限等待的线程
c.被唤醒的线程进入锁阻塞状态,直到再次抢到锁
消亡状态(Terminated)
线程的任务执行完毕,此时线程就会进入消亡
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fpZQMn3e-1591441676737)(img/image-20191228101138166.png)]
public class WaitAndNotifyDemo {
public static void main(String[] args) throws InterruptedException {
//0.创建一个锁对象
Object obj = new Object();
//1.创建一个线程
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程1即将获取锁obj..");
synchronized (obj){
System.out.println("线程1获取到了锁obj...");
System.out.println("线程1进入了无限等待...");
try {
obj.wait(); //进入无限等待,并自动释放锁对象
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程1从无限等待中醒来了..");
}
}
}).start();
for (int i = 0; i < 10; i++) {
Thread.sleep(500);
System.out.println(i);
}
//2.在创建一个线程
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程2来了..");
synchronized (obj) {
System.out.println("线程2获取到了obj锁...");
System.out.println("线程2把线程1唤醒了...");
obj.notify();
//这个循环的目的: 让线程2唤醒线程1之后,不要立刻释放锁
//此时线程1醒来了,但是由于没有锁对象,进入锁阻塞状态
//线程2数完10下之后,才释放锁,线程1才能再次获取锁进入运行状态
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(i);
}
}
}
}).start();
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-h1IieWmF-1591441676738)(img/image-20191228113820866.png)]
//盘子类,里面有包子
public class PanZi {
boolean flag = true;// true代表有包子 false 没有包子
}
public class ProducerAndCustomerDemo {
public static void main(String[] args) {
//1.创建一个盘子
PanZi pz = new PanZi();
Object obj = new Object();
//2.生产者线程
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
//加锁
synchronized (obj) {
//判断
if (pz.flag == true) {
//进入WAITING
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//做包子
pz.flag = true;
System.out.println("生产者的包子做好了...");
//唤醒消费者
System.out.println("吃货来吧...");
obj.notify();
}
}
}
}).start();
//3.消费者线程
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
//加锁
synchronized (obj) {
//判断
if (pz.flag == false) {
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//吃包子
pz.flag = false;
System.out.println("包子真好吃....");
//唤醒生产者
System.out.println("再给哥做一个....");
obj.notify();
}
}
}
}).start();
}
}
只强调输入量,计算过程和输出量,而不强调其他语法形式
public class LambdaDemo {
public static void main(String[] args) {
//1.使用传统的面向对象思想,开启线程
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"执行了..");
}
}).start();
//分析:以上代码的冗余之处
/**
* a.由于面向对象语法的要求,我们不得不创建Runnable的实现类
* b.由于面向对象语法的要求,我们不得不重写run方法(和接口中的run必须一模一样)
* c.除了任务代码,其他似乎都是面向对象逼我的,真正我心甘情愿就是任务代码
*/
}
}
Lambda思想: 强调输入量(参数)-->计算过程(方法体)-->输出量(返回值)
//使用Lambda可以替代匿名内部类
new Thread(()->{System.out.println(Thread.currentThread().getName()+"执行了..");}).start();
Lambda的标准格式:
(参数列表)->{方法体;return 返回值;}
参数列表: 输入量(格式: 数据类型 参数名,数据类型 参数,...)
方法体: 计算过程(一句或者多句代码)
return语句: 输出量 (return 结果;)
public class LambdaDemo {
public static void main(String[] args) {
//1.创建一个数组
Integer[] arr = {3, 4, 678, 1, 5, 67, 33};
//2.排序
// Arrays.sort(arr, new Comparator() {
// @Override
// //口诀: 升序 前-后
// public int compare(Integer o1, Integer o2) {
// return o2-o1;
// }
// });
//使用Lambda替代匿名内部类
Arrays.sort(arr,(Integer o1, Integer o2)->{return o2-o1;});
//3.打印
System.out.println(Arrays.toString(arr));
}
}
public class Dog {
private int age;
private String name;
private int legs;
//..其他省略
}
public class LambdaDemo01 {
public static void main(String[] args) {
//1.创建一个数组
ArrayList<Dog> dogs = new ArrayList<Dog>();
//2.添加数据
dogs.add(new Dog(2, "旺财", 3));
dogs.add(new Dog(3, "来福", 2));
dogs.add(new Dog(1, "哮天犬", 4));
dogs.add(new Dog(5, "大黄", 5));
//3.对集合进行排序
// Collections.sort(dogs, new Comparator() {
// @Override
// public int compare(Dog o1, Dog o2) {
// //按照狗的腿数升序排序
// return o1.getLegs()-o2.getLegs();
// }
// });
//使用Lambda替代匿名内部类
Collections.sort(dogs,(Dog o1, Dog o2)->{return o1.getLegs()-o2.getLegs();});
//4.打印
for (Dog dog : dogs) {
System.out.println(dog);
}
}
}
a.参数列表中数据类型可以省略
b.如果参数只有一个,那么小括号可以省略
c.如果方法体和返回值可以写成一句代码,那么{},return,和;可以同时省略
//使用Lambda可以替代匿名内部类
new Thread(()->{System.out.println(Thread.currentThread().getName()+"执行了..");}).start();
//使用Lambda的省略格式
new Thread(()->System.out.println(Thread.currentThread().getName()+"执行了..")).start();
//使用Lambda替代匿名内部类
Arrays.sort(arr,(Integer o1, Integer o2)->{return o2-o1;});
//使用Lambda的省略格式
Arrays.sort(arr, (o1, o2) -> o2 - o1);
//使用Lambda替代匿名内部类
Collections.sort(dogs,(Dog o1, Dog o2)->{return o1.getLegs()-o2.getLegs();});
//使用Lambda的省略格式
Collections.sort(dogs, (o1, o2) -> o1.getLegs() - o2.getLegs());
a.Lambda只能用于替代函数式接口的匿名内部类
函数式接口:有且只有一个抽象方法的接口
b.Lambda的省略格式只有以上三种,其他均不可省略(可推导即可省略)
/**
* 传统的集合遍历
*/
public class StreamDemo01 {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("张无忌");
list.add("周芷若");
list.add("赵敏");
list.add("张强");
list.add("张三丰");
// 1. 首先筛选所有姓张的人;
List<String> one = new ArrayList<String>();
for (String s : list) {
if (s.startsWith("张")) {
one.add(s);
}
}
// 2. 然后筛选名字有三个字的人;
List<String> two = new ArrayList<String>();
for (String s : one) {
if (s.length() == 3) {
two.add(s);
}
}
// 3. 最后进行对结果进行打印输出。
for (String s : two) {
System.out.println(s);
}
}
}
a.注重形式(面向对象语法的束缚)
每次都需要定义集合
每次都需要遍历集合
list.stream().filter(s->s.startsWith("张")).filter(s->s.length()==3).
forEach(s-> System.out.println(s));
流式思想:Stream流式思想
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Mw6oq4tU-1591441676739)(img/image-20191228160714763.png)]
集合获取流:
调用集合的一个方法:stream()
数组获取流:
Stream.of(数组)
/**
* 容器如何获取流
*/
public class StreamDemo02 {
public static void main(String[] args) {
//1.集合获取流
ArrayList<String> arr1 = new ArrayList<String>();
Stream<String> stream1 = arr1.stream();
HashSet<String> set1 = new HashSet<String>();
Stream<String> stream2 = set1.stream();
//双列集合无法直接获取流(了解即可)
// HashMap map = new HashMap();
// Stream stream3 = map.keySet().stream();
// Stream> stream4 = map.entrySet().stream();
//2.数组获取流
Integer[] arr = new Integer[10];
Stream<Integer> stream = Stream.of(arr);
}
}
//1.获取一个Stream对象
Stream<String> stream = Stream.of("jack", "rose", "bob", "james", "tom", "jerry");
逐个处理:forEach(代码演示)
//2.foreach 逐一遍历
stream.forEach(new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
});
//使用Lambda替代匿名内部类
stream.forEach((String s)->{System.out.println(s);});
//省略格式
stream.forEach(s -> System.out.println(s));
统计个数:count(代码演示)
//3.count 统计个数
long count = stream.count();
System.out.println("流中有"+count+"个元素");
过滤:filter(代码演示)
//4.filter 过滤方法
Stream<String> stream1 = stream.filter(new Predicate<String>() {
@Override
public boolean test(String s) {
//只要长度为5的元素
return s.length() == 5;
}
});
//使用Lambda替换匿名内部类
Stream<String> stream1 = stream.filter((String s)->{return s.length() == 5;});
//省略格式
Stream<String> stream1 = stream.filter(s-> s.length() == 5);
stream1.forEach(s -> System.out.println(s));
取前几个:limit(代码演示)
//5.limit 取前几个
Stream<String> stream1 = stream.limit(3);
stream1.forEach(s -> System.out.println(s));
跳过前几个:skip(代码演示)
//6.skip 跳过前几个
Stream<String> stream1 = stream.skip(2);
stream1.forEach(s -> System.out.println(s));
映射方法:map(代码演示)
//7.map 映射方法
Stream<Integer> stream1 = stream.map(new Function<String,Integer>() {
@Override
public Integer apply(String s) {
return s.length();
}
});
//使用Lambda代替匿名内部类
Stream<Integer> stream1 = stream.map((String s)->{return s.length();});
//使用Lambda的省略格式
Stream<Integer> stream1 = stream.map(s -> s.length());
stream1.forEach(i -> System.out.println(i));
静态方法合并流:concat(代码演示)
Stream的静态方法:合并两个流
//8.concat 合并流
Stream<String> one = Stream.of("jack","rose");
Stream<String> two = Stream.of("bob", "tom");
//合并
Stream<String> all = Stream.concat(one, two);
//foreach
all.forEach(s -> System.out.println(s));
问题1:
如果有三个流,怎么合并? 先合并两个,再和第三个合并
问题2:
合并的这个两个流的泛型有没有要求呢????
没有!!! 合并之后的流的泛型可以写两个流泛型的共同父类
Stream<Double> one1 = Stream.of(1.1,2.2,3.3);
Stream<Integer> two1 = Stream.of(10, 20);
Stream<Number> all1 = Stream.concat(one1,two1);
public class StreamDemo04 {
public static void main(String[] args) {
List<String> one = new ArrayList<>();
one.add("迪丽热巴");
one.add("宋远桥");
one.add("苏星河");
one.add("老子");
one.add("庄子");
one.add("孙子");
one.add("洪七公");
List<String> two = new ArrayList<>();
two.add("古力娜扎");
two.add("张无忌");
two.add("张三丰");
two.add("赵丽颖");
two.add("张二狗");
two.add("张天爱");
two.add("张三");
// 1. 第一个队伍只要名字为3个字的成员姓名;
// 2. 第一个队伍筛选之后只要前3个人;
Stream<String> streamOne = one.stream().filter(s -> s.length() == 3).limit(3);
// 3. 第二个队伍只要姓张的成员姓名;
// 4. 第二个队伍筛选之后不要前2个人;
Stream<String> streamTwo = two.stream().filter(s -> s.startsWith("张")).skip(2);
// 5. 将两个队伍合并为一个队伍;
Stream<String> streamNames = Stream.concat(streamOne, streamTwo);
// 6. 根据姓名创建 Person 对象,映射
Stream<Person> personStream = streamNames.map(s -> new Person(s));
// 7. 打印整个队伍的Person对象信息
personStream.forEach(p-> System.out.println(p));
// 大神专用,我们了解即可
// Stream.concat(
// one.stream().filter(s -> s.length() == 3).limit(3),
// two.stream().filter(s -> s.startsWith("张")).skip(2)
// ).map(s -> new Person(s))
// .forEach(p -> System.out.println(p));
}
}
函数拼接方法(模型拼接方法)
如果调用方法之后,返回的还是流对象,此方法称为函数拼接方法
filter方法
limit方法
skip方法
map方法
concat方法
所有的函数拼接方法支持链式编程!!!!
终结方法
如果调用方法之后,返回的不是流对象或者没有返回值,此方法称为终结方法
foreach方法
count方法
所有的总结方法,不支持链式编程,一个流只能调用一次终结方法,调用完毕之后流会关闭
调用流对象的collect方法
//9.收集流的结果
//收集到List集合中
List<String> list = stream.collect(Collectors.toList());
System.out.println(list);
//收集到Set集合中
Set<String> set = stream.collect(Collectors.toSet());
System.out.println(set);
//收集到数组中
Object[] objs = stream.toArray();
System.out.println(Arrays.toString(objs));
能够描述Java中线程池运行原理
能够描述死锁产生的原因
"能够说出线程6个状态的名称,并且能够描述他们之间的转换
"能够理解等待唤醒案例(基本案例,吃包子案例)
"能够掌握Lambda表达式的标准格式与省略格式
"能够通过集合、映射或数组方式获取流
"能够掌握常用的流操作
======"只要我们完成最后一个Stream流的综合即可"==========
能够将流中的内容收集到集合和数组中