线程通信吃包子案例、线程池、Lambda表达式、可变长参数、JUnit: 单元测试

本文深入探讨Java多线程的线程通信,包括wait、notify和notifyAll方法,通过包子铺与吃货的线程通信案例进行解释。接着讲解线程池的使用,如Executors工厂类中的方法。然后,重点介绍Lambda表达式的概念和使用,包括其与匿名内部类的区别。此外,还涵盖了可变长参数的使用规则。最后,简述了单元测试框架JUnit的基本操作和常用注解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一。线程通信: 共享资源
wait() notify()
notify() - 每次只能唤醒一个线程, 只能唤醒等待时间久的那个线程
notifyAll() - 唤醒所有正在等待的线程
wait() -> 只能被notify() 或者 notifyAll() 唤醒
wait(long) -> 到时间以后, 自动醒来
1.包子铺和吃货的线程通信

public class Baozi {
    String pier;
    String xiang;
    boolean flage=false;//初始化包子资源状态
}
---------------------------------------------
public class Baozipu extends Thread {
    private Baozi bz;

    public Baozipu(String name, Baozi bz) {
        super(name);
        this.bz = bz;
    }
    @Override
    public void run() {
        int count = 0;
        //造包子
        while (true) {
            synchronized (bz) {
                //有包子,先等待不造
                if (bz.flage) {
                    try {
                        bz.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                //没包子,造包子
                if (!bz.flage) {
                    System.out.println(Thread.currentThread().getName() + "包子铺开始生产包子");
                    try {
                        Thread.sleep(3000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    if (count % 2 == 0) {
                        bz.pier = "冰皮";
                        bz.xiang = "无仁";
                    } else {
                        bz.pier = "薄皮";
                        bz.xiang = "牛肉大葱";
                    }
                    count++;
                    //造出包子改flage
                    System.out.println("包子造好了:" + bz.pier + bz.xiang);
                    System.out.println("吃货来吃吧");
                    bz.flage = true;
                    //唤醒吃货线程
                    bz.notify();//唤醒等待时间最长的那个线程
                }
            }
        }
    }
}
--------------------------------------------------
public class Chihuo extends Thread {
    private Baozi bz;

    public Chihuo(String name, Baozi bz) {
        super(name);
        this.bz = bz;
    }

    @Override
    public void run() {
        while (true) {
            synchronized (bz) {
                //没包子,等包子
                //System.out.println(Thread.currentThread().getName() + "在等包子");
                if (!bz.flage) {
                    System.out.println(Thread.currentThread().getName() + "在等包子");

                    try {
                        bz.wait(500);//0.5s后自动醒来
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                }

                if (bz.flage) {
                    //有包子吃包子
                    System.out.println(Thread.currentThread().getName() + "吃货在吃" + bz.pier + bz.xiang + "包子");
                    try {
                        Thread.sleep(3500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //吃完把flage改false
                    bz.flage = false;
                    //释放做包子,释放其他吃货
                    bz.notifyAll();//唤醒全部线程

                }
            }

        }
    }
}
-----------------------------------------------
public class Text {
    public static void main(String[] args) {
        Baozi bz=new Baozi();
        Baozipu bzp=new Baozipu("正新bzp",bz);
        Chihuo ch=new Chihuo("齐天大圣",bz);
        Chihuo ch1=new Chihuo("天蓬元帅",bz);

        bzp.start();
        ch.start();
        ch1.start();
    }
}

二。线程池: Executors 工厂类中的方法
线程池执行任务的API:
1.submit(Runnable/Callable)
2.execute(Runnable)
newCachedThreadPool(): 创建一个根据需要创建新线程的线程池,但在可用时将重新使用以前构造的线程。
newFixedThreadPool(int nThreads): 创建一个线程池,该线程池重用固定数量的从共享无界队列中运行的线程。
newScheduledThreadPool(int corePoolSize): 创建一个线程池,可以调度命令在给定的延迟之后运行,或定期执行。
newSingleThreadExecutor(): 创建一个使用从无界队列运行的单个工作线程的执行程序。

  1. newFixedThreadPool
1.Callable 创建线程任务
public class Demo02 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 创建一个固定线程数量的线程池, 创建好线程池的时候, 就已经有了三个线程对象
        ExecutorService pool = Executors.newFixedThreadPool(3);
        // 通过线程任务, 来获得线程对象并且直接开始线程
        Callable run = new Callable<Date>() {
            @Override
            public Date call() throws Exception {
                for (int i = 0; i < 10; i++) {
                    System.out.println(Thread.currentThread().getName() + ": " + i);
                }
                Thread.sleep(10000);
                return new Date();
            }
        };
        // 将线程任务交给线程池 -- 线程对象可以反复使用
        Future<Date> f = pool.submit(run);//获取Callable中call方法返回值
        Date d = f.get(); //获取submit的返回值。此处为线程池中第一个线程执行完的时间。而且get()有阻塞的作用
        System.out.println(d);

        /*try {//此处为get()的用法
            Date date = f.get(3, TimeUnit.SECONDS);
            System.out.println(date);
        } catch (TimeoutException e) {
            System.out.println("结果超时了");
        }*/
        System.out.println("主线程继续");
        // 如果没有关闭线程池, 线程对象依然在, 程序就不会结束

        // 手动关闭线程池 - 会自动将里面的线程对象销毁
        pool.shutdown();
    }
}

Callable对象只能在 :  Future<Date> f = pool.submit(Callable);
                     f.get() -> 得到call方法的返回值
                                可能会遇到阻塞
                     f.get(long, TimeUnit.xx) -> 超时继续
-----------------------------------------------------------------
2.Runnable 创建线程任务
public class Demo {
    public static void main(String[] args) {
        // 通过线程任务, 来获得线程对象并且直接开始线程
        Runnable run = new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    System.out.println(Thread.currentThread().getName() + ": " + i);
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        // 创建一个固定线程数量的线程池, 创建好线程池的时候, 就已经有了三个线程对象
        ExecutorService pool = Executors.newFixedThreadPool(3);

        // 将线程任务交给线程池 -- 线程对象可以反复使用
        pool.execute(run);//开启线程池可以用pool.execute(run);但是execute里只能传Runnable
        Future f = pool.submit(run); // f = null
        pool.submit(run);//开启线程池也可以用pool.submit(run);
        pool.submit(run);

        // 如果没有关闭线程池, 线程对象依然在, 程序就不会结束
        // 手动关闭线程池 - 会自动将里面的线程对象销毁
        pool.shutdown();
    }
}

三。Lambda表达式: JDK1.8 函数式编程思想
面向对象思想: 什么对象, 做什么, 结果是什么
函数式编程思想: 强调做什么,而不是以什么形式做。

  • 语法: (参数列表) -> {一些代码}

    使用Lambda前提:
    1.实现一个接口
    2.接口中只有一个抽象方法
    3.接口对象是作为方法参数使用的

    (参数列表) -> {一些代码}
    (参数列表): 表示要重写的抽象方法的参数列表
    -> : 固定语法, 指向/传递的意思
    {一些代码}: 要重写的方法体
    Lambda 取代匿名内部类的
    匿名内部类: 本质还是类, 编译后也会生成字节码文件, 运行时也要加载
    Lambda: 本质是一个函数, 编译后不会有字节码文件, 直接从内存中获取
    效率更高

  • 可推导即可省略:
    1.() 中的参数类型, 可以省略, 如果有多个参数, 每个参数类型都要一起省略
    2.{} 中如果只有一行代码, 不论这个方法有没有返回值, 那么[{} return ;] 可以省略
    {} return ; 必须一起省略
    3.() 中如果只有一个参数, () 可以省略, 和类型一起省略
    () 中如果没有参数, 必须写 ()

1.Thread的Lambda表达式

public class Lambda1 {
    public static void main(String[] args) {
        //Thred
        Thread t=new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("哈哈");
            }
        });
        t.start();

       //lambda简化
        //方法((参数列表){方法});
       new Thread(()->{
           System.out.println("哈哈");
       }).start();

       //继续优化
        new Thread(()->
            System.out.println("哈哈")//方法只有一行省略{}和;  当()里没有参数()不可以省略
        ).start();

    }
}

2.Collections.sort的Lambda表达式

public class lambda2 {
    public static void main(String[] args) {
       List<String>lis=List.of("99","10","100");
//        lis=new ArrayList<>();数组简化写法

        List<String> list=new ArrayList<>();
        list.add("33");
        list.add("222");
        list.add("1");
        Collections.sort(list, new Comparator<String>() {//匿名内部类形式
            @Override
            public int compare(String o1, String o2) {
                return o1.length()-o2.length();
            }
        });
        System.out.println(list);
        //lambda简化
          Collections.sort(list,(String o1, String o2) -> {//lambda形式
            return o1.length() - o2.length();
        });
        System.out.println(list);
        //继续简化
        Collections.sort(list,( o1, o2)-> o1.length()-o2.length()  //可以省略()内参数类型,只有一行方法,省略{}和;和return
        );
    }
}

3.自定义接口,将接口对象作为方法参数, 然后使用Lambda表达式
demo1

public interface Myinterface {

    public int sum(int a,int b);
}
-----------------------------------
public class Text {
    public static void main(String[] args) {
        int s = Myin((int a, int b) -> {//可以省略return和;和{}
            return a + b;
        }, 3, 2);
        System.out.println(s);
        //继续简化
        int s1 = Myin(( a,  b) -> a + b
        , 3, 2);
    }

    public static int Myin(Myinterface in, int a, int b) {// 需要将接口对象作为方法参数, 才可以使用Lambda表达式
        return in.sum(a, b);
    }
}

demo2

@FunctionalInterface//函数接口的注解
public interface Myinterface {
    int play(int a);//接口中只有一个抽象方法叫函数式接口。但还可以有默认、静态、私有方法
}
-----------------------------------------------
public class Text {
    public static void main(String[] args) {
        int t= myplay((int a)->{//()中只有一个参数时可以省略()
            return a+3;
        },3);
          //简化
        int t2=myplay(a->a+3,3);
        System.out.println(t+":"+t2);

    }
    public static int myplay(Myinterface in,int a){//接口对象作为参数
        return in.play(a);
    }
}

4.函数式接口: 接口中只有一个抽象方法, 默认方法\静态方法\私有方法 随意
@FunctionalInterface -> 检测是不是函数式接口
注解: JDK 1.5 属于程序的一部分, 可以取代一部分配置信息
@Override -> 检测方法是不是重写
@SuppressWarnings -> 压制警告
@Deprecated -> 标记一个类或者方法或者变量, 过时的
@FunctionalInterface -> 检测一个接口是不是函数式接口
四。 可变长参数:
取代了数组 int[] arr -> int… arr
JDK1.5后, 修改为可变长参数
1.方法中, 除了可变长参数, 还有别的参数, 可变长参数必须放在参数列表最后
2.而且一个方法中, 只能有一个可变长参数

public class Demo01 {
    public static void main(String[] args) {
        sum(2, 4);
        sum(1,2,3,4,5);
        sum(1);
        sum();
        sum(1, 2, 3);
        sum(1, 2, 3, 4);

    }

    /*
        传入n个整数值, 将传入的整数相加求和, 并返回
     */
    /*public static int sum(int a, int b) {
        return a + b;
    }*/
    // JDK1.5之前, 不确定参数个数时, 就传入数组类型
    /*@Deprecated
    public static int sum(int[] arr) {
        int sum = 0;
        for (int i = 0; i < arr.length; i++) {
            sum += arr[i];
        }
        return sum;
    }*/
    /*
    JDK1.5后, 修改为可变长参数
    方法中, 除了可变长参数, 还有别的参数, 可变长参数必须放在参数列表最后
    而且一个方法中, 只能有一个可变长参数
     */
    public static int sum(int... arr) {
        int sum = 0;
        for (int i = 0; i < arr.length; i++) {
            sum += arr[i];
        }
        return sum;
    }
    public static void method1(int a, String... ss) {

    }
}

五。JUnit: 单元测试 -> 以方法为单位, 可以取代主方法
System.out.println(); -> 打桩测试
Debug -> 断点测试
JUnit -> 单元测试

别人写好的代码, 我们想要使用
1.将别人写好的代码工程, 打包 jar 文件
是将编译后的字节码文件打包的
2.在自己的工程中 关联这个jar文件 -> 添加依赖
3.代码中直接使用 import

JUnit使用步骤:
1.在工程中创建一个文件夹lib
2.将2个jar包复制到lib中
3.选择jar包 右键 -> Add as Library… 默认添加即可
4.写一个测试类, 在其中写一个方法[不需要返回值,不需要参数]
5.在方法上添加注解 @Test
6.运行这个方法即可
JUnit常用注解:
@Test: 测试的方法, 可以直接运行
@Before: 在测试方法之前调用的
@After: 在测试方法之后调用的

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值