等待与唤醒案例.线程池.Lambda表达式

一  等待唤醒机制

1.1 线程之间通信

概念:多个线程在处理同一个资源,但是处理的动作(线程的任务)却不相同。

为什么要处理线程间通信:
多个线程并发执行时, 在默认情况下CPU是随机切换线程的,当我们需要多个线程来共同完成一件任务,并且我们希望他们有规律的执行, 那么多线程之间需要一些协调通信,以此来帮我们达到多线程共同操作一份数据。

1.2  等待唤醒机制

等待唤醒机制:是多个线程间的一种协作机制。就是在一个线程进行了规定操作后,就进入等待状态(wait()), 等待其他线程执行完他们的指定代码过后 再将其唤醒(notify());在有多个线程进行等待时, 如果需要,可以使用 notifyAll()来唤醒所有的等待线程。
wait/notify 就是线程间的一种协作机制。

等待唤醒中的方法
等待唤醒机制3个方法的含义如下:
1. wait:线程不再活动,不再参与调度,进入 wait set 中,因此不会浪费 CPU 资源,也不会去竞争锁了,这时的线程状态即是 WAITING。它还要等着别的线程执行一个特别的动作,也即是“通知(notify)”在这个对象
上等待的线程从wait set 中释放出来,重新进入到调度队列(ready queue)中
2. notify:则选取所通知对象的 wait set 中的一个线程释放;例如,餐馆有空位置后,等候就餐最久的顾客最先
入座。
3. notifyAll:则释放所通知对象的 wait set 上的全部线程。
 

注意:
哪怕只通知了一个等待的线程,被通知线程也不能立即恢复执行,因为它当初中断的地方是在同步块内,而此刻它已经不持有锁,所以她需要再次尝试去获取锁(很可能面临其它线程的竞争),成功后才能在当初调用 wait 方法之后的地方恢复执行。

总结如下:
如果能获取锁,线程就从 WAITING 状态变成 RUNNABLE 状态;
否则,从 wait set 出来,又进入 entry set,线程就从 WAITING 状态又变成 BLOCKED 状态

调用wait和notify方法需要注意的细节
1. wait方法与notify方法必须要由同一个锁对象调用。因为:对应的锁对象可以通过notify唤醒使用同一个锁对象调用的wait方法后的线程。
2. wait方法与notify方法是属于Object类的方法的。因为:锁对象可以是任意对象,而任意对象的所属类都是继承了Object类的。
3. wait方法与notify方法必须要在同步代码块或者是同步函数中使用。因为:必须要通过锁对象调用这2个方法。

//做包子的(包子铺):是一个线程类,可以继承Thread设置线程任务(run)

/*注意:
包子铺线程和包子线程关系是 通信(互斥)
必须同时同步技术保证两个线程只能有一个在执行
锁对象必须保持唯一,可以使用包子对象作为参数传递进来
包子铺和吃货的类就需要把包子对象作为参数传递进来
1  需要在成员位置创建一个包子变量
2  使用带参数的构造方法,为这个包子变量赋值

 */
public class BaoZiPu extends Thread {
    private BaoZi bz;


    public BaoZiPu(BaoZi bz) {
        this.bz = bz;
    }

    @Override
    public void run() {
        //定义一个变量
        int count = 0;
        //让包子铺一直做包子
        while (true) {

            synchronized (bz) {
                if (bz.zhuangtai == true) {
                    try {
//有包子的时候等待
                        bz.wait();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
                if (count % 2 == 0) {
                    bz.pi = "薄皮";
                    bz.xian = "鲜肉";
                } else {
                    bz.pi = "厚皮";
                    bz.xian = "青菜";
                }
                count++;
                System.out.println("包子铺正在生产:" + bz.pi + bz.xian + "的包子");
                //生产需要3秒时间
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                bz.zhuangtai = true;
                bz.notify();
                System.out.println("包子铺包子做好了" + bz.pi + bz.xian + "可以吃包子了");
            }
        }
    }
}
public class GuKe extends Thread{
    //创建包子变量用于锁
    private BaoZi bz;

    //使用带参数的构造方法,为包子变量赋值
    public GuKe(BaoZi bz) {
        this.bz = bz;
    }

    //设置线程任务
    @Override
    public void run() {
        super.run();
        //使用死循环,让顾客一直吃包子
        while(true){
            synchronized (bz){
                //看有没有包子
                if(bz.zhuangtai == false){
                    try{
                        bz.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println("吃货正在吃"+bz.pi+bz.xian+"的包子");
                bz.zhuangtai = false;
                bz.notify();
                System.out.println("顾客吃完"+bz.pi+bz.xian+"的包子");
                System.out.println("===========================");

            }
        }
    }
}

二 线程池

2.1 概述

线程池:其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多资源。

合理利用线程池能够带来三个好处:
1. 降低资源消耗。减少了创建和销毁线程的次数,每个工作线程都可以被重复利用
2. 提高响应速度。任务可以不需要的等到线程创建就能立即执行。
3. 提高线程的可管理性。可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存

2.2 线程池的使用

Java里面线程池的顶级接口是java.util.concurrent.Executor ,但是严格意义上讲Executor 并不是一个线程池,而只是一个执行线程的工具。真正的线程池接口java.util.concurrent.ExecutorService

Executors类中有个创建线程池的方法如下:

public static ExecutorService newFixedThreadPool(int nThreads) 
:返回线程池对象。(创建的是有界线程池,也就是池中的线程个数可以指定最大数量)

使用线程池对象的方法如下:

public Future<?> submit(Runnable task) :获取线程池中的某一个线程对象,并执行

Future接口:用来记录线程任务执行完毕后产生的结果。线程池创建与使用。


使用线程池中线程对象的步骤:
1. 创建线程池对象。使用线程工厂类Execuyors里面提供的静态方法

2. 创建Runnable接口子类对象。实现Runnable接口,重写run方法

3. 提交Runnable接口子类对象。调用submit方法,传递线程任务(实现类),开启线程执行run方法

4. 关闭线程池(一般不做)。

三  Lambda 表达式

3.1 Lambda 表达式概述

面向对象的思想:
做一件事情,找一个能解决这个事情的对象,调用对象的方法,完成事情.

函数式编程思想:
只要能获取到结果,谁去做的,怎么做的都不重要,重视的是结果,不重视过程

3.2 冗余的Runnable代码

对于Runnable 的匿名内部类用法,可以分析出几点内容:
Thread 类需要Runnable 接口作为参数,其中的抽象run 方法是用来指定线程任务内容的核心;
为了指定run 的方法体,不得不需要Runnable 接口的实现类
为了省去定义一个RunnableImpl 实现类的麻烦,不得不使用匿名内部类
必须覆盖重写抽象run 方法,所以方法名称、方法参数、方法返回值不得不再写一遍,且不能写错;而实际上,似乎只有方法体才是关键所在。

3.3 Lambda优化写法

public class Demo02LambdaRunnable {
public static void main(String[] args) {
new Thread(() ‐> System.out.println("多线程任务执行!")).start(); // 启动线程
}
}

匿名内部类的好处与弊端
一方面,匿名内部类可以帮我们省去实现类的定义;另一方面,匿名内部类的语法——确实太复杂了!

Runnable 接口只有一个run 方法的定义:
public abstract void run();
即制定了一种做事情的方案(其实就是一个函数):
无参数:不需要任何条件即可执行该方案。
无返回值:该方案不产生任何结果。
代码块(方法体):该方案的具体执行步骤。

同样的语义体现在Lambda 语法中,要更加简单:
前面的一对小括号即run 方法的参数(无),代表不需要任何条件;
中间的一个箭头代表将前面的参数传递给后面的代码;
后面的输出语句即业务逻辑代码。

3.3 Lambda标准写法

Lambda省去面向对象的条条框框,格式由3个部分组成:
一些参数
一个箭头
一段代码
Lambda表达式的标准格式为: (参数类型 参数名称) ‐> { 代码语句 }

格式说明:
1,小括号内的语法与传统方法参数列表一致:无参数则留空;多个参数则用逗号分隔。

2,-> 是新引入的语法格式,代表指向动作。传递,把参数传递给方法体

3,大括号内的语法与传统方法体要求基本一致。

public class TestCalculator {
    public static void main(String[] args) {
        invokeCal(200,20,(int a,int b)->{
            return  a+b;
        });
    }
    public static void invokeCal(int a,int b,Calculator C){
        int sum = a + b;
        System.out.println(sum);
    }
}

Lambda表达式:是可推导,可以省略

凡是根据上下文推导出来的内容,都可以省略书写

可以省略的内容:

1,(参数列表):括号中参数列表的数据类型,可以省略

2,(参数列表):括号中的参数如果只有一个,那么类型是()都可以省略

3,(一些代码):如果{ }中的代码只有一行,无论是否有返回值,都可以省略({ },return,分号) 注意:要省略{ },return,分号必须一起省略

invokeCal(200,20,(int a,int b)->{
            return  a+b;
        });


//省略后

invokeCal(200,20,(a, b)-> a+b)
        

注意:
1. 使用Lambda必须具有接口,且要求接口中有且仅有一个抽象方法。
无论是JDK内置的Runnable 、Comparator 接口还是自定义的接口,只有当接口中的抽象方法存在且唯一时,才可以使用Lambda。
2. 使用Lambda必须具有上下文推断。也就是方法的参数或局部变量类型必须为Lambda对应的接口类型,才能使用Lambda作为该接口的实例。

备注:有且仅有一个抽象方法的接口,称为“函数式接口”。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值