Day 26开篇:
"今天java基础主要学习了线程通信,Lambda表达式,Stream等"
知识点反馈:
今天的知识点总结的思维导图
一.线程通信
1. Lock锁、synchronized的对比
(1)Lock是一个显示锁(锁开跟锁关都需要手动进行操作,别忘记了释放锁资源)
(2)synchronized是一个隐式锁,完全靠作用域操作,不需要手动管理
(3)Lock只有代码块锁,synchronized既有代码块也有方法锁
(4)Lock的性能比较好,Jvm只需要花比较少的时间来调用代码,而且扩展性也比较强(Lock是一个接口,接口可以多实现)
如果是简单的同步代码块,可以优先考虑Lock,如果是同步方法只能使用synchronized但是在实际的开发当中,synchronized的使用场景还是比Lock高,因为Lock是一个新特性。
2.一个类似的案例(你们熟悉的学生对象)
资源:学生对象Student
获取线程:GetStudent
设置线程:SetStudent
案例产生问题:
(1)每次获取的数据都是null---0---/u00000
原因:是因为获取和设置两个线程分别使用了不同的Student对象
解决:创建一个Student的栈空间,不进行赋值,交给构造器 要求调用者进行赋值堆空间
(2)数据获取的不完整,甚至造成了数据错乱
原因:因为有可能设置线程并没有抢到执行权,所有获取线程就拿不到数据,也有可能设置或者获取线程执行过程当中,相互抢夺了,所以造成数据错乱
解决:给两个线程,都加上synchronize同步锁,至少可以保证两个线程在执行过程当中不会被打断,从而造成数据错乱的问题
为了保证数据的真实准确,我们加入死循环不断生产和消费数据(事实证明,数据错乱的问题,已经解决好了)
(3)读到的数据仍然可能是null---0---/u0000
原因:设置线程并没有从第一次就抢到执行权,所有获取线程拿到的就是空数据
3.何谓线程通信?
(1)其实就是两个线程之间,产生一个互相连接通知的过程。
(2)void wait():使当前线程挂起并放弃CPU的执行权,同步资源并等待,使其他线程可以访问并修改共享资源,wait一旦被执行必须调用
(3)notify或者notifyAll进行唤醒,唤醒后在就绪位置重新进行CPU的抢夺。
(4)notify():唤醒正在排队(阻塞状态)等待同步资源线程中优先级别最高的线程进行操作。
(5)notifyAll():唤醒所有正在排队(阻塞状态)等待同步线程的资源
(6)为什么wait/notify不是在Thread里面,反而跑到Object里面?
wait/notify进行操作的前提一定是基于一个同步监视器(对象锁)来完成。而对象锁又可以是任何的锁对象(任意类都没问题),所有的类都是Object的子类,所以放到Object里面避免类型接收和转换的问题。
(7)notify:是唤醒线程,只不过是把线程从一个阻塞状态调整为就绪状态,所以不保证一定执行,只能有抢夺执行权的机会。
这个案例的整个编码的过程,其实就是一个非常经典的生产者和消费者设计模式。
例子:
public class Demo {
public static void main(String[] args) {
//保证get和set两个线程获取的是同一个资源
Student s = new Student();
GetStundet gs = new GetStundet(s);
SetStudent ss = new SetStudent(s);
//创建设置Thread
Thread t1 = new Thread(ss);
//创建获取Thread
Thread t2 = new Thread(gs);
t2.start();
t1.start();
}
}
二.Lambda表达式
1.JDK8的介绍:
(1) java 8,是java语言再1.5版本之后更新最大的一个版本。是2014年3年发布可以看做是能够和java5并肩的一个重量级版本
(2)特性:
速度更快
代码更少(新增的新的语法:Lambda表达式)
非常的强大的StreamAPI
最大的减少了空指针异常的操作:Optional(谷歌写着玩)
Nashorm引擎,运行JVM当中操作JavaScrpit代码
2. 为什么要去使用Lambda表达式?
Lambda其实本质上就是一个匿名函数,可以将Lambda表达式理解成一个一段可以进行执行传递的代码,他可以写出更加简洁,更加灵活的代码,是一个非常紧凑型的代码分割。
3.Lambda的格式解释:
() -> {
System.out.println("我是一个Lambda");
};
(1) ->:lambda的操作符号(箭头操作符)
(2)左边数据:lambda的形参列表(其实就你重新接口那个抽象方法的形参列表)
(3)右边数据:lambda的方法体(其实就是你重写那个接口具体方法体)
4. lambda只能用于接口?
是的,而且只能用于函数式接口(整个接口当中只存一个抽象方法)
5.lambda是怎么找到这方法?
因为只能用于存在一个抽象方法的接口,所以完全采用了自动推断机制进行推断
6.lambda的具体使用(看下面例子代码)
7.关于省略的问题:
(1)左边:所有的形参的数据类型可以直接省略,实行自动推断。如果形参列表只有一个的时候,甚至连括号都可以直接省略掉
(2)右边:当语句体里面值存在一句代码的时候,可以省略大括号,甚至可以连return省略掉
8.啥时候用它呢?
凡是可以使用内部类实现的,都可以使用lambda表达式(这个接口有且只有一个抽象)
例子:
package lambda;
import java.util.Comparator;
import java.util.function.Consumer;
public class LambdaTest02 {
public static void main(String[] args) {
//test01();
//test02();
//test03();
//test04();
//当你的lambda表达式,语句体里面只存在一句代码的时候,可以将大括号、return都直接省略
Comparator<Integer> com = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1+o2;
}
};
System.out.println(com.compare(2,1));
System.out.println("--------------");
Comparator<Integer> com2 = (o1,o2) -> o1+o2;
System.out.println(com2.compare(2,1));
}
public static void test04() {
Comparator<Integer> com = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1+o2;
}
};
System.out.println(com.compare(2,1));
System.out.println("--------------");
Comparator<Integer> com2 = (o1,o2) -> {
System.out.println(o1);
System.out.println(o2);
return o1+o2;
};
System.out.println(com2.compare(2,1));
}
public static void test03() {
//语法格式三:使用Lambda的时候,所有的参数列表的数据类型可以省略,因为完全实现自动推断
Consumer<String> con = new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
};
con.accept("这个世界上最美的女人");
System.out.println("---------------");
//Consumer con1 = (s) -> System.out.println(s);
//当你的形参只有一个的时候,连形参括号都可以直接省略
Consumer con1 = s -> System.out.println(s);
con1.accept("小花");
}
public static void test02() {
//语法格式二:一个形式参数,没有返回值
Consumer<String> con = new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
};
con.accept("这个世界上最美的女人");
System.out.println("---------------");
Consumer<String> con1 = (String s) -> {
System.out.println(s);
};
con1.accept("小花");
}
public static void test01() {
//语法格式一:方法无参也无返回值
Runnable r1 = new Runnable() {
@Override
public void run() {
System.out.println("我是一个内部类");
}
};
r1.run();
System.out.println("---------------");
Runnable r2 = () -> {
System.out.println("我是一个Lambda");
};
r2.run();
}
}
9. 方法引用:
(1)使用场景:当要传递给Lambda体的操作,已经有实现的方法,就可以使用方法引用。
(2)具体介绍:本质上就是Lambda表达式,而Lambda表达式作为函数式接口的实例,所以方法引用就是函数数接口实例。一样。
(3)格式:
类(具体对象) :: 方法名(必须是成立的)
例子:
import java.io.PrintStream;
import java.util.function.Consumer;
import java.util.function.Supplier;
public class MethodRef {
public static void main(String[] args) {
//test01();
Student s = new Student("toobug", 19);
Supplier<String> sup = () -> s.getName();
System.out.println(sup.get());
System.out.println("-------------");
Supplier<String> sup1 = s::getName;
System.out.println(sup1.get());
}
public static void test01() {
Consumer<String> con = new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
};
con.accept("TOOBUG");
System.out.println("-----------------");
Consumer<String> con1 = (String str) -> System.out.println(str);
con1.accept("TOOBUG");
System.out.println("-----------------");
PrintStream ps = System.out;
Consumer<String> con2 = ps::println;//自动推断
con2.accept("toobug");
}
}
三.Stream
1.Java8有两大重点特性:Lambda和StreamAPI
2.StreamAPI:是真正将一个函数式编程的风格引入到Java当中的一种技术,可以让程序员写出更加高效、干净简洁的代码
3.Stream是Java8当中处理集合的关键抽象概念,可以对你指定的集合进程非常负载的操作,比如集合的查找、过滤、映射数据等操作,使用Stream可以对复杂数据进行操作,就有点类似于SQL执行数据操作一样。
4.Stream和Collection集合的区别是什么?
(1)Collection是一种静态的内存数据结构。
(2) Stream是一个面向CPU的计算结构。
5.Stream特点:
(1)Stream本身不会存储数据(数据从集合过来,和迭代器一样)
- Stream本身并不会改变源数据,但是他会返回一个持有将结果的新的Stream对象
(3)Stream是延时的,只有在你关闭的时候才会同一返回数据(类似缓冲流)
6.Stream创建的四个步骤:
(1)准备一个数据源(可以是集合,也可以是数组)
(2)获取一个流
(3)自己进行中间操作链(其实就是对集合进行处理的一大堆步骤)
(4)终止操作
7.问题:获取流
java8当中,其实对Collection的接口进行一个扩展,这个扩展提供了两个流的获取方法:(1)顺序流:default Stream<E> stream;
(2)并行流:default Stream<E> parallelStream;
例子:
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.stream.Stream;
public class StreamAPIDemo {
public static void main(String[] args) {
//准备好数据源
List<String> list = Arrays.asList("小马","小明","小军","小东");
//获取集合的顺序流
Stream<String> stream = list.stream();
System.out.println(stream);
//获取一个并行流
Stream<String> stream1 = list.parallelStream();
System.out.println(stream1);
//获取所有偶数,并且输出
//无限流(无止境生产数据)
Stream.iterate(0,t -> t + 2).forEach(System.out::println);
}
}