文章目录
一、多线程技术概述
1、线程与进程
进程:一个内存中运行的应用程序,每个进程都有一个独立的内存空间。
线程:进程中的一个执行路径,共享一个内存空间,线程之间可以自由切换,并发执行。一个进程最少有一个线程。
两者的关系:
线程实际上是在进程基础之上进一步划分,一个进程启动之后,里面的若干执行路径又可以划分成若干个线程。
2、线程调度
①时分调度:
所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间。
②抢占式调度:
优先让优先级高的线程使用CPU,若线程的优先级相同,那么会随机选择一个,Java使用的为抢占式调度。
CPU使用抢占式调度模式在多个线程间进行着高速的切换。对于CPU的一个核心而言,某一时刻只能执行一个线程,而CPU在多个线程间切换的速度相对飞快,宏观上看似是在同一时刻运行。其实,多线程程序并不能提高程序的运行速度,但能够提高程序运行效率(体现在某一时间段内做多件事情),让CPU的使用率更高。
3、同步与异步
同步:排队执行,效率低,安全(如同一群人排队吃饭,有秩序)。
异步:同时执行,效率高,不安全(如同一群人抢一碗饭吃,有风险)。
4、并发与并行
并发:指两个或多个事件在【同一时间段内】发生。(时间段:时间轴中的一段,eg:1s内,1天中… …)
并行:指两个或多个事件在【同一时刻】发生。(时刻:时间轴中的一点,eg:7:05时,9:30时… …)
二、继承Thread
线程的第一种实现方式:
作用:用于实现线程的类。
描述:继承Thread类就是线程的一个类,run()方法就是线程要执行的任务方法。
示例代码:
public class Demo1{
public static void main(String[] args){
//1、创建线程
Thread t = new MyThread();
//2、启动线程
t.start();
for(int i = 0;i < 10;i++){
System.out.println("Hello ->" + i); //主线程打印输出
}
}
static class MyThread extends Thread{
@Override
public void run() {
/**
* 这里的代码就是一条新的执行路径。
* 这个执行路径的触发不是调用run()方法,而是
* 通过Thread对象的start()来启动任务。
*/
for(int i = 0;i < 10;i++){
System.out.println("World ->" + i); //子线程打印输出
}
}
}
}
时序图分析:
内存分析:
每个线程都拥有自己的栈空间,共用一份堆内存。(一栈一线程)
三、实现Runnable(常用)
线程的第二种实现方式:
作用:用于多线程进行执行的任务。
描述:实现Runnable接口就是线程的一个类,重写run()方法实现线程要执行的任务。
示例代码:
public class Demo1{
public static void main(String[] args){
//1、创建2个任务对象
Runnable r1 = new MyRunnable();
Runnable r2 = new MyRunnable();
//2、创建2个线程,并各自分配一个任务
Thread t1 = new Thread(r1,"子线程1");
Thread t2 = new Thread(r2,"子线程2");
//3、执行这两个线程
t1.start();
t2.start();
for(int i = 0;i < 10;i++){
//主线程打印输出
System.out.println(Thread.currentThread().getName() + ":Hello ->" + i);
}
}
static class MyRunnable implements Runnable{
@Override
public void run() {
//线程的任务
for(int i = 0;i < 10;i++){
//子线程打印输出
System.out.println(Thread.currentThread().getName() + ":World ->" + i);
}
}
}
}
总结,实现Runnable与继承Thread相比有如下优势:
1、通过创建任务,然后给线程分配的方式来实现多线程,更适合多个线程同时执行相同任务的情况。
2、可以避免单继承所带来的局限性。
3、任务与线程本身是分离的,提高了程序的健壮性。
4、后续学习的线程池技术,接收Runnable类型的任务,不接收Thread类型的线程。
5、Thread之匿名内部类的使用方式:
public static void main(String[] args) { //使用匿名内部类方式开启子线程 new Thread() { @Override public void run(){ for(int i = 0;i < 5;i++) { //子线程打印输出 System.out.println("子线程:Hello."); } } }.start(); for(int i = 0;i < 5;i++) { //主线程打印输出 System.out.println("主线程:World."); } }
四、Thread类
1、字段(线程优先级)
变量和类型 | 字段 | 描述(值) |
---|---|---|
static int | MAX_ PRIORITY | 最大优先级(10) |
static int | MIN_PRIORITY | 最低优先级(1) |
static int | NORM_PRIORITY | 默认优先级(5) |
2、常用构造方法
构造器 | 描述 |
---|---|
Thread() | 分配新的Thread对象(无任务) |
Thread(Runnable target) | 分配新的Thread对象,并给线程分配一个任务 |
Thread(Runnable target,String name) | 分配新的Thread对象,给线程分配任务同时为线程取名。 |
Thread(String name) | 分配新的Thread对象(无任务,单纯起名称) |
3、常用方法
变量和类型 | 方法 | 描述 |
---|---|---|
long | getId() | 获取线程的标识符 |
String | getName() | 获取此线程的名称 |
void | setName(String name) | 设置此线程的名称 |
int | getPriority() | 获取线程的优先级 |
void | setPriority(int newPriority) | 更改线程的优先级[见以上3字段] |
static void | sleep(long millis) | 指定毫秒级的线程休眠(暂时停止执行) |
static void | sleep(long millis,int nanos) | 指定毫秒与纳秒级的线程休眠(同理) |
void | start() | 执行此线程,JVM调用此线程的run()方法 |
void | interrupt() | 中断此线程。 |
void | setDaemon(boolean on) | 将此线程标记为守护线程(true)或用户线程(false) |
4、设置和获取线程名称
String name = Thread.currentThread().getName(); //获取线程名称
Thread.currentThread().setName(String name); //设置线程名称
//使用示例: public static void main(String[] args){ print(Thread.currentThread().getName());//打印主线程名称:"main" Thread.currentThread().setName("A"); //设置主线程名称为"A" print(Thread.currentThread().getName());//打印主线程名称:"A" } public static <T> void print(T t){ System.out.println(t); }
5、线程休眠sleep
Thread.sleep(long millis); //线程休眠millis毫秒
Thread.sleep(long millis,int nanos); //线程休眠millis毫秒 + nanos纳秒
//使用示例: public static void main(String[] args){ new Thread(new MyRunnable()).start(); for(int i = 0;i < 5;i++){ try{ Thread.sleep(1000);//此线程休眠1秒 }catch(InterruptedException e) { e.printStackTrace(); } System.out.println("主线程:" + i); } } static class MyRunnable implements Runnable{ @Override public void run() { for(int i = 0;i < 5;i++) { try{ Thread.sleep(1000,5000);//此线程休眠1秒5000纳秒 }catch(InterruptedException e) { e.printStackTrace(); } System.out.println("子线程" + i); } } }
6、线程阻塞
线程阻塞也叫耗时操作,可理解为所有较为耗时的操作。
例如:文件读写,用户输入(未输入前停在那里,仅输入回车后继续执行)
7、线程的中断
明确:
一个线程是一个独立的执行路径,它是否应该结束,应该由其自身决定。
stop()【已过时】可让线程停止,若使用此方法会强行关闭线程,可使得资源得不到释放而长期占用,故禁止使用此方法。
解决方案:
①给线程添加中断标记(线程对象名.interrupt();)。
②凭借以下方式停止线程:
try{
Thread.sleep(ms)
}catch(InterruptedException e){
//若有要释放的资源,可在return;语句前释放。
return;
}//示例代码: public static void main(String[] args) throws InterruptedException { Thread t = new Thread(new MyRunnable()); t.start(); for(int i = 0;i < 5;i++) { System.out.println("子线程" + i); Thread.sleep(1000);//此线程休眠1秒 } t.interrupt(); //①给t线程添加中断标记 } static class MyRunnable implements Runnable{ @Override public void run() { for(int i = 0;i < 5;i++) { System.out.println("子线程" + i); //②借助try-catch捕捉此线程中断标记 try{ Thread.sleep(1000);//此线程休眠1秒 }catch(InterruptedException e) { //此行可添加需要释放资源的执行语句 return; //停止当前线程 } } } }
8、守护线程
线程分为:
①用户线程:当一个进程不包含任何存活的用户线程时,进程结束。
②守护线程:是守护用户线程的,当最后一个线程结束时,所有守护线程自动死亡。
使用setDaemon(boolean on)方法设置,on = true:守护线程,on = false:用户线程(不设置默认为用户线程)。
public class Demo{ public static void main(String[] args) throws InterruptedException { Thread t = new Thread(new MyRunnable()); t.setDaemon(true); //设置为守护线程(此后会随着所有用户线程的消亡而消亡) t.start(); //启动守护线程 new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 5; i++) { System.out.println("子线程:" + i); try { Thread.sleep(1000);//此线程休眠1秒 } catch (InterruptedException e) { e.printStackTrace(); } } } }).start(); for(int i = 0;i < 5;i++){ System.out.println("主线程:" + i); Thread.sleep(1000); } } static class MyRunnable implements Runnable{ @Override public void run() { for(int i = 0;i < 10;i++) { System.out.println("守护线程:" + i); try{ Thread.sleep(1000);//此线程休眠1秒 }catch(InterruptedException e) { e.printStackTrace(); } } } } }
五、线程安全问题
多个线程同时争抢一个数据时,将可能导致一些线程得不到真实的数据(存在修改的缘故),从而产生不安全的现象,最终导致整个程序的运行逻辑混乱而出错。(线程不安全)
//线程不安全示例: public static void main(String[] args){ //线程不安全 Runnable run = new Ticket(); new Thread(run).start(); new Thread(run).start(); new Thread(run).start(); } static class Ticket implements Runnable{ private int count = 10; //票数 @Override public void run(){ while(count > 0){ System.out.println("正在售票~"); try{ Thread.sleep(1000); //此线程休眠1秒 }catch(InterruptedException e){ e.printStackTrace(); } count--; System.out.println(Thread.currentThread().getName() + "售票成功,余票:" + count); } } }
结果:
。。。 。。。 | 原因分析(这是其中一种情况):
线程2售票成功,余票:1 | 当最后只剩1张票时,线程1执行后休眠1秒;此时线程2
正在售票~ | 抢到了CPU抛出的时间片执行,随之休眠1秒;此时线程3抢
线程1售票成功,余票:0 | 到了CPU抛出的时间片执行,也随之休眠1秒;线程1先结束
线程2售票成功,余票:-1 | 休眠,count–,余票0;随后是线程2结束休眠,count–,余
线程3售票成功,余票:-2 | 票-1;最后才是线程3结束休眠,count–,余票-2。
1、线程安全1-同步代码块
线程同步关键字:synchronized
同步代码块格式:
private int[] 锁对象 = new int[0]; ... ... synchronized(锁对象){ //此锁对象可以是任意的引用类型。 //需要保护的程序 }
//使用示例: public class Demo{ public static void main(String[] args){ Runnable r = new Ticket(); new Thread(r).start(); new Thread(r).start(); new Thread(r).start(); } static class Ticket implements Runnable{ private int count = 10; //总票数 private Object o = new Object(); @Override public void run(){ while(true){ synchronized(o){ //一旦执行此语句,则对o上锁,原理为内部逻辑实现,无需理解 if(count > 0){ System.out.println("正在售票~"); try{ Thread.sleep(1000); //此线程休眠1秒 }catch(InterruptedException e){ e.printStackTrace(); } count--; System.out.println(Thread.currentThread().getName() + "售票成功,余票:" + count); }else{ break; } } //执行完此语句块后,对o解锁,以便下一个线程抢到o上锁 } } } }
分析:
在同一时刻多个线程调用run()方法的过程中只能有一个线程可抢到此锁对象上锁,执行同步代码块,执行完毕再释放锁对象解锁,从而实现了线程安全的机制。
2、线程安全2-同步方法
与同步代码块相比仅格式上的不同,其格式如下:
权限修饰符 synchronized 返回值声明 方法名称(参数列表){ //需要保护的程序 return 返回值; //返回值声明为void,略 }
//示例代码: public class Demo{ public static void main(String[] args){ Runnable r = new Ticket(); new Thread(r).start(); new Thread(r).start(); new Thread(r).start(); } static class Ticket implements Runnable{ private int count = 10; //总票数 @Override public void run(){ while(sale()); //count > 0继续,相反结束 } public synchronized boolean sale(){ //进入同步方法,上锁 if(count > 0){ System.out.println("正在售票~"); try{ Thread.sleep(1000); //此线程休眠1秒 }catch(InterruptedException e){ e.printStackTrace(); } count--; System.out.println(Thread.currentThread().getName() + "售票成功,余票:" + count); return true; } return false; } //执行完同步方法,解锁 } }
分析:
在同一时刻多个线程调用run()方法的过程中只能有一个线程可抢到此锁对象上锁,执行同步方法,执行完毕再释放锁对象解锁,从而实现了线程安全的机制。
3、线程安全3-显式锁Lock
以上的同步代码块与同步方法均属于隐式锁。
显示锁Lock的使用方法与步骤:
//①声明 权限修饰符 Lock 锁对象名 = new ReentrantLock(); //②结合try-catch + 方法调用 //<1>上锁 锁对象名.lock(); try{ //为需要保护的程序 Thread.sleep(1000); }catch(InterruptedException e){ e.printStackTrace(); }finally{ //<2>解锁 锁对象名.unlock(); }
//示例代码: public class Demo{ public static void main(String[] args){ Runnable r = new Ticket(); new Thread(r).start(); new Thread(r).start(); new Thread(r).start(); } static class Ticket implements Runnable{ private int count = 10; //总票数 private Lock l = new ReentrantLock(); @Override public void run(){ while(true){ l.lock(); //加锁 try { if (count > 0) { System.out.println("正在售票中~"); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } count--; System.out.println(Thread.currentThread().getName() + "售票成功,余票:" + count); } else { break; } }finally { l.unlock(); //解锁 } } } } }
注意:若程序在执行过程中未加锁,执行finally语句块中解锁将产生异常,不安全!【慎用】
4、公平锁与非公平锁
公平锁:先来先到原则,排队执行每一个线程,使每个线程都能获得同一把锁。
非公平锁:无排队原则,多个线程抢一把锁。
synchronized属于非公平锁。Lock即可属于非公平锁,也可属于公平锁。
Lock l = new ReentrantLock(); //非公平锁(不传参默认false) Lock l = new ReentrantLock(true); //公平锁
5、线程死锁
死锁是指多个线程因竞争资源而造成的一种僵局(互相等待),若无外力作用,这些进程都将无法向前推进。——百度经验
//示例代码:警察与罪犯的对话[产生死锁] public class Demo { public static void main(String[] args) { Culprit c = new Culprit(); Police p = new Police(); c.say(p); } static class MyThread extends Thread { private Culprit c; private Police p; public MyThread(Culprit c,Police p){ this.c = c; this.p = p; } @Override public void run(){ p.say(c); } } static class Culprit { public synchronized void say(Police p){ System.out.println("罪犯:你放了我,我放了人质。"); p.reply(); } public synchronized void reply(){ System.out.println("罪犯被放了,罪犯也放了人质"); } } static class Police { public synchronized void say(Culprit c){ System.out.println("警察:你放了人质,我放了你"); c.reply(); } public synchronized void reply(){ System.out.println("我救了人质,但罪犯跑了"); } } }
以上程序产生死锁的原因:
警察在调用say©方法的同时,罪犯也调用了say§方法,使得双方同时被锁上,此时彼此不能调用对方也导致锁产生的方法reply(),最终整个程序被卡住而无法向下执行。
六、多线程通信问题
eg:
①A线程下载音乐,B线程播放音乐,C线程显示歌词。
②M线程炒菜,N线程休眠;—M线程炒菜完毕,唤醒N线程端菜—》N线程端菜,M线程休眠。
1、Object类中相关的常用方法:
变量和类型 | 方法 | 描述 |
---|---|---|
void | wait() | 使当前线程一直等待,直到被唤醒。 |
void | wait(long timeoutMillis) | 等待timeoutMillis毫秒后,自动或直接被唤醒。 |
void | wait(long timeoutMillis,int nanos) | 等待timeoutMillis毫秒nanos纳秒后,同理。 |
void | notify() | 唤醒正在此对象监视器上等待的单个线程。 |
void | notifyAll() | 唤醒等待此对象监视器上所有的线性。 |
七、线程的六种状态
enum Thread.Start
new:刚创建,未启动。
Runnable:正在执行。
Blocked:被阻塞(排队时)。
Waiting:无限等待(休眠)。
TineWaiting:指定时间等待。
Terminated:终止。
线程的状态图:
八、带返回值的线程Callable
线程的第三种实现方式(用之少):
作用:用于带返回值的线程。
描述:实现Callable接口就是线程的一个类,重写call()方法实现线程要执行的任务。
Callable的使用步骤如下:
//1、编写类实现Callable接口,重写call()方法: class MyCallable implements Callable<T> { @Override public <T> call() throws Exception { return T; } } //2、创建FutureTask对象,并传入第一步编写的Callable类对象 Callable callable = new MyCallable(); FutureTask<Integer> future = new FutureTask<>(callable); //3、通过Thread,启动线程 new Thread(future).start();
示例代码:
public class Demo{
public static void main(String[] args) throws ExecutionException, InterruptedException {
Callable c1 = new Sum(1,3,5,7,9);
FutureTask<Integer> f1 = new FutureTask<>(c1);
new Thread(f1).start();
int sum = f1.get();
System.out.println("sum = " + sum);
Callable<String> c2 = new Say<>();
FutureTask<String> f2 = new futureTask<String>(c2);
new Thread(f2).start();
String time = f2.get();
System.out.println(time);
}
static class Sum implements Callable<Integer>{ //指定泛型
private int[] a;
public Sum(int... a){
this.a = a;
}
@Override
public Integer call() throws Exception {
for(int i = 1;i < a.length;i++){
a[0] += a[i];
}
return a[0];
}
}
static class Say<T> implements Callable<T>{ //不指定泛型
private DateFormat df = new SimpleDateFormat("HH:mm:ss");
@Override
public T call() throws Exception {
return (T)("当前时间:" + df.format(new Date()));
}
}
}
//结果:
sum = 25
当前时间:10:21:13
Runnable 与 Callable 的接口定义:
//Callable接口 |//Runnable接口 public interface Callable<V> { |public interface Runnable { V call() throws Exception; | public abstract void run(); } |}
Runnable与Callable的相同点:
- 都是接口
- 都可以编写多线程程序
- 都采用Thread.start()启动线程
Runnable与Callable的不同点:
- Runnable没有返回值,Callable可以返回执行的结果
- Callable接口的call()允许抛出异常,Runnable的run()不能抛出异常
Callable获取返回值:
Callable接口支持返回执行结果,需要调用 FutureTask.get() 得到,此方法会阻塞主线程的继续往下执行,如果不调用不会阻塞。
九、线程池概述
线程所经历的流程:
①创建线程
②创建任务
③执行任务
④关闭线程 其中创建线程与关闭线程,将占用约95%的时间;创建任务与执行任务,将占用约15%的时间。
缺陷:频繁地创建或关闭线程将消耗大量的时间,从而降低的性能。
解决方案:
将所需要的所有线程添加至线程池中,在创建任务和执行任务的过程中,实现线程池中对应的某一线程即可。
1、缓存线程池
(长度无限制)
任务加入后的执行流程:
1、判断线程池是否存在空闲线程。
2、存在则使用。
3、不存在,则创建线程,并放入线程池中,然后使用。//示例: public class Demo{ private static int i = 0; public static void main(String[] args){ ExecutorService service = Executors.newCachedThreadPool(); //向线程池中添加第一个任务 service.execute(new Runnable() { @Override public void run() { while (true) { System.out.println(Thread.currentThread().getName() + " hello -> " + i++); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }); //向线程池中添加第二个任务 service.execute(new Runnable() { @Override public void run() { while (true) { System.out.println(Thread.currentThread().getName() + " world -> " + i++); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }); } } //结果: pool-1-thread-2 world -> 1 pool-1-thread-1 hello -> 0 pool-1-thread-1 hello -> 2 。。。 。。。间隔1秒,无限输出
2、定长线程池
(长度是指定的数值)
任务加入后的执行流程:
1、判断线程池是否在空闲线程。
2、存在则使用。
3、不存在空闲线程,且线程池未满的情况下,则创建线程,并放入线程池,然后执行。
4、不存在空闲线程,且线程池已满的情况下,则等待线程池存在空闲线程。//示例: public class Demo{ public static void main(String[] args){ ExecutorService service = Executors.newFixedThreadPool(2); service.execute(new Runnable() { @Override public void run() { while (true) { System.out.println(Thread.currentThread().getName() + " hello " + i++); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }); service.execute(new Runnable() { @Override public void run() { while (true) { System.out.println(Thread.currentThread().getName() + " world " + i++); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }); } } //结果: pool-1-thread-2 world 1 pool-1-thread-1 hello 0 pool-1-thread-2 world 2 。。。 。。。间隔1秒,无限输出
3、单线程线程池
执行流程:
1、判断线程池的那个线程是否空闲。
2、空闲则使用。
3、不空闲,则等待池中的单个线程空闲后使用。//示例: public class Demo{ public static void main(String[] args){ ExecutorService service = Executors.newSingleThreadExecutor(); service.execute(new Runnable() { @Override public void run() { while (true) { System.out.println(Thread.currentThread().getName() + " hello " + i++); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }); service.execute(new Runnable() { @Override public void run() { while (true) { System.out.println(Thread.currentThread().getName() + " world " + i++); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }); } } //结果 |//都去掉while(true){}后的结果: pool-1-thread-1 hello 0 |pool-1-thread-1 hello 0 pool-1-thread-1 hello 1 |pool-1-thread-1 world 1 pool-1-thread-1 hello 2 | 无输出,且程序不会结束,而是等待向线程池中传任务 。。。 。。。 间隔1秒,无限输出 |
4、周期定长线程池
(周期任务,定长线程池)
执行流程:
1、判断线程池是否存在空闲线程。
2、存在则使用。
3、不存在空闲线程,且线程池未满的情况下,则创建线程并放入线程池,然后使用。
4、不存在空闲线程,且线程池已满的情况下,则等待线程池存在空闲线程。周期性任务执行时:
定时执行,当某个时机触发时,自动执行某个任务。
//示例: public class Demo{ public static void main(String[] args){ ScheduledExecutorService service = Executors.newScheduledThreadPool(2); /** * 1、定时执行一次 * 参数1:定时执行的任务。 * 参数2:延迟时长数字。 * 参数3:时长数字的时间单位,由TimeUnit的常量指定。 * 对应的单位:天、时、分、秒、毫秒、微妙、纳秒。 */ service.schedule(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName() + " hello "); } },1,TimeUnit.SECONDS); //1秒后,执行其run()方法一次 /** * 2、周期性执行任务 * 参数1:周期性执行的任务。 * 参数2:延迟时长数字(第一次执行在什么时间之后)。 * 参数3:周期时长数字(每隔多久执行一次)。 * 参数4:时长数字的时间单位,由TimeUnit的常量指定。 */ service.scheduleAtFixedRate(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName() + " world"); } },5,1,TimeUnit.SECONDS); //5秒后,每隔1秒执行其run()方法 } } //结果: pool-1-thread-1 hello pool-1-thread-2 world pool-1-thread-2 world pool-1-thread-2 world 。。。 。。。每隔1秒,无限执行pool-1-thread-2线程
十、Lambda表达式
Lambda表达式是一个匿名函数,是Java8中提供的新特性,它支持Java进行简单的“函数式编程”。
作用:可理解为是 实现函数式接口对象的创建 + 方法的重写。
优点:可使代码变得简洁紧凑,编写更加简单、灵活。
注意:Java中,Lambda表达式仅用于有且仅有一个方法的接口(也称函数式接口,@FunctionalInterface)。
//示例代码: public class Demo{ private static int i = 0; public static void main(String[] args) { ScheduledExecutorService service = Executors.newScheduledThreadPool(2); //定时执行一次,1秒后,仅执行一次 service.schedule(() -> System.out.println(Thread.currentThread().getName() + " hello "),1,TimeUnit.SECONDS); //周期性执行任务,5秒后,每隔1秒执行一次 service.scheduleAtFixedRate(() -> System.out.println(Thread.currentThread().getName() + " world"),5,1,TimeUnit.SECONDS); float s = ((MyMath) (a, b) -> a + b).myMath(10,20); System.out.println(s); //30.0 MyMath add = (a, b) -> a + b; //加 MyMath sub = (a, b) -> a - b; //减 MyMath mut = (a, b) -> a * b; //乘 MyMath div = (a, b) -> a / b; //除 MyMath mod = (a, b) -> a % b; //余 float adds = add.myMath(100,200); float subs = sub.myMath(100,200); float muts = mut.myMath(100,200); float divs = div.myMath(100,200); float mods = mod.myMath(100,200); System.out.println(adds); //300.0 System.out.println(subs); //-100.0 System.out.println(muts); //20000.0 System.out.println(divs); //0.0 System.out.println(mods); //100.0 print((a,b) -> a + b,1000,2000); //3000.0 } public static void print(MyMath m,int a,int b){ float f = m.myMath(a,b); System.out.println(f); } interface MyMath{ float myMath(int a,int b); } }