- 浏览: 369260 次
- 性别:
- 来自: 成都
文章分类
最新评论
-
tuspark:
关于javadoc这里讲解的更全面:《javadoc设置》。
Eclipse中生成javadoc【Z】 -
yuexiang1007:
帮我解决了问题,谢谢!!!
java.math.BigInteger使用心得总结 -
netwelfare:
个人感觉,文章对HashMap的遍历分析的有点浅,不如这里的介 ...
HashMap遍历的两种方式【Z】 -
memoryisking:
关于java.math.BigInteger讲解在这里可以看到 ...
java.math.BigInteger使用心得总结 -
巴尾的兔兔帅:
divide应该是除吧?不是减。dividepublic Bi ...
java.math.BigInteger使用心得总结
进程和线程的主要差别在于它们是不同的操作系统资源管理方式。进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。
首先要理解线程首先需要了解一些基本的东西,我们现在所使用的大多数操作系统都属于多任务,分时操作系统。正是由于这种操作系统的出现才有了多线程这个概念。我们使用的windows,linux
就
属于此列。什么是分时操作系统呢,通俗一点与就是可以同一时间执行多个程序的操作系统,在自己的电脑上面,你是不是一边听歌,一边聊天还一边看网页呢?但
实际上,并不上cpu在同时执行这些程序,cpu只是将时间切割为时间片,然后将时间片分配给这些程序,获得时间片的程序开始执行,不等执行完毕,下个程
序又获得时间片开始执行,这样多个程序轮流执行一段时间,由于现在cpu的高速计算能力,给人的感觉就像是多个程序在同时执行一样。
一般可以在同
一时间内执行多个程序的操作系统都有进程的概念.一个进程就是一个执行中的程序,而每一个进程都有自己独立的一块内存空间,一组系统资源.在进程概念中,
每一个进程的内部数据和状态都是完全独立的.因此可以想像创建并执行一个进程的系统开像是比较大的,所以线程出现了。在java中,程序通过流控制来执行
程序流,程序中单个顺序的流控制称为线程,多线程则指的是在单个程序中可以同时运行多个不同的线程,执行不同的任务.多线程意味着一个程序的多行语句可以
看上去几乎在同一时间内同时运行.(你可以将前面一句话的程序换成进程,进程是程序的一次执行过程,是系统运行程序的基本单位)
线程与进
程相似,是一段完成某个特定功能的代码,是程序中单个顺序的流控制;但与进程不同的是,同类的多个线程是共享一块内存空间和一组系统资源,而线程本身的数
据通常只有微处理器的寄存器数据,以及一个供程序执行时使用的堆栈.所以系统在产生一个线程,或者在各个线程之间切换时,负担要比进程小的多,正因如此,
线程也被称为轻负荷进程(light-weight process).一个进程中可以包含多个线程.
多任务是指在一个系统中可以同时运
行多个程序,即有多个独立运行的任务,每个任务对应一个进程,同进程一样,一个线程也有从创建,运行到消亡的过程,称为线程的生命周期.用线程的状态
(state)表明线程处在生命周期的哪个阶段.线程有创建,可运行,运行中,阻塞,死亡五中状态.通过线程的控制与调度可使线程在这几种状态间转化每个
程序至少自动拥有一个线程,称为主线程.当程序加载到内存时,启动主线程.
[线程的运行机制以及调度模型]
java中多线程就
是一个类或一个程序执行或管理多个线程执行任务的能力,每个线程可以独立于其他线程而独立运行,当然也可以和其他线程协同运行,一个类控制着它的所有线
程,可以决定哪个线程得到优先级,哪个线程可以访问其他类的资源,哪个线程开始执行,哪个保持休眠状态。
下面是线程的机制图:
线程的状态表示线程正在进行的活动以及在此时间段内所能完成的任务.线程有创建,可运行,运行中,阻塞,死亡五中状态.一个具有生命的线程,总是处于这五种状态之一:
1.创建状态
使用new运算符创建一个线程后,该线程仅仅是一个空对象,系统没有分配资源,称该线程处于创建状态(new thread)
2.可运行状态
使用start()方法启动一个线程后,系统为该线程分配了除CPU外的所需资源,使该线程处于可运行状态(Runnable)
3.运行中状态
Java
运行系统通过调度选中一个Runnable的线程,使其占有CPU并转为运行中状态(Running).此时,系统真正执行线程的run()方法.
4.阻塞状态
一个正在运行的线程因某种原因不能继续运行时,进入阻塞状态(Blocked)
5.死亡状态
线程结束后是死亡状态(Dead)
同一时刻如果有多个线程处于可运行状态,则他们需要排队等待CPU资源.此时每个线程自动获得一个线程的优先级(priority),优先级的高低反映线程的重要或紧急程度.可运行状态的线程按优先级排队,线程调度依据优先级基础上的"先到先服务"原则.
线程调度管理器负责线程排队和CPU在线程间的分配,并由线程调度算法进行调度.当线程调度管理器选种某个线程时,该线程获得CPU资源而进入运行状态.
线程调度是先占式调度,即如果在当前线程执行过程中一个更高优先级的线程进入可运行状态,则这个线程立即被调度执行.先占式调度分为:独占式和分时方式.
独占方式下,当前执行线程将一直执行下去,直 到执行完毕或由于某种原因主动放弃CPU,或CPU被一个更高优先级的线程抢占
分时方式下,当前运行线程获得一个时间片,时间到时,即使没有执行完也要让出CPU,进入可运行状态,等待下一个时间片的调度.系统选中其他可运行状态的线程执行
分时方式的系统使每个线程工作若干步,实现多线程同时运行
另外请注意下面的线程调度规则(如果有不理解,不急,往下看):
①如果两个或是两个以上的线程都修改一个对象,那么把执行修改的方法定义为被同步的(Synchronized),如果对象更新影响到只读方法,那么只度方法也应该定义为同步的
②如果一个线程必须等待一个对象状态发生变化,那么它应该在对象内部等待,而不是在外部等待,它可以调用一个被同步的方法,并让这个方法调用wait()
③每当一个方法改变某个对象的状态的时候,它应该调用notifyAll()方法,这给等待队列的线程提供机会来看一看执行环境是否已发生改变
④
记住wait(),notify(),notifyAll()方法属于Object类,而不是Thread类,仔细检查看是否每次执行wait()方法都
有相应的notify()或notifyAll()方法,且它们作用与相同的对象
在java中每个类都有一个主线程,要执行一个程序,那么这个类当中一定要有main方法,这个man方法也就是java
class中的主线程。你可以自己创建线程,有两种方法,一是继承Thread类,或是实现Runnable接口。一般情况下,最好避免继承,因为
java中是单根继承,如果你选用继承,那么你的类就失去了弹性,当然也不能全然否定继承Thread,该方法编写简单,可以直接操作线程,适用于单重继
承情况。至于选用那一种,具体情况具体分析。
eg.继承Thread
public class MyThread_1 extends Thread { public void run() { //some code } }
eg.实现Runnable接口
public class MyThread_2 implements Runnable { public void run() { //some code } }
当使用继承创建线程,这样启动线程:
new MyThread_1().start()
当使用实现接口创建线程,这样启动线程:
new Thread(new MyThread_2()).start()
注意,其实是创建一个线程实例,并以实现了Runnable接口的类为参数传入这个实例,当执行这个线程的时候,MyThread_2中run里面的代码将被执行。
下面是完成的例子:
public class MyThread implements Runnable { public void run() { System.out.println("My Name is "+Thread.currentThread().getName()); } public static void main(String[] args) { new Thread(new MyThread()).start(); } }
执行后将打印出:
My Name is Thread-0
你也可以创建多个线程,像下面这样
new Thread(new MyThread()).start(); new Thread(new MyThread()).start(); new Thread(new MyThread()).start();
那么会打印出:
My Name is Thread-0
My Name is Thread-1
My Name is Thread-2
看了上面的结果,你可能会认为线程的执行顺序是依次执行的,但是那只是一般情况,千万不要用以为是线程的执行机制;影响线程执行顺序的因素有几点:首先看看前面提到的优先级别
public class MyThread implements Runnable { public void run() { System.out.println("My Name is "+Thread.currentThread().getName()); } public static void main(String[] args) { Thread t1=new Thread(new MyThread()); Thread t2=new Thread(new MyThread()); Thread t3=new Thread(new MyThread()); t2.setPriority(Thread.MAX_PRIORITY);//赋予最高优先级 t1.start(); t2.start(); t3.start(); } }
再看看结果:
My Name is Thread-1
My Name is Thread-0
My Name is Thread-2
线程的优先级分为10级,分别用1到10的整数代表,默认情况是5。上面的t2.setPriority(Thread.MAX_PRIORITY)等价与t2.setPriority(10)
然后是线程程序本身的设计,比如使用sleep,yield,join,wait等方法(详情请看JDKDocument)
public class MyThread implements Runnable { public void run() { try { int sleepTime=(int)(Math.random()*100);//产生随机数字, Thread.currentThread().sleep(sleepTime);//让其休眠一定时间,时间又上面sleepTime决定 //public static void sleep(long millis)throw InterruptedException (API) System.out.println(Thread.currentThread().getName()+" 睡了 "+sleepTime); }catch(InterruptedException ie)//由于线程在休眠可能被中断,所以调用sleep方法的时候需要捕捉异常 { ie.printStackTrace(); } } public static void main(String[] args) { Thread t1=new Thread(new MyThread()); Thread t2=new Thread(new MyThread()); Thread t3=new Thread(new MyThread()); t1.start(); t2.start(); t3.start(); } }
执行后观察其输出:
Thread-0 睡了 11
Thread-2 睡了 48
Thread-1 睡了 69
上
面的执行结果是随机的,再执行很可能出现不同的结果。由于上面我在run中添加了休眠语句,当线程休眠的时候就会让出cpu,cpu将会选择执行处于
runnable状态中的其他线程,当然也可能出现这种情况,休眠的Thread立即进入了runnable状态,cpu再次执行它。
[线程组概念]
线程是可以被组织的,java中存在线程组的概念,每个线程都是一个线程组的成员,线程组把多个线程集成为一个对象,通过线程组可以同时对其中的多个线程进行操作,如启动一个线程组的所有线程等.Java
的线程组由java.lang包中的Thread——Group类实现.
ThreadGroup
类用来管理一组线程,包括:线程的数目,线程间的关系,线程正在执行的操作,以及线程将要启动或终止时间等.线程组还可以包含线程组.在Java的应用程
序中,最高层的线程组是名位main的线程组,在main中还可以加入线程或线程组,在mian的子线程组中也可以加入线程和线程组,形成线程组和线程之
间的树状继承关系。像上面创建的线程都是属于main这个线程组的。
借用上面的例子,main里面可以这样写:
public static void main(String[] args) { /*************************************** ThreadGroup(String name) ThreadGroup(ThreadGroup parent, String name) ***********************************/ ThreadGroup group1=new ThreadGroup("group1"); ThreadGroup group2=new ThreadGroup(group1,"group2"); Thread t1=new Thread(group2,new MyThread()); Thread t2=new Thread(group2,new MyThread()); Thread t3=new Thread(group2,new MyThread()); t1.start(); t2.start(); t3.start(); }
线程组的嵌套,t1,t2,t3被加入group2,group2加入group1。
另
外一个比较多就是关于线程同步方面的,试想这样一种情况,你有一笔存款在银行,你在一家银行为你的账户存款,而你的妻子在另一家银行从这个账户提款,现在
你有1000块在你的账户里面。你存入了1000,但是由于另一方也在对这笔存款进行操作,人家开始执行的时候只看到账户里面原来的1000元,当你的妻
子提款1000元后,你妻子所在的银行就认为你的账户里面没有钱了,而你所在的银行却认为你还有2000元。
看看下面的例子:
class BlankSaving //储蓄账户 { private static int money=10000; public void add(int i) { money=money+i; System.out.println("Husband 向银行存入了 [¥"+i+"]"); } public void get(int i) { money=money-i; System.out.println("Wife 向银行取走了 [¥"+i+"]"); if(money<0) System.out.println("余额不足!"); } public int showMoney() { return money; } } class Operater implements Runnable { String name; BlankSaving bs; public Operater(BlankSaving b,String s) { name=s; bs=b; } public static void oper(String name,BlankSaving bs) { if(name.equals("husband")) { try { for(int i=0;i<10;i++) { Thread.currentThread().sleep((int)(Math.random()*300)); bs.add(1000); } }catch(InterruptedException e){} }else { try { for(int i=0;i<10;i++) { Thread.currentThread().sleep((int)(Math.random()*300)); bs.get(1000); } }catch(InterruptedException e){} } } public void run() { oper(name,bs); } } public class BankTest { public static void main(String[] args)throws InterruptedException { BlankSaving bs=new BlankSaving(); Operater o1=new Operater(bs,"husband"); Operater o2=new Operater(bs,"wife"); Thread t1=new Thread(o1); Thread t2=new Thread(o2); t1.start(); t2.start(); Thread.currentThread().sleep(500); } }
下面是其中一次的执行结果:
---------first--------------
Husband 向银行存入了 [¥1000]
Wife 向银行取走了 [¥1000]
Wife 向银行取走了 [¥1000]
Husband 向银行存入了 [¥1000]
Wife 向银行取走了 [¥1000]
Husband 向银行存入了 [¥1000]
Wife 向银行取走了 [¥1000]
Husband 向银行存入了 [¥1000]
Wife 向银行取走了 [¥1000]
Husband 向银行存入了 [¥1000]
Husband 向银行存入了 [¥1000]
Wife 向银行取走了 [¥1000]
Husband 向银行存入了 [¥1000]
Husband 向银行存入了 [¥1000]
Wife 向银行取走了 [¥1000]
Wife 向银行取走了 [¥1000]
Husband 向银行存入了 [¥1000]
Wife 向银行取走了 [¥1000]
Wife 向银行取走了 [¥1000]
Husband 向银行存入了 [¥1000]
看
到了吗,这可不是正确的需求,在husband还没有结束操作的时候,wife就插了进来,这样很可能导致意外的结果。解决办法很简单,就是将对数据进行
操作方法声明为synchronized,当方法被该关键字声明后,也就意味着,如果这个数据被加锁,只有一个对象得到这个数据的锁的时候该对象才能对这
个数据进行操作。也就是当你存款的时候,这笔账户在其他地方是不能进行操作的,只有你存款完毕,银行管理人员将账户解锁,其他人才
能对这个账户进行操作。
修改public static void oper(String name,BlankSaving bs)为public static void oper(String name,BlankSaving bs),再看看结果:
Husband 向银行存入了 [¥1000]
Husband 向银行存入了 [¥1000]
Husband 向银行存入了 [¥1000]
Husband 向银行存入了 [¥1000]
Husband 向银行存入了 [¥1000]
Husband 向银行存入了 [¥1000]
Husband 向银行存入了 [¥1000]
Husband 向银行存入了 [¥1000]
Husband 向银行存入了 [¥1000]
Husband 向银行存入了 [¥1000]
Wife 向银行取走了 [¥1000]
Wife 向银行取走了 [¥1000]
Wife 向银行取走了 [¥1000]
Wife 向银行取走了 [¥1000]
Wife 向银行取走了 [¥1000]
Wife 向银行取走了 [¥1000]
Wife 向银行取走了 [¥1000]
Wife 向银行取走了 [¥1000]
Wife 向银行取走了 [¥1000]
Wife 向银行取走了 [¥1000]
当丈夫完成操作后,妻子才开始执行操作,这样的话,对共享对象的操作就不会有问题了。
[wait and notify]
你
可以利用这两个方法很好的控制线程的执行流程,当线程调用wait方法后,线程将被挂起,直到被另一线程唤醒(notify)或则是如果wait方法指定
有时间得话,在没有被唤醒的情况下,指定时间时间过后也将自动被唤醒。但是要注意一定,被唤醒并不是指马上执行,而是从组塞状态变为可运行状态,其是否运
行还要看cpu的调度。
事例代码:
class MyThread_1 extends Thread { Object lock; public MyThread_1(Object o) { lock=o; } public void run() { try { synchronized(lock) { System.out.println("Enter Thread_1 and wait"); lock.wait(); System.out.println("be notified"); } }catch(InterruptedException e){} } } class MyThread_2 extends Thread { Object lock; public MyThread_2(Object o) { lock=o; } public void run() { synchronized(lock) { System.out.println("Enter Thread_2 and notify"); lock.notify(); } } } public class MyThread { public static void main(String[] args) { int[] in=new int[0];//notice MyThread_1 t1=new MyThread_1(in); MyThread_2 t2=new MyThread_2(in); t1.start(); t2.start(); } }
执行结果如下:
Enter Thread_1 and wait
Enter Thread_2 and notify
Thread_1 be notified
可
能你注意到了在使用wait and
notify方法得时候我使用了synchronized块来包装这两个方法,这是由于调用这两个方法的时候线程必须获得锁,也就是上面代码中的
lock[],如果你不用synchronized包装这两个方法的得话,又或则锁不一是同一把,比如在MyThread_2中
synchronized(lock)改为synchronized(this),那么执行这个程序的时候将会抛出
java.lang.IllegalMonitorStateException执行期异常。另外wait and
notify方法是Object中的,并不在Thread这个类中。最后你可能注意到了这点:int[] in=new
int[0];为什么不是创建new Object而是一个0长度的数组,那是因为在java中创建一个0长度的数组来充当锁更加高效。
发表评论
-
JSTL中的<c:标签【Z】
2011-08-31 20:48 1146Taglib 伪指令 Java代码 ... -
java接口嵌套【Z】
2011-05-18 17:09 1342在Java语言中,接口可以嵌套在类或其它接口中。由于Jav ... -
JMF安装【Z】
2011-05-07 20:52 1094下载并安装JMF 在MyEclipse中选择 窗口/首选项/ ... -
javaMail【Z】
2011-04-18 16:12 1078commons-email-1.1.jar: 这是Apache ... -
java生成PDF文件【Z】
2011-01-14 15:03 1211如果应用程序需要动态 ... -
字符集编码【Z】
2010-12-29 18:54 893问题研究 --字符集 ... -
java常用第三方jar包【Z】
2010-12-26 22:39 3360commons-digester.jar Digeste ... -
不使用JNI获得系统信息【Z】
2010-12-22 13:28 589在Java中,可以获得总的物理内存、剩余的物理内存、已使用的物 ... -
java获得当前路径【Z】
2010-12-18 14:37 9421、利用System.getProperty()函数获取当前路 ... -
java容器类-1【Z】
2010-12-17 21:43 1013对象的集合 如果程序的对象数量有限 ... -
java UIManager的风格【Z】
2010-12-17 12:28 2671Java'中的几种Look and Feel 1、Met ... -
serialVersionUID的作用和意义【Z】
2010-12-15 10:38 1553在Java中,软件的兼容性是一个大问题,尤其在使用到对象串行性 ... -
HashMap遍历的两种方式【Z】
2010-12-07 18:42 968HashMap遍历的两种方式 ... -
23个经典JDK设计模式——系统JDK使用设计模式的例子【Z】
2010-12-05 21:33 1029酷壳 版主陈皓近日发表博文《JDK里的设计模式 》,文中他 ... -
String/StringBuffer/StringBuild【Z】
2010-11-12 14:10 1346[编辑] String,StringBuffer和String ... -
tomcat中web.xml详解【Z】
2010-10-18 14:47 9251 定义头和根元素 部 ... -
Java 理论与实践: 线程池与工作队列【Z】
2010-10-18 09:45 932线程池有助于实现最 ... -
单例模式完全剖析【Z】
2010-10-17 22:48 790Buildfile: build.xml ... -
在MyEclipse中直接查看class文件(在没有源码的情况下)【Z】
2010-10-17 22:04 1309想直接在myeclipse中查看class文件,就像查看普通的 ... -
HashMap实现及冲突【Z】
2010-10-12 21:41 962了解 HashMap 原理对于日后的缓存机制多少有 ...
相关推荐
Java线程有五种状态:新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)和终止(Terminated)。线程的生命周期可以通过start(), join(), interrupt(), sleep(), yield(), wait(), notify()等方法...
总结,`jps`和`jstack`是Java开发和运维中的有力工具,结合Shell脚本实现定时收集,可以有效监控和排查Java应用的问题。通过深入了解并熟练运用这些工具,开发者可以更好地理解和管理他们的Java应用程序。
- 在`run()`方法中,线程会按照指定的时间间隔(默认5秒),从`'a'`到`'z'`循环显示字母。 - 使用`Thread.sleep()`方法实现线程暂停。 3. **Letter.java**: - 作为共享资源,用于存储当前要显示的字母。 - ...
Java线程错误捕获工具CheckThread是一款专门针对Java多线程编程中可能出现的错误和异常进行检测和诊断的实用工具。在多线程环境中,由于并发执行和共享资源的特性,错误和异常的发生往往难以预测且难以定位。...
`7z解压.txt`文件可能是对解压过程的进一步说明或注意事项,可能包括错误处理、性能优化、多线程解压等信息。实际使用时,你可以根据具体需求进行调整,例如增加日志记录、异常处理、进度显示等功能。 总的来说,...
"HEIC-Convert-Java.7z"压缩包提供了一个Java解决方案来实现这一目的。 Java是一种广泛使用的编程语言,具有跨平台的特性,能够运行在多种操作系统上。这个HEIC转换Java项目旨在帮助开发者或用户在Java环境下将HEIC...
在Java编程中,多线程是并发处理任务的关键技术,它可以极大地提高程序的执行效率。在实际应用中,我们常常会遇到一种典型的多线程问题——生产者消费者模型。这个模型描述了两种类型的线程:生产者线程负责创建或...
Java多线程编程是开发高并发应用的关键技术之一,但同时也带来了复杂性和潜在的问题,比如死锁。死锁是指两个或多个并发线程在执行过程中,因争夺资源而造成的一种相互等待的现象,若无外力干涉,它们都将无法推进...
启动两个线程,一个线程打印1~52,另一个线程打印A~Z
1. 写两个线程,一个线程打印1-52,另一个线程打印字母A-Z。打印顺序为12A34B56C……5152Z。 学习线程之间的通信协调关系。 2. 使用 runnable 接口实现按两个不同的时间间隔( 1 秒和 3 秒)在屏幕上显示当前时间。...
学习Java的基础知识,包括语法、面向对象编程概念、异常处理、集合框架、IO流、多线程和网络编程等,是成为Java开发者的必经之路。同时,熟悉Java EE(企业版)和Spring框架等技术将有助于开发复杂的企业级应用。
在IT行业中,多线程是提高程序性能和并发能力的重要技术。在本课程"day24-多线程-设计模式"中,我们将深入探讨如何利用多线程结合设计模式来优化程序执行效率。首先,我们要理解的是,多线程是指在一个进程中同时...
下面我们将深入探讨Java编程语言的关键知识点,包括基础语法、面向对象特性、异常处理、集合框架、多线程、网络编程以及JVM优化等方面。 1. **基础语法**:Java是一种静态类型的强类型语言,它的语法结构清晰,易于...
【JAVA学习总结】 Java是一种广泛使用的面向对象的编程语言,由Sun Microsystems(现已被Oracle公司收购)于1995年推出。它的设计目标是具有跨平台性、可移植性、安全性和高效性,使得“一次编写,到处运行”成为...
z Java多线程编程 z Java I/O 流(character Stream, byte Stream, serialization) z Java Collections Framework z Java GUI(awt, swing, layoutManger, eventhandling….) z 共32学时,上机两次(每次4小时) 3 第一...
《Mandelbrot集的Java多线程实现方法》 Mandelbrot集是一个在复数平面上由特定迭代过程定义的点集,其特点在于通过反复应用一个数学函数,某些点的序列会保持在有限区域内,而其他点则会发散到无穷大。这一集合在...
这份“Java基础核心总结”涵盖了Java编程的基础知识,是初学者和有经验的开发者巩固技能的重要资源。以下是对该压缩包内容的详细解读: 1. **Java语法基础**:Java的语法基于C++,但更加简洁和安全。它包括变量声明...
以下是对"java面试题总结.7z"中可能包含的一些核心知识点的详细解析: 1. **基础语法**:这部分内容可能包括变量、数据类型、运算符、流程控制(如if-else、switch-case、for、while、do-while)、方法定义与调用、...
这个"Java Jdk 环境软件安装包.7z"包含了Java JDK的8版本,是开发者进行Java应用程序开发、编译和运行的必备组件。Java 8是一个重要的里程碑,因为它引入了许多新特性,比如lambda表达式、方法引用、流API以及默认...