线程概念
线程就是独立的执行路径。
在程序运行时,即使自己没有创建线程,后台也会有多个线程,如主线程,gc线程。
在一个进程中,如果开辟了多个线程,线程的运行由调度器安排调度,调度器是与操作系统紧密相连的,先后顺序不能人为干预。
线程会带来额外开销,如cpu调度时间,并发控制开销。
每个线程在自己的工作内存交互,内存控制不会造成数据不一致。主内存是所有线程可以共享的,而工作内存是每个线程都独有的,主内存保存了所有的变量,线程会把需要用到的变量从主内存中拷贝副本到工作内存中使用,不同线程无法访问别的工作内存中的的变量,线程之间变量值的传递也是通过主内存来完成。
创建线程的方式
1、继承Thread类,重写Run方法(Thread类实现了Runnable接口,因此也可以第二种)
2、实现Runnable接口,实现Run方法
3、实现Callable接口,实现Call方法
4、线程池方式
实现Runnable接口和实现Callable接口的区别?
1、实现Runnable接口无返回值,实现Callable接口有返回值,返回值通过FutureTask对象的get()获取返回值。
2、实现Runnable接口需重写run方法,且必须try catch捕获异常,实现Callable接口需重写call方法,异常可以捕 获也可以向上抛。
3、Runnable接口的实例可以作为Thread线程实例的target(target是传入的异步任务对象new Thread(target)),而Callable接口实例不可以,创建Callable类型任务的线程还需要一个FutureTask类。
实现Callable接口具体步骤?
1、创建实现类实现Callable接口,重写call方法。
2、创建FutureTask类实例,传入这个Callable实例属性。
3、创建Thread实例,传入FutureTask这个实例(异步任务对象)
4、Thread类.start()开启线程。可以通过FuruteTask类.get获取异步执行结果。
为什么不用thread.run()启动线程而是start()方法?
run()是一个普通方法,调用run方法,程序只有主线程这一条执行路径,此时主线程执行run()方法,执行结束再回来继续执行主线程的内容。
start()方法是开启了一条子线程,由子线程执行实现的run方法,和主线程并行交替执行,程序中有多条执行路径。
小结
继承Thread类的方式:子类继承Thread具备了多线程能力,通过子类对象.start()启动线程,不推荐使用:OOP单继承局限性。
实现Runnable接口的方式:实现了Runnable接口具备了多线程能力,通过创建Thread对象并传入目标对象+Thread对象.start()方法启动线程,推荐使用:避免了单继承局限性,灵活方便,同一个对象可被多个线程同时使用。
实现Callable接口的方式:优点:可以定义返回值,可以抛出异常
Future接口和FutureTask类
Future接口是一个对异步任务进行交互,操作的接口。
Future接口的功能有?
1、能取消异步执行中的任务。
2、判断异步任务是否执行完成完成。
3、获取异步任务完成时的执行结果。V get()
FutureTask类和Future接口的关系?
FutureTask类实现了RunnableFuture接口,而RunnableFuture接口又继承了Runnbale接口和Future接口,因此它可以作为Thred类的target,也可以获取异步执行结果。
FutureTask类.get()
get()的调用时阻塞性的,如果异步任务还没有完成的话,调用get()获取异步结果的线程会一直阻塞,直到异步任务执行完成,将结果返回。
FutureTask类认识
FutureTask类实现了RunnableFuture接口,而RunnableFuture接口又继承了Runnbale接口和Future接口,因此它可以作为Thred类的target,也可以获取异步执行结果。
FutureTask类有一个Callable类型的成员变量,用来保存并发执行的Callable类型的任务,并且在构造FutureTask类实例的时候需要传入一个Callable类型的实例对象进行初始化。
因为FutureTask实现了Runnable接口,在FutureTask类重写的run方法中会去调用Callable成员的call()方法 。而FutureTask内部还有一个重要的Object类型 的成员变量,用来保存call()异步执行的结果。
线程池
线程和线程池区别
1、线程的话次次使用次次新建,性能差,而且线程缺乏统一的管理,无限制的创建线程对象,相互之间不停地竞争资源,容易堵塞甚至会造成死机。
2、线程池可以重复使用已经存在的线程,减少对象的创建带来的开销,比如提升响应速度,通过复用降低了资源消耗,而且可以有效控制最大并发线程数,提高系统资源的使用效率,避免过多资源竞争造成堵塞。
线程池分类
一、newCachedThreadPool() 可缓存的线程池
1、线程池的容量不固定,可达最大值(Integer.MAX_VALUE)。
2、如果没有可用线程,会自动创建一个新线程。
3、如果线程默认超过1分钟未使用,会被线程池自动回收进行复用。
4、适合处理执行时间较短的任务。如果都是执行很长时间的线程对象,线程数量可以扩展,如果创建很多个,会带来严重的性能问题。
二、newFixedThreadPool() 固定线程数目的线程池
1、线程池中的线程数量一定,可以很好的控制线程的并发量。
2、超出数量的线程在对列中等待。
3、线程在显式地关闭之前,会一直存在于线程池中。
4、如果线程发生错误导致终止时,线程池会创建一个新线程代替它继续执行任务。
三、newSingleThreadExecutor() 单个线程的线程池
1、线程池中最多执行一个线程,超出的在队列中排队。
2、如果线程因异常而结束,会创建一个新线程代替它继续执行任务,从而保证任务按提交的顺序执行。
3、适用于需要保证顺序执行的场景,并且只有一个线程执行。
四、newScheduleThreadPool() 能定时或者周期性执行任务的线程
1、可定时或者周期性的执行线程任务。
2、适用于需要多个后台线程执行周期性任务的场景。
ExecutorService es = Executors.newFixedThreadPool(3);
Future<?> submit = es.submit(new Runnable() {
@Override
public void run() {
}
});
Object outcome = submit.get();
线程池创建方式
1、通过Executors工具类的方式,本质调用的还是ThreadPoolExecutor构造器。
2、new ThreadPoolExecutor的方式。
Executors工具类提供了工厂方法来创建线程池,通过调用不同方法创建不同类型的线程池,返回ExecutorService,线程池都实现了ExecutorService接口。
Executors是一个线程池工具类,提供了工厂方法来创建并返回不同类型的线程池。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class MyPL {
public static void main(String[] args) {
//创建服务,创建线程池,大小为5
ExecutorService service = Executors.newFixedThreadPool(5);
service.execute(new MyThread("我是线程1"));
service.execute(new MyThread("我是线程2"));
service.execute(new MyThread("我是线程3"));
service.execute(new MyThread("我是线程4"));
service.execute(new MyThread("我是线程5"));
service.execute(new MyThread("我是线程6"));
service.execute(new MyThread("我是线程7"));
//关闭连接
service.shutdown();
//打印:
//我是线程2
//我是线程1
//我是线程3
//我是线程4
//我是线程5
}
}
class MyThread implements Runnable{
private String name;
public MyThread(String name) {
this.name = name;
}
@Override
public void run() {
System.out.println(name);
try {
//休眠3s:模拟线程都还在使用中,没有归还给线程池,sleep方法不会释放锁,
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
线程池七大参数
线程池总的参数一共有七个:
1、核心线程数
2、最大线程数
3、最大空闲单位
4、最大空闲时间单位
5、阻塞队列
6、线程工厂
7、拒绝策略
ExecutorService和Executor
ExecutorService是线程池的接口,继承Executor接口。
Excutor接口中定义了一个execute()。
ExecutorService接口是对Executor接口的扩展,提供了管理终止的方法,以及可以返回Future对象,用于跟踪异步任务的进度。
ExecutorService中shutdown()和shutdownNow()?
相同点:
1、shutdown()和shutdownNow()都不允许新线程提交到线程池
2、都是异步的,不等待正在执行的任务终止(不等待处理结束)
不同点:
1、shutdown()允许正在运行的和线程池队列中的线程任务执行完毕再退出。
2、shutdownNow()尝试停止所有正在执行的任务并清除等待队列中的任务,返回等待执行的任务列表。
线程池的execute()和submit()区别?
1、execute()只能接收Runnbale类型任务,submit()既可以接收Runnable任务,也可以接收Callable任务。
2、execute()没有返回值,submit()会返回一个Future对象,通过Future对象跟踪任务进度,它的get()获取返回结 果,如果传的是Runnable接口对象,那么返回的是null。
线程的六种状态
Thread.State(JDK文档)
○ NEW(新建)尚未启动的线程处于此状态。
○ RUNNABLE(运行)可运行线程的线程状态。处于可运行状态的线程正在Java虚拟机中执行,但它可能正在等待来自操作系统的其他资源,例如处理器。
○ BLOCKED(堵塞)等待监视器锁而阻塞的线程的线程状态。处于阻塞状态的线程正在等待监视锁进入同步块/方法,或者在调用Object.wait后重新进入同步块/方法。
○ WAITING(无限等待)等待线程的线程状态。
○ 处于等待状态的线程正在等待另一个线程执行特定的操作。例如,在对象上调用object. wait()的线程正在等待另一个线程在该对象上调用object. notify()或object. notifyall()。调用thread .join()的线程正在等待指定的线程终止。
○ TIMED_WAITING(计时等待)正在等待另一个线程执行最多指定等待时间的操作的线程处于此状态。 线程调用sleep(),就处于该状态。
○ TERMINATED(消亡)已退出的线程处于此状态。
阻塞状态和等待状态的区别
1、阻塞状态是线程在等待获得同步对象锁,因为此时这个锁被其他线程获得,所以线程要等待获取锁,然后获得锁后才能进入同步块或同步方法。
2、等待状态是线程本来已经获得了同步对象锁并进入同步块或同步方法。但是因调用了某些方法,线程主动释放了它的锁,并等待且只能等待其他线程调用同步对象的notify()或notifyAll()方法来唤醒线程,被唤醒的线程将再以通常的方式与其他线程竞争该锁。等待可以指定一个时间,超时的话线程会自动唤醒自己。
共同点:两者都暂停了线程的执行,线程本身不会占用CPU时间。而如果是调用了sleep()方法,线程会受cpu调度。sleep()是Thread类的一个本地方法,阻塞时间内不会释放线程的持有锁。
不同点:
进入阻塞状态是被动的,进入等待状态是主动的;
进入阻塞状态是在同步代码外,进入等待状态是在同步代码内。
阻塞的方式
sleep()和join()
sleep方法是Thread类 的静态方法。
join方法是Thread类的实例方法。
调用sleep(time)实现阻塞称之为“线程睡眠”,会有一个时间限制,到达指定时间后重新进入就绪状态等待cpu调度。sleep等待期间不会释放锁。
调用join()是被强占的线程等待抢占的线程运行结束或终止后,重新进入就绪状态。
同步阻塞
多个线程想要获取同一个同步锁,先到先得,未抢到锁的线程进入锁对象的等待池中等待。
wait()阻塞
wait是Object对象的方法。
wait方法实现阻塞称之为“线程等待”,
等待有两种情况:要么传入时间,有限制的等待,时间到就唤醒自己;要么没有时间限制,无限期的等待,只能通过获取该锁对象的线程调用锁对象的notify()或notifyAll()方法来唤醒,唤醒后转为就绪状态。
sleep方法和wait方法区别
sleep()和wait()区别
sleep方法不释放锁;
wait释放锁;
sleep方法是Thread的静态方法,任何地方都可使用。
wait方法属于Object类的方法,只能在同步代码中使用,由同步对象调用。
而notify或notifyAll方法也是Object类的方法,是唤醒调用了wait方法后进入了这个锁对象的等待池中等待获取锁的线程,因此也只能在同步代码中使用,试想如果不在同步代码中使用,没有持有锁,有何来的释放锁和锁对象的调用nofify()。
线程中断和终止
1、线程中断是指线程在运行过程中被其他线程给打断了,线程中断就是给目标线程发送一个中断信号,如果目标线程接收到了中断信号,是否中断或继续运行还要看目标线程本身的实现逻辑。
2、线程终止是当start()方法启动线程,当线程的任务执行完毕后会终止。但是如果线程因为阻塞或长之间执行而不能结束的话,需要通过一定方式让线程来自动终止。
线程中断的三个重要方法
1、interrupt() :Thread类的实例方法,用于中断线程,给线程发送中断信号,设置线程中断标志位为true
2、isInterrupted: Thread类的实例方法,判断线程是否被中断,不会清除中断标志。
3、interrupted() :Thread类的静态方法,判断线程是否被中断,会清除中断标志。
线程终止的方法
1、线程执行结束,自动终止。
2、使用退出标志位。比如在主线程里设置一个线程更共享的Boolean类型的标志位。
public volatile boolean exit = false;
通过while循环判断这个标志位为true或false决定是否退出。
3、stop方法强行终止,不推荐使用。
4、使用Interrupt()方法终止线程
给线程发送中断信号,分两种情况:
(1)线程处于阻塞状态,会立马退出阻塞,抛出InterruptedException异常,线程可以通过捕获异常来让线程退出。
(2)线程处于运行状态中,那么interrupt()只是中断线程,给线程发送中断信号,设置线程中断标志位为true,线程不受任何影响,继续执行。**如何优雅的终止线程:**interrupt()中断线程,再用while循环+isInterrupted()判断线程是否中断,如果标志位为true可以做退出操作。注意如果线程有调用sleep方法的话会清除中断标志,还需要再interrupt()一下。
ps:如果线程的interrupt()先被调用,然后线程调用阻塞方法进入阻塞状态,依旧抛出InterruptedException异常。如果线程捕获InterruptedException异常后,继续调用阻塞方法,将不再触发InterruptedException异常。
其他线程方法
线程休眠:sleep()
sleep(时间):指当前线程阻塞的毫秒数;
sleep存在异常InterruptedException;
sleep可以模拟网络延时,倒计时等;
每个对象都有一个锁,sleep不会释放锁。
模拟倒计时:
public class TestSleep {
public static void main(String[] args) {
//系统当前时间
Date startTime = new Date(System.currentTimeMillis());
while (true){
try {
//休眠1s
Thread.sleep(1000);
System.out.println(new SimpleDateFormat("HH:mm:ss").format(startTime));
//更新当前时间
startTime = new Date(System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
线程礼让:yield()
就是让当前正在执行的线程暂停,但不阻塞;
将当前线程从运行状态转为就绪状态;
让cpu重新调度,礼让不一定成功,因为cpu可能还会选择先调度这个线程。
public class TestYield {
public static void main(String[] args) {
MyYield myYield = new MyYield();
Thread t1 = new Thread(myYield,"t1");
Thread t2 = new Thread(myYield,"t2");
Thread t3 = new Thread(myYield,"t3");
t1.start();
t2.start();
t3.start();
}
}
class MyYield implements Runnable{
@Override
public void run() {
System.out.println("当前线程是:" + Thread.currentThread().getName());
if(Thread.currentThread().getName().equals("t1")){
System.out.println("让t1线程礼让");
Thread.yield();
System.out.println("礼让over(礼让可能失败)");
}
}
}
线程强制执行:join()
join可以想象成插队,此线程(调用join方法的线程)执行完成后,再执行其他线程,其他线程阻塞。
public class TestJoin {
public static void main(String[] args) throws InterruptedException {
MyJoin myJoin = new MyJoin();
Thread t1 = new Thread(myJoin);
t1.start();
//main线程
for (int i = 1; i <= 10; i++) {
System.out.println("main" + i);
if(i == 5){
//t1线程插队,main线程阻塞,t1线程执行完毕才能执行main线程
t1.join();
}
}
}
}
class MyJoin implements Runnable{
@Override
public void run() {
for(int i = 1; i <= 10; i++){
System.out.println("join" + i);
}
}
}
观测线程状态:
public class TestState {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(()->{
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("//");
});
Thread.State state = thread.getState();
System.out.println("线程状态:" + state);// NEW
//启动线程
thread.start();
state = thread.getState();
System.out.println("线程状态:" + state);// RUNNABLE
//只要线程未终止,就一直输出状态
while (state != Thread.State.TERMINATED){
Thread.sleep(100);
//更新线程状态
state = thread.getState();
//输出状态
System.out.println("线程状态:" + state);
}
}
}
线程同步
同步:线程同步其实是一种等待机制,同时访问同一个同步对象的多个线程进入这个同步对象的等待池形成队列,等待前面线程使用完毕。
如何保证多线程安全:在访问时加入锁机制synchronized,当一个线程获得对象的排他锁,独占资源,其他线程必须等待,使用后释放。
存在问题:性能下降:
一个线程持有锁可能导致其他需要此锁的线程都被挂起;
加锁,释放锁,会导致比较多的上下文切换和调度延时;
一个优先级高的等待一个优先级低的线程释放锁,会导致优先级倒置;
线程不安全案例:
public class UnSafeThread {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
for(int i = 0; i < 100; i++){
new Thread(()->{
list.add(Thread.currentThread().getName());
}).start();
}
try {
//sleep()可以放大问题的发生性
//让main线程休眠3s,好让for循环里面是子线程都有时间跑完,再打印size
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//结果可能小于100,因为List集合线程不安全--》执行add方法不安全--》
//当i=10的时候开启一个线程去执行add,此时main线程循环i等于11,再开启一个线程执行add
//可能存在多个线程再对同一位置进行添加操作,发生覆盖,故list长度小于100
System.out.println(list.size());
}
}
synchronsized关键字:意为同步
两种用法:同步方法,同步代码块。
同步方法
public synchronized void add(){}
同步方法控制对当前对象(类的实例)的访问,每个对象都有一把锁,每个同步方法都必须获得调用该方法的对象的锁才能执行,否则阻塞;
缺陷:将一个大的方法加锁影响效率。
同步块
synchronized (obj){}
obj:同步监视器,是一个java对象
注意:
- 同步监视器必须是引用数据类型,不能是基本数据类型。
- 可以改变同步监视器堆中属性的值,但是不能改变指向堆中的地址。
- 尽量不要用String和包装类型作为同步监视器,容易造成指向堆中地址的变化。
- 一般使用共享资源作为同步监视器,也可以专门创建一个没有任何语义化含义的同步监视器。
同步方法中无需同步监视器,因为同步方法的同步监视器就是this,即对象本身,或者说class(反射);
同步监视器执行过程:
线程1访问,锁定同步监视器,执行代码;
线程2访问,同步监视器已被锁定,无法执行代码块;
线程1访问完毕,解锁同步监视器;
线程2访问,同步监视器未被访问,线程2锁定同步监视器,执行代码。
死锁
同一个同步代码同时拥有两个或两个以上对象的锁时,可能发生死锁问题。
死锁的四个必要条件
互斥条件:一个资源每次只能被一个进程使用;
请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放;
不剥夺条件:进程未释放资源之前,不能强行剥夺;
循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
lock锁
jdk1.5开始,更强大的线程同步机制,通过对Lock对象加锁可以显式地实现同步。
java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具,线程访问资源之前应先获得Lock锁对象。
ReentrantLock(可重入锁)类实现了Lock接口,ReentranLock拥有与synchronized相同的并发性和内存语义,而且可以显式加锁,释放锁。
package com.itheima.controller.thread;
import java.util.concurrent.locks.ReentrantLock;
public class TestLock {
public static void main(String[] args) {
BuyTicketThread buy = new BuyTicketThread();
new Thread(buy,"布布").start();
new Thread(buy,"一二").start();
}
}
class BuyTicketThread implements Runnable{
int ticketNums = 100;
//定义Lock锁
ReentrantLock rl = new ReentrantLock();
@Override
public void run() {
while (true){
//加锁
rl.lock();
if(ticketNums <= 0){
return;
}
System.out.println(Thread.currentThread().getName() + "买到第" + ticketNums + "张票");
ticketNums--;
//解锁
rl.unlock();
}
}
}
lock锁和synchronized区别
ps:也可以描述ReentrantLock可重入锁和synchronized的区别
1、synchronized是关键字,lock是接口。
2、synchronized是隐式加锁,lock是显示加锁。
3、synchronized可以用在方法上,代码块上,而lock只能作用在代码块上。
4、lock可以用isLocked()方法判断有没有获取到锁,而synchronized不能。
5、synchronized可以发生异常时自动释放占有的锁,从而避免死锁,而lock必须手动unlock()释放锁,为了避免死锁,可以try catch 然后把unlock()放进finally中。
6、synchronized使用object对象本身的wait,notify,notifyAll调度机制,而lock可以用Condition进行线程之间的调度。
7、竞争激烈时,lock锁性能远远优于synchronized。lock可以提高多线程下操作的效率,即可以通过readlock,writelock实现读写分离。
通信问题
这是一个线程同步问题,生产者和消费者共享同一个资源,并且生产者和消费者之间互相依赖,互为条件。
对于生产者,没生产产品前,要通知消费者等待;生产了产品后,通知消费者消费。
对于消费者,消费后通知生产者生产新的产品以供消费。
关于生产者和消费者,仅有synchronized不够,它可阻止并发更新同一个资源,实现同步,但不能实现不同线程间的消息传递(通信)。
Object类提供了几个方法解决线程通信问题:
wait() 线程一直等到,直到其他线程通知,与sleep不同,会释放锁;
wait(long timeout) 指定了等待的毫秒数;
notify() 唤醒一个处于等待状态的线程;
notifyAll() 唤醒同一个对象上所有调用wait()方法的线程,优先级高的优先调度。
以上方法只能在同步方法或同步块中使用,否则报错IllegalMonitorStateException
管程法:
使用缓冲区:消费者不能直接使用生产者是数据,生产者将生产出来是数据放入缓冲区,消费者从缓冲区拿数据。
信号灯法:使用标志位。
public class TestPS {
public static void main(String[] args) {
SynContainer synContainer = new SynContainer();
new Producer(synContainer).start();
new Consumer(synContainer).start();
}
}
//生产者
class Producer extends Thread{
SynContainer container;
public Producer(SynContainer container){
this.container = container;
}
//生产
@Override
public void run() {
for(int i = 0; i < 100; i++){
container.push(new Product(i));
System.out.println("生产了" + (i + 1) + "个产品");
System.out.println("count共有:" + container.count);
}
}
}
//消费者
class Consumer extends Thread{
SynContainer container;
public Consumer(SynContainer container){
this.container = container;
}
//消费
@Override
public void run() {
for(int i = 0; i < 100; i++){
Product product = container.pop();
System.out.println("消费了第" + (product.id + 1) + "个产品");
System.out.println("消费后还剩:" + container.count + "个产品");
}
}
}
//产品
class Product{
int id;
public Product(int id) {
this.id = id;
}
}
//缓冲区
class SynContainer{
//缓冲区大小
Product[] products = new Product[10];
//计数器
int count = 0;
//生产者放入产品
public synchronized void push(Product product){
//如果缓冲区满了,等待消费者消费
if(count == products.length){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//缓冲区没满,继续丢入产品
products[count] = product;
count++;
//通知消费者消费
this.notify();
}
//消费者消费产品
public synchronized Product pop(){
//如果没有产品,等待生产者生产
if(count == 0){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//进行消费
count--;
Product product = products[count];
//消费完毕,通知生产者生产
this.notify();
return product;
}
}
lambda表达式
只能简化函数式接口的匿名内部类的写法形式。
函数式接口:首先是一个接口,其次只有一个抽象方法,则为函数式接口。
对于函数式接口:可以用lamda表达式来创建该接口对象。
普通方法创建函数值接口的实例对象:
public class MyLambda {
public static void main(String[] args) {
ILike iLike = new Like();
iLike.lambda();
}
}
//定义函数式接口
interface INLike{
void lambda();
}
//定义函数值接口实现类
class Like implements INLike{
@Override
public void lambda() {
System.out.println("this is lambda");
}
}
使用匿名内部类简化函数值接口对象的创建:
public class MyLambda {
public static void main(String[] args) {
INLike like = new INLike() {
@Override
public void lambda() {
System.out.println("this is lambda");
}
};
like.lambda();
}
}
//定义函数式接口
interface INLike{
void lambda();
}
使用lambda简化匿名内部类:
public class MyLambda {
public static void main(String[] args) {
INLike like = () ->{
System.out.println("this is lambda");
};
like.lambda();
}
}
//定义函数式接口
interface INLike{
void lambda();
}
lambda进一步简化:
public class MyLambda {
public static void main(String[] args) {
INLike like = () ->System.out.println("this is lambda");
like.lambda();
}
}
//定义函数式接口
interface INLike{
void lambda();
}
抽象方法带参数:
public class MyLambda {
public static void main(String[] args) {
INLike like = (aa) ->System.out.println("this is lambda:" + aa);
//只有一个参数,括号可省略: INLike like = aa ->System.out.println("this is lambda:" + aa);
like.lambda(10);
}
}
//定义函数式接口
interface INLike{
void lambda(int a);
}
小结:
进一步简化:
参数类型可省略;
如果只有一个参数,参数外括号()可省略;
如果lambda表达式方法体一有一行代码(return语句也算一行),可省略这行代码外的{}大括号,同时省略{}后面的分号,如果是return语句,同时必须省略return这个词。
public class MyLambda {
public static void main(String[] args) {
/*INLike like = ()-> {
return 10;
};*/
INLike like = ()-> 10;
int a = like.lambda();
System.out.println(a);// 10
}
}
//定义函数式接口
interface INLike{
int lambda();
}