03-Java核心类库_多线程

127 阅读18分钟

四,多线程

1,线程与进程

1.1 线程与进程

进程:

  • 是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间

线程:

  • 是进程中的一个执行路径,共享一个内存空间,线程之间可以自由切换,并发执行. 一个进程最少有一个线程
  • 线程实际上是在进程基础之上的进一步划分,一个进程启动之后,里面的若干执行路径又可以划分成若干个线程

1.2 线程调度

目的是为了更合理的利用CPU

分时调度

  • 所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间。

抢占式调度

  • 优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度。
  • CPU使用抢占式调度模式在多个线程间进行着高速的切换。对于CPU的一个核新而言,某个时刻,只能执行一个线程,而 CPU的在多个线程间切换速度相对我们的感觉要快,看上去就是 在同一时刻运行。 其实,多线程程序并不能提高程序的运行速度,但能够提高程序运行效率,让CPU的 使用率更高。

2,同步与异步&并发与并行

2.1 同步与异步

同步:排队执行 , 效率低但是安全.

异步:同时执行 , 效率高但是数据不安全

2.2 并发与并行

并发:指两个或多个事件在同一个时间段内发生。

并行:指两个或多个事件在同一时刻发生(同时发生)。

3,继承Thread

3.1 程序实例

3.2 时序图

3.3 补充

每个线程都有自己的栈空间,共用一份堆内存

4,实现Runnable

4.1 使用方法

另一种实现多线程的方法

  • 创建自定义类实现Runnable接口,并重写run方法;
  • 用自定义类创建一个对象r;
  • 用Thread类创建一个对象t,并将r作为t构造方法的参数;

4.2 实现Runnable与继承Thread

1)实现Runnable与继承Thread相比有如下优势

  • 1,通过创建任务,然后给线程分配任务的方式实现多线程,更适合多个线程同时执行任务的情况;
  • 2,可以避免单继承所带来的局限性(Java允许实现多个接口,但不允许继承多个父类);
  • 3,任务与线程是分离的,提高了程序的健壮性;
  • 4,后期学习的线程池技术,接受Runnable类型的任务,不接受Thread类型的线程;

2)Thread也有一定的好处

5,Thread类

5.1 常用构造方法

5.2 常用其他方法

停止线程的方法:声明一个变量,线程不断监控这个变量,一旦变量达到某种条件调用return即可

所有的用户线程结束,程序才能结束。守护线程是为了守护用户线程,用户线程可以自动结束,所有用户线程结束后,守护线程便会像没有一样。

6,设置和获取线程名称

7,线程休眠sleep

8,线程的中断

过时的stop方法可以直接中断线程,但是如果线程来不及释放资源,会造成一部分垃圾无法回收;

这里采用添加中断标记的方法:调用interrupt方法,子线程执行时捕获中断异常,并在catch块中,添加处理释放资源的代码;

9,守护线程

9.1 概述

线程分为守护线程和用户线程;

  • 用户线程:当一个进程不包含任何存活的用户线程时,进程结束;
  • 守护线程:守护用户线程,当最后一个用户线程结束后,所有守护线程自动死亡;

直接创建的都是用户线程;

设置守护线程:线程对象.setDaemon(true);

9.2 实例

1)不设置守护线程

2)设置为守护线程

10,线程安全1-同步代码块

10.1 线程不安全的原因

多个线程争抢同一个数据,使得数据在判断和使用时出现不一致的情况。解决方法,保证一段数据同时只能被一个线程使用(排队使用)。

解决方案一:同步代码块

格式:

synchronize(锁对象){


}

10.2 代码实例

1,不加锁

2,加锁后

package com.kaikeba;

import javax.xml.namespace.QName;
import java.io.*;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Properties;
import java.util.Scanner;
import java.util.SimpleTimeZone;

public class Demo1 {
    public static void main(String[] args) throws InterruptedException {
        Object o = new Object();
        //线程不安全
        //解决方案1  同步代码块
        //格式:synchronized(锁对象){
        //
        //
        //      }
        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;
        private Object o = new Object();
        @Override
        public void run() {
            //Object o = new Object();    //这里不是同一把锁,所以锁不住
            while (true) {
                synchronized (o) {
                    if (count > 0) {
                        //卖票
                        System.out.println("正在准备卖票");
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        count--;
                        System.out.println(Thread.currentThread().getName()+"卖票结束,余票:" + count);
                    }else {
                        break;
                    }

                }
            }
        }
    }
}

11,线程安全2-同步方法

同步代码块粒度较细,可以给一行代码单独加锁,同步方法顾名思义,是给方法加锁;

package com.kaikeba;

import javax.xml.namespace.QName;
import java.io.*;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Properties;
import java.util.Scanner;
import java.util.SimpleTimeZone;

public class Demo1 {
    public static void main(String[] args) throws InterruptedException {
        Object o = new Object();
        //线程不安全
        //解决方案2  同步方法
        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 (true) {
                boolean flag = sale();
                if(!flag){
                    break;
                }
            }
        }
        public synchronized boolean sale(){
            if (count > 0) {
                //卖票
                System.out.println("正在准备卖票");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                count--;
                System.out.println(Thread.currentThread().getName()+"卖票结束,余票:" + count);
                return true;
            }
            return false;

        }
    }
}

给方法上锁,对应的锁对象就是this, 如果是静态修饰方法的话,锁对象为类名.class(比如这里sale方法若被修饰为静态方法的话,锁对象为Ticket.class,也就是字节码文件对象)

针对以上代码来说 ,锁对象如下:

12,线程安全3-显式锁Lock

同步方法和同步代码块都属于隐式锁,显式锁则是程序员手动加锁、解锁;

package thread;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;


//同步代码块和同步方法都属于隐式锁
//线程同步lock

public class Demo10 {
    public static void main(String[] args) {
        Object o = new Object();
        //线程不安全
        //解决方案1   显示锁  Lock  子类 ReentrantLock

        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;
        //参数为true表示公平锁    默认是false 不是公平锁
        private Lock l = new ReentrantLock(true);
        @Override
        public void run() {
            while (true) {
                l.lock();
                    if (count > 0) {
                        //卖票
                        System.out.println("正在准备卖票");
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        count--;
                        System.out.println(Thread.currentThread().getName()+"卖票结束,余票:" + count);
                    }else {
                        break;
                    }
                    l.unlock();
            }
        }
    }
}

13,公平锁与非公平锁

13.1 区别

公平锁:先来先得,遵循排队;

非公平锁:大家一起抢(同步代码块,同步方法,显式锁都属于非公平锁);

13.2 实现方法

在显式锁实例化时,传入参数true()

14,多线程通信问题

主要借助于wait和notify函数实现

15,生产者与消费者

15.1 前提条件

厨师cook为生产者线程,服务员waiter为消费者线程,食物为生产与消费的物品;

假设目前只有一个厨师,一个服务员,一个盘子。理想状态是:厨师生产一份饭菜,服务员端走一份,且饭菜的属性未发生错乱;

厨师可以制作两种口味的饭菜,制作100次;

服务员可以端走饭菜100次;

15.2 问题一:饭菜的属性错乱

1)实验代码

package com.kaikeba;

public class Demo1 {
    public static void main(String[] args) {
        //多线程通信    生产者与消费者问题
        Food f = new Food();
        new Cook(f).start();
        new Waiter(f).start();
    }
    //厨师
    static class Cook extends Thread{
        private Food f;
        public Cook(Food f) {
            this.f = f;
        }

        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                if(i%2==0){// 设计两种菜色
                    f.setNameAndTaste("老干妈小米粥","香辣味");
                }else {
                    f.setNameAndTaste("煎饼果子","甜辣味");
                }
            }
        }
    }
    //服务员
    static class Waiter extends Thread{
        private Food f;
        public Waiter(Food f) {
            this.f = f;
        }

        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                f.get();
            }
        }
    }
    //食物
    static class Food{
        private String name;
        private String taste;
        public void setNameAndTaste(String name,String taste){// 生产
            this.name = name;
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            this.taste = taste;

        }
        public void get(){  // 消费
            System.out.println("服务员端走的菜的名称是:"+name+",味道是:"+taste);
        }
    }
}

2)错误现象

3)错误原因

15.3 问题二:一次性消费/生产多个菜品

1)实验代码

为了防止在生产过程中setNameAndTaste出现时间片切换,可以用synchronized修饰此方法;

public synchronized void setNameAndTaste(String name,String taste){// 生产
            this.name = name;
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            this.taste = taste;

        }
        public synchronized void get(){  // 消费
            System.out.println("服务员端走的菜的名称是:"+name+",味道是:"+taste);
        }

2)运行效果

3)原因分析

synchronized只是确保了方法内部不会发生线程切换,但并不能保证生产一个消费一个的逻辑关系;

15.4 解决方法

厨师做完饭后喊醒服务员,自己睡着。服务员送完饭后喊醒厨师,自己睡着;

主要修改的部分为setNameAndTaste与get方法:

public synchronized void setNameAndTaste(String name,String taste){// 生产
            if(flag) {
                this.name = name;
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                this.taste = taste;
                flag = false;        // 表示饭菜生产完毕
                this.notifyAll();    // 叫醒服务员
                try {
                    this.wait();     // 睡着
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        public synchronized void get(){  // 消费
            if(!flag){
                System.out.println("服务员端走的菜的名称是:"+name+",味道是:"+taste);
                flag = true;
                this.notifyAll();
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

        }

可以看出饭菜是交替产生并消费的;

16,线程的六种状态

17,带返回值的线程Callable

新的创建线程的方式。之前的创建线程方式:实现Thread的子类、实现Runnable接口,可以看成是和主线程并发执行的;

这里要讲的线程更像是主线程指派的一个任务,主线程可以获得其返回值;

17.1 Runnable 与 Callable

1)接口定义

接口定义 

//Callable接口 
public interface Callable<V> { 
    V call() throws Exception; 
}

//Runnable接口 
public interface Runnable { 
    public abstract void run(); 
}

2)相同点

都是接口

都可以编写多线程程序

都采用Thread.start()启动线程

3)不同点

Runnable没有返回值;Callable可以返回执行结果

Callable接口的call()允许抛出异常;Runnable的run()不能抛出

17.2 Callable使用步骤

1. 编写类实现Callable接口 , 实现call方法

class XXX implements Callable<T> { 
    @Override 
    public <T> call() throws Exception { 
        return T; 
    } 
} 

2. 创建FutureTask对象 , 并传入第一步编写的Callable类对象

FutureTask<Integer> future = new FutureTask<>(callable); 

3. 通过Thread,启动线程

new Thread(future).start();

17.3 常用方法

1)方法介绍

2)代码示例

package com.kaikeba;

import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

public class Demo1 {
    public static void main(String[] args) {
        Callable<Integer> c = new MyCallable();
        FutureTask<Integer> f = new FutureTask<>(c);
        new Thread(f).start();
        for(int i = 0; i < 10; i++) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + i);
        }
    }
    static class MyCallable implements Callable<Integer> {

        @Override
        public Integer call() throws Exception {
//            Thread.sleep(100);// 睡眠过后 给出结果
            for(int i = 0; i < 10; i++) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + i);
            }
            return 100;
        }
    }

}

未使用get方法时,主线程和另一个线程交替执行

使用get方法

18,线程池概述

18.1 为什么需要线程池

如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程 就会大大降低 系统的效率,因为频繁创建线程和销毁线程需要时间.

线程池就是一个容纳多个线程的容 器,池中的线程可以反复使用,省去了频繁创建线程对象的操作,节省了大量的时间和资源。

Java中的四种线程池(对象均为ExecutorService)

18.2 缓存线程池

1)概述

/*** 缓存线程池. 
* (长度无限制) 
* 执行流程: 
* 1. 判断线程池是否存在空闲线程 
* 2. 存在则使用 
* 3. 不存在,则创建线程 并放入线程池, 然后使用 
*/ 
ExecutorService service = Executors.newCachedThreadPool(); 
//向线程池中 加入 新的任务 
service.execute(new Runnable() { 
    @Override 
    public void run() { 
        System.out.println("线程的名称:"+Thread.currentThread().getName()); 
    } 
}); 
service.execute(new Runnable() { 
    @Override 
    public void run() { 
        System.out.println("线程的名称:"+Thread.currentThread().getName()); 
    } 
}); 
service.execute(new Runnable() { 
    @Override 
    public void run() { 
        System.out.println("线程的名称:"+Thread.currentThread().getName()); 
    } 
});

2)代码示例

package com.kaikeba;

import java.util.concurrent.*;

public class Demo1 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        /*** 缓存线程池.
         * (长度无限制)
         * 执行流程:
         * 1. 判断线程池是否存在空闲线程
         * 2. 存在则使用
         * 3. 不存在,则创建线程 并放入线程池, 然后使用
         */
        ExecutorService service = Executors.newCachedThreadPool();
        // 指挥线程池执行新的任务
        service.execute(new Runnable() {// 匿名内部类
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "滴滴滴");
            }
        });
        service.execute(new Runnable() {// 匿名内部类
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "滴滴滴");
            }
        });
        service.execute(new Runnable() {// 匿名内部类
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "滴滴滴");
            }
        });

    }
}

睡眠一段时间后,添加新的任务,查看是否能利用线程池中已存在的线程:

18.3 定长线程池

1)概述

/**
* 定长线程池. 
* (长度是指定的数值) 
* 执行流程:
* 1. 判断线程池是否存在空闲线程 
* 2. 存在则使用 
* 3. 不存在空闲线程,且线程池未满的情况下,则创建线程 并放入线程池, 然后使用 
* 4. 不存在空闲线程,且线程池已满的情况下,则等待线程池存在空闲线程 
*/ 
ExecutorService service = Executors.newFixedThreadPool(2); 
service.execute(new Runnable() { 
    @Override 
    public void run() { 
        System.out.println("线程的名称:"+Thread.currentThread().getName()); 
    } 
}); 
service.execute(new Runnable() { 
    @Override 
    public void run() { 
        System.out.println("线程的名称:"+Thread.currentThread().getName()); 
    } 
});

2)代码示例

设定线程池大小为2,即线程池中最多只允许存在两个线程;

前两个线程执行时,均sleep三秒钟;

第三个任务由于线程池已满,不能开辟新的线程,所以必须等线程池中有空闲线程出现才可以执行;

package com.kaikeba;

import java.util.concurrent.*;

public class Demo1 {
    /*定长线程池
    长度是指定的线程池
    加入任务后的执行流程
        1 判断线程池是否存在空闲线程
        2 存在则使用
        3 不存在空闲线程  且线程池未满的情况下  则创建线程  并放入线程池中  然后使用
        4 不存在空闲线程  且线程池已满的情况下  则等待线程池的空闲线程
    **/
    public static void main(String[] args) {
        ExecutorService service = Executors.newFixedThreadPool(2);
        service.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"锄禾日当午");
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }
        });
        service.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"锄禾日当午");
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }
        });
        service.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"锄禾日当午");
            }
        });
    }
}

18.4 单线程线程池

1)概述

效果与定长线程池 创建时传入数值1 效果一致. 
/**
* 单线程线程池. 
* 执行流程: 
* 1. 判断线程池 的那个线程 是否空闲 
* 2. 空闲则使用 
* 3. 不空闲,则等待 池中的单个线程空闲后 使用 
*/ 
ExecutorService service = Executors.newSingleThreadExecutor(); 
service.execute(new Runnable() { 
    @Override 
    public void run() { 
        System.out.println("线程的名称:"+Thread.currentThread().getName()); 
    } 
}); 
service.execute(new Runnable() { 
    @Override 
    public void run() { 
        System.out.println("线程的名称:"+Thread.currentThread().getName()); 
    } 
});

2)代码示例

package com.kaikeba;

import java.util.concurrent.*;

public class Demo1 {
    /*单线程线程池
    执行流程
        1 判断线程池的那个线程是否空闲
        2 空闲则使用
        3 不空闲则等待它空闲后再使用
    **/
    public static void main(String[] args) {
        ExecutorService service = Executors.newSingleThreadExecutor();
        service.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"锄禾日当午");
            }
        });
        service.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"锄禾日当午");
            }
        });
        service.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"锄禾日当午");
            }
        });
    }
}

18.5 周期定长线程池

1)概述

public static void main(String[] args) { 
    /**
    * 周期任务 定长线程池. 
    * 执行流程: 
    * 1. 判断线程池是否存在空闲线程 
    * 2. 存在则使用 
    * 3. 不存在空闲线程,且线程池未满的情况下,则创建线程 并放入线程池, 然后使用 
    * 4. 不存在空闲线程,且线程池已满的情况下,则等待线程池存在空闲线程 
    *
    * 周期性任务执行时: 
    * 定时执行, 当某个时机触发时, 自动执行某任务 .
    */ 
    ScheduledExecutorService service = Executors.newScheduledThreadPool(2); 

    /**
    * 定时执行 
    * 参数1. runnable类型的任务 
    * 参数2. 时长数字 
    * 参数3. 时长数字的单位 
    */ 

    /*
    service.schedule(new Runnable() { 
        @Override 
        public void run() { 
            System.out.println("俩人相视一笑~ 嘿嘿嘿"); 
        } 
    },5,TimeUnit.SECONDS); 
    */

    /**
    * 周期执行 
    * 参数1. runnable类型的任务 
    * 参数2. 时长数字(延迟执行的时长) 
    * 参数3. 周期时长(每次执行的间隔时间) 
    * 参数4. 时长数字的单位 
    */ 
    service.scheduleAtFixedRate(new Runnable() { 
        @Override 
        public void run() { 
            System.out.println("俩人相视一笑~ 嘿嘿嘿"); 
        } 
    },5,2,TimeUnit.SECONDS); 
}

2)代码示例

package com.kaikeba;

import java.util.concurrent.*;

public class Demo1 {
    /*周期任务  定长线程池
    执行流程
        1 判断线程池是否存在空闲线程
        2 存在则使用
        3 不存在空闲线程  且线程池未满的情况下  则创建线程  并放入线程池中  然后使用
        4 不存在空闲线程  且线程池已满的情况下  则等待线程池的空闲线程

        周期性任务执行时
                定时执行 当某个任务触发时  自动执行某任务
    **/
    public static void main(String[] args) {
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2);
        //定时执行一次
        //参数1:定时执行的任务
        //参数2:时长数字
        //参数3:2的时间单位    Timeunit的常量指定
       /* scheduledExecutorService.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"锄禾日当午");
            }
        },5, TimeUnit.SECONDS);      //5秒钟后执行*/

        /*
        周期性执行任务
            参数1:任务
            参数2:延迟时长数字(第一次执行延迟的时间)
            参数3:周期时长数字(每隔多久执行一次)
            参数4:时长数字的单位
        * **/
        scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"锄禾日当午");
            }
        },5,1,TimeUnit.SECONDS);
    }
}

19,Lambda表达式

19.1 为什么要用lambda表达式

对于某些应用场景,我们更注重于结果,如果能用一个方法解决,那么通过创建对象、调用方法的方式可能会更加繁琐;

1)冗余的Runnable方法

public class Demo1 {
    /**
     * lambda表达式
     * 函数式编程思想(注重结果,而面向对象则是通过创建对象,解决问题)
     * @param args
     */
    public static void main(String[] args) {
        // 冗余的Runnable代码
        Runnable r = new MyRunnable();
        Thread t = new Thread(r);
        t.start();  // 写了这么多 只为了完成一个简单的任务
    }
    static class MyRunnable implements Runnable{

        @Override
        public void run() {
            System.out.println("任务完成!");
        }
    }
}

2)通过匿名内部类简化代码

public class Demo1 {
    /**
     * lambda表达式
     * 函数式编程思想(注重结果,而面向对象则是通过创建对象,解决问题)
     * @param args
     */
    public static void main(String[] args) {
        // 冗余的Runnable代码
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("任务完成!");
            }
        });
        t.start();  // 写了这么多 只为了完成一个简单的任务
    }
}

19.2 使用实例

1)不使用lambda

package com.kaikeba;

import java.util.concurrent.*;

public class Demo1 {
    /**
     * lambda表达式
     * 函数式编程思想(注重结果,而面向对象则是通过创建对象,解决问题)
     * @param args
     */
    public static void main(String[] args) {
        print(new MyMath() {
            @Override
            public int sum(int x, int y) {
                return x + y;
            }
        }, 100, 200);
    }
    public static void print(MyMath m, int x, int y){
        int num = m.sum(x, y);
        System.out.println(num);
    }
    static  interface MyMath{
        int sum(int x, int y);
    }
}

2)使用lambda

不需要实现接口、实例化对象;

package com.kaikeba;

import java.util.concurrent.*;

public class Demo1 {
    /**
     * lambda表达式
     * 函数式编程思想(注重结果,而面向对象则是通过创建对象,解决问题)
     * @param args
     */
    public static void main(String[] args) {
        print((int x, int y) -> {
                return x + y;
            }, 100, 200);
    }
    public static void print(MyMath m, int x, int y){
        int num = m.sum(x, y);
        System.out.println(num);
    }
    static  interface MyMath{
        int sum(int x, int y);
    }
}