第四章、第5节 多线程

一、多线程技术概述

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 intMAX_ PRIORITY最大优先级(10)
static intMIN_PRIORITY最低优先级(1)
static intNORM_PRIORITY默认优先级(5)

2、常用构造方法

构造器描述
Thread()分配新的Thread对象(无任务)
Thread(Runnable target)分配新的Thread对象,并给线程分配一个任务
Thread(Runnable target,String name)分配新的Thread对象,给线程分配任务同时为线程取名。
Thread(String name)分配新的Thread对象(无任务,单纯起名称)

3、常用方法

变量和类型方法描述
longgetId()获取线程的标识符
StringgetName()获取此线程的名称
voidsetName(String name)设置此线程的名称
intgetPriority()获取线程的优先级
voidsetPriority(int newPriority)更改线程的优先级[见以上3字段]
static voidsleep(long millis)指定毫秒级的线程休眠(暂时停止执行)
static voidsleep(long millis,int nanos)指定毫秒与纳秒级的线程休眠(同理)
voidstart()执行此线程,JVM调用此线程的run()方法
voidinterrupt()中断此线程。
voidsetDaemon(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类中相关的常用方法:

变量和类型方法描述
voidwait()使当前线程一直等待,直到被唤醒。
voidwait(long timeoutMillis)等待timeoutMillis毫秒后,自动或直接被唤醒。
voidwait(long timeoutMillis,int nanos)等待timeoutMillis毫秒nanos纳秒后,同理。
voidnotify()唤醒正在此对象监视器上等待的单个线程。
voidnotifyAll()唤醒等待此对象监视器上所有的线性。

七、线程的六种状态

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);
    }
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值