Day 26开篇:

      

       "今天java基础主要学习了线程通信,Lambda表达式,Stream等"




知识点反馈:


今天的知识点总结的思维导图

java 代码锁和数据库锁 java锁的区别_System

 

一.线程通信

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本身不会存储数据(数据从集合过来,和迭代器一样)

  1. 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);
    }
}