Day 9 2021.3.10多线程-Lambda表达式-File类

Day 009 2021.3.10

多线程

快速对齐代码: Ctrl + Alt + L

4个空格: Tab

多线程第一种创建方式(extends Thread)

package com.hong.Day009.Demo01;
//1.创建一个Thread子类
public class MyThread extends Thread{
    //2.在Thread类的子类中重写Thread类中的run方法,设置线程任务(开启线程要做什么)
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println("run" + i);
        }
    }
}
package com.hong.Day009.Demo01;

public class Demo02 {
    public static void main(String[] args) {
        //3.创建Thread类的子类对象
        MyThread  mt = new MyThread();
        //4.调用Thread类中的方法start方法,开启新的线程,执行run方法
        mt.start();
        //主线程会继续执行方法中的代码
        for (int i = 0; i < 20; i++) {
            System.out.println("main" + i);
        }
    }
}

多线程第二种创建方式(implements Runnable)

package com.hong.Day009.Demo02;
/*
    创建多线程程序的第二种方法:实现Runnable接口
    java.lang.Runnable
         Runnable接口应该由那些打算通过某一线程执行其实例的类来实现,类必须定义一个称为run的无参数方法
    Thread(Runnable target)
    分配一个新的 Thread对象。
    Thread(Runnable target, String name)
    分配一个新的 Thread对象。

    实现步骤:
         1.创建一个Runnable接口的实现类
         2.在实现类中重写Runnable接口的run方法
         3.创建一个Runnable接口的实现类对象
         4.创建Thread类对象,构造方法中传递Runnable接口的实现类对象
         5.调用Thread类中的start方法,开启新的线程
*/
//1.创建一个Runnable接口的实现类
public class MyThread implements Runnable{
    //2.在实现类中重写Runnable接口的run方法
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println(Thread.currentThread().getName() + i);
        }
    }
}
package com.hong.Day009.Demo02;

public class Demo01 {
    public static void main(String[] args) {
        //3.创建一个Runnable接口的实现类对象
        MyThread mt = new MyThread();
        //4.创建Thread类对象,构造方法中传递Runnable接口的实现类对象
        Thread t = new Thread(mt);
        //5.调用Thread类中的start方法,开启新的线程
        t.start();
        for (int i = 0; i < 20; i++) {
            System.out.println(Thread.currentThread().getName() + i);
        }
    }
}

Thread与Runnable的区别

/*
实现Runnable接口的创建多线程程序的好处:
    1.避免了单继承的局限性
    2.增强了程序的扩展性,降低了程序的满合性
*/

匿名内部类实现多线程创建

package com.hong.Day009.Demo02;
/*
匿名内部类方式实现多线程的创建
作用:简化代码
    把子类继承父类,重写父类的方法,创建子类对象合一步完成
    把实现类实现接口,重写接口中的方法,创建实现类对象合一步完成
格式:
    new 父类/接口(){
         重复父类/接口中的方法
    };
*/
public class Demo02 {
    public static void main(String[] args) {
        //线程父类Thread
        //new MyThread().start();
        new Thread(){
            @Override
            public void run() {
                for (int i = 0; i < 20; i++) {
                    System.out.println(Thread.currentThread().getName() + i + "黑马");
                }
            }
        }.start();

        //线程接口Runnable
        //Runnable r =new RunnableImpl();//多态

        Runnable r = new Runnable(){
            @Override
            public void run() {
                for (int i = 0; i < 20; i++) {
                    System.out.println(Thread.currentThread().getName() + i + "程序员");
                }
            }
        };
        new Thread(r).start();

        //简化Runnable
        new Thread(new Runnable(){
            @Override
            public void run() {
                for (int i = 0; i < 20; i++) {
                    System.out.println(Thread.currentThread().getName() + i + "www");
                }
            }
        }).start();
    }
}

多线程运行原理

并发与并行

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

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AQo4amID-1615379510653)(C:\Users\asus\AppData\Roaming\Typora\typora-user-images\image-20210310093504125.png)]

进程与线程

进程:是一个内存中运行的应用程序,每个进程都有一个独立的内存空间,一个应用程序可以同时运行多个进程;进程也是程序的一次执行,是系统运行程序的基本单位;系统运行一个程序即是一个进程从创建、运行到消亡的过程

线程:线程是进程中的一个执行单位,负责当前进程中程序的执行,一个进程中至少有一个线程,一个进程中是可以由多个线程的,这个应用程序也可以称之为多线程程序

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yMeV4Y4U-1615379510655)(C:\Users\asus\AppData\Roaming\Typora\typora-user-images\image-20210310094057401.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5PpwPuu6-1615379510657)(C:\Users\asus\AppData\Roaming\Typora\typora-user-images\image-20210310094650673.png)]

线程调度
  • 分时调度

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

  • 抢占式调度

    有限让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),java使用的为抢占式调度

主线程(mian线程)

java使用java.lang.Thread类代表线程,所有的线程对象都必须是Thread类或其子类的实例

执行主(main)方法,从上到下依次执行

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6BXuuIGz-1615379510660)(C:\Users\asus\AppData\Roaming\Typora\typora-user-images\image-20210310095858773.png)]

运行原理

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-91MF2G21-1615379510661)(C:\Users\asus\AppData\Roaming\Typora\typora-user-images\image-20210310082854726.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Wp8gUsd0-1615379510663)(C:\Users\asus\AppData\Roaming\Typora\typora-user-images\image-20210310083402738.png)]

Thread类的常用方法

获取和设置线程的名称

package com.hong.Day009.Demo01;
/*
    获取线程的名称:
        1.使用Thread类中的方法getName()
            String getName() 返回此线程的名称。
        2.可以先获取到当前正在执行的线程,使用线程中的getName()获取线程名称
            static Thread currentThread() 返回对当前正在执行的线程对象的引用。

*/
//1.定义一个Thread类的子类
public class MyThread1 extends Thread{
    //2.重写run方法,设置线程任务

    @Override
    public void run() {
        //获取线程的名称
//        方法1
//        String name = getName();
//        System.out.println(name);

//        方法2
//        Thread thread = Thread.currentThread();
//        System.out.println(thread);

//        方法3
        System.out.println(Thread.currentThread().getName());
    }
}
package com.hong.Day009.Demo01;
/*
    设置线程的名称:
        1.使用Thread类中的setName(名字) 方法
            void setName(String name) 将此线程的名称更改为等于参数 name 。
        2.创建一个带参数的构造方法,参数传递线程的名称,通过父类的带参数构造方法,把线程名称传递给父类,让父类(Thread)给子线程取一个名字
            Thread(String name) 分配一个新的 Thread对象。
*/
public class Demo03 {
    public static void main(String[] args) {
        //创建Thread类的子类对象
        MyThread1 mt = new MyThread1();
        //调用start方法,开启新线程
        mt.setName("小强");
        mt.start();//Thread-0  //Thread[Thread-0,5,main] //Thread-0

        new MyThread1().start();//Thread-1  //Thread[Thread-1,5,main] //Thread-1

    }
}

暂定线程(Sleep)

package com.hong.Day009.Demo01;
/*
    static void sleep(long millis)
    使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行),具体取决于系统定时器和调度程序的精度和准确性。
*/
public class Demo01 {
    public static void main(String[] args) {
        //模拟秒表
        for (int i = 1; i <= 60; i++) {
            System.out.println(i);

            //使用Thread类的sleep方法让程序睡眠i秒钟
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

线程安全问题

概述

安全问题可能出现的前提:多线程访问了共享数据

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-G62rugUo-1615379510664)(C:\Users\asus\AppData\Roaming\Typora\typora-user-images\image-20210310100829778.png)]

线程安全问题代码实现(卖票案例)

package com.hong.Day009.Demo02;
/*
     模拟卖票案例
     创建三个线程:同时开始,对共享的票进行出售
*/
public class Demo03 {
    public static void main(String[] args) {
        //创建Runnable接口的实现类对象
        RunnableImpl run = new RunnableImpl();
        //创建Thread类对象
        Thread t0 = new Thread(run,"票口1");
        Thread t1 = new Thread(run,"票口2");
        Thread t2 = new Thread(run,"票口3");
        //调用start方法
        t0.start();
        t1.start();
        t2.start();
    }
}
package com.hong.Day009.Demo02;
/*
     实现卖票案例

*/
public class RunnableImpl implements Runnable{
    //定义一个多个线程共享的资源
    private int ticket = 100;
    //设置线程任务:买票
    @Override
    public void run() {
        //先判断票是否存在
        //使用死循环,让买票行为重复执行
        while (true){
            if (ticket>0){
                //提高安全问题出现的概率,让程序睡眠
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //票存在
                System.out.println(Thread.currentThread().getName() + "正在卖第" + ticket + "张票");
                ticket--;
            }
        }
    }
}

线程安全问题产生的原理

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VsOvw4Nv-1615379510666)(C:\Users\asus\AppData\Roaming\Typora\typora-user-images\image-20210310102548335.png)]

同步技术原理

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ap43cFYR-1615379510667)(C:\Users\asus\AppData\Roaming\Typora\typora-user-images\image-20210310105255477.png)]

解决线程安全问题(同步代码块)

package com.hong.Day009.Demo02;

/*
     实现买票案例
     出现了线程安全问题:卖出了不存在的票和重复的票
     解决线程安全问题的第一种方法:使用同步代码块
     格式:
         synchronized(锁对象){
             可能会出现线程安全问题的代码(访问了共享数据的代码)
         }
     注意:
         1.通过代码块中的锁对象,可以使用任意的对象
         2.但是必须保证多个线程使用的锁对象是同一个
         3.锁对象作用:把同步代码块锁住,只让一个线程在同步代码块中执行
*/
public class RunnableImpl implements Runnable{
    //定义一个多个线程共享的资源
    private int ticket = 1000;

    //创建一个锁对象(唯一)
    Object object = new  Object();

    //设置线程任务:买票
    @Override
    public void run() {
        //先判断票是否存在
        //使用死循环,让买票行为重复执行
        while (true){
            //创建一个同步代码块
            synchronized (object){
                if (ticket>0){
                    //提高安全问题出现的概率,让程序睡眠
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //票存在
                    System.out.println(Thread.currentThread().getName() + "正在卖第" + ticket + "张票");
                    ticket--;
                }
            }
        }
    }
}
package com.hong.Day009.Demo02;
/*
     模拟买票案例
     创建三个线程:同时开始,对共享的票进行出售
*/
public class Demo03 {
    public static void main(String[] args) {
        //创建Runnable接口的实现类对象
        RunnableImpl run = new RunnableImpl();
        //创建Thread类对象
        Thread t0 = new Thread(run);
        Thread t1 = new Thread(run);
        Thread t2 = new Thread(run);
        //调用start方法
        t0.start();
        t1.start();
        t2.start();
    }
}

解决线程安全问题(同步方法)

package com.hong.Day009.Demo02;

/*
     实现买票案例
     出现了线程安全问题:卖出了不存在的票和重复的票
     解决线程安全问题的第二种方法:使用同步方法
     使用步骤:
         1.把访问了共享数据的代码抽取出来,放到一个方法中
         2.在方法上添加synchronized修饰符
     格式:
         修饰符 synchronized 返回值类型 方法名(参数列表){
             可能会出现线程安全问题的代码(访问了共享数据的代码)
         }
     注意:
        1.同步方法也会把方法内部锁住只让一个线程执行
        2.同步方法的锁住对象是 new RunnableImpl() 也就是this
        3.静态同步方法锁住对象是本类的class属性-->class文件对象(反射)
*/
public class RunnableImpl implements Runnable {
    //定义一个多个线程共享的资源
    private int ticket = 1000;
    //静态同步方法
    /*private static int ticket = 1000;*/
    //设置线程任务:买票
    @Override
    public void run() {
        //先判断票是否存在
        //使用死循环,让买票行为重复执行
        while (true) {
            //创建一个同步代码块
            payTicket();
        }
    }
    /*
        定义一个同步方法
    */
    public /*static*/ synchronized void payTicket(){
        if (ticket > 0) {
            //提高安全问题出现的概率,让程序睡眠
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //票存在
            System.out.println(Thread.currentThread().getName() + "正在卖第" + ticket + "张票");
            ticket--;
        }
    }
}
package com.hong.Day009.Demo02;
/*
     模拟买票案例
     创建三个线程:同时开始,对共享的票进行出售
*/
public class Demo03 {
    public static void main(String[] args) {
        //创建Runnable接口的实现类对象
        RunnableImpl run = new RunnableImpl();
        //创建Thread类对象
        Thread t0 = new Thread(run);
        Thread t1 = new Thread(run);
        Thread t2 = new Thread(run);
        //调用start方法
        t0.start();
        t1.start();
        t2.start();
    }
}

解决线程安全问题(Lock锁)

package com.hong.Day009.Demo02;

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

/*
     实现买票案例
     出现了线程安全问题:卖出了不存在的票和重复的票
     解决线程安全问题的第三种方法:使用Lock锁
     java.util.concurrent.locks
     Lock实现提供比使用synchronized方法和语句可以获得的更广泛的锁定操作
     Lock接口中的方法:
        void lock() 获得锁。
        void unlock() 释放锁。
     java.util.concurrent.locks.ReentrantLock implements locks
     使用步骤:
         1.在成员位置创建一个ReentrantLock对象
         2.在可能会出现安全问题的代码前调用Lock接口中的void lock()获得锁
         3.在可能会出现安全问题的代码后调用Lock接口中的void unlock()释放锁
     格式:
     注意:
*/
public class RunnableImpl implements Runnable {
    //定义一个多个线程共享的资源
    private int ticket = 1000;

    //在成员位置创建一个ReentrantLock对象
    Lock lock1 = new ReentrantLock();
    //设置线程任务:买票
    @Override
    public void run() {
        //先判断票是否存在
        //使用死循环,让买票行为重复执行
/*        while (true) {
            //2.在可能会出现安全问题的代码前调用Lock接口中的void lock()获得锁
            lock1.lock();
            //创建一个同步代码块
            if (ticket > 0) {
                //提高安全问题出现的概率,让程序睡眠
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //票存在
                System.out.println(Thread.currentThread().getName() + "正在卖第" + ticket + "张票");
                ticket--;
            }
            //3.在可能会出现安全问题的代码后调用Lock接口中的void unlock()释放锁
            lock1.unlock();
        }*/
        //更好的写法
        while (true) {
            //2.在可能会出现安全问题的代码前调用Lock接口中的void lock()获得锁
            lock1.lock();
            //创建一个同步代码块
            if (ticket > 0) {
                //提高安全问题出现的概率,让程序睡眠
                try {
                    Thread.sleep(10);
                    //票存在
                    System.out.println(Thread.currentThread().getName() + "正在卖第" + ticket + "张票");
                    ticket--;
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    //3.在可能会出现安全问题的代码后调用Lock接口中的void unlock()释放锁
                    lock1.unlock();
                }
            }
        }
    }
}
package com.hong.Day009.Demo02;
/*
     模拟买票案例
     创建三个线程:同时开始,对共享的票进行出售
*/
public class Demo03 {
    public static void main(String[] args) {
        //创建Runnable接口的实现类对象
        RunnableImpl run = new RunnableImpl();
        //创建Thread类对象
        Thread t0 = new Thread(run);
        Thread t1 = new Thread(run);
        Thread t2 = new Thread(run);
        //调用start方法
        t0.start();
        t1.start();
        t2.start();
    }
}

等待唤醒机制

概述

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

等待唤醒机制(线程之间的通信):

  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 状态

调用的注意细节:

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

生产者与消费者案例

package com.hong.Day009.Demo03;
/*
分析:
资源类包子类
    属性:
        皮
        馅
        包子的状态:true  false
生产者(包子铺)类:
    一个线程类,可以继承Thread
    重写run方法生产包子:
    对包子的状态进行判断:(交替生产两种包子  i%2)
        true --> 包子铺进入等待状态 -->唤醒吃货线程吃包子
        false --> 包子铺进入生产包子状态 -->修改包子状态为true
吃货(消费者)类:
    一个线程类,可以继承Thread
    重写run方法吃包子:
    对包子的状态进行判断:
        true --> 吃货进行吃包子状态 -->吃完包子修改包子的状态为false
        false --> 吃货进入wait状态
测试类:
    包含main方法类
    创建包子对象,创建线程,创建吃货线程
*/
public class Demo01 {
    public static void main(String[] args) {
        //创建包子对象
        Baozi bz = new Baozi();
        //创建包子铺线程
        new BaoZiPu(bz).start();
        //创建吃货线程
        new ChiHuo(bz).start();
    }
}
package com.hong.Day009.Demo03;

/*生产者(包子铺)类:
        一个线程类,可以继承Thread
        重写run方法生产包子:
        对包子的状态进行判断:(交替生产两种包子  i%2)
        true --> 包子铺进入等待状态 -->唤醒吃货线程吃包子
        false --> 包子铺进入生产包子状态 -->修改包子状态为true

注意:
    包子铺线程和包子线程关系-->(通信)互斥
    必须使用同步技术保证两个线程只能有一个在执行
    锁对象必须要保证唯一,可以使用包子对象作为锁对象
        1.需要在成员位置创建一个包子变量
        2.使用带参数构造方法,为这个包子变量赋值
*/
public class BaoZiPu extends Thread {
    //1.需要在成员位置创建一个包子变量
    private Baozi bz;
    //2.使用带参数构造方法,为这个包子变量赋值

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

    @Override
    public void run() {
        //必须使用同步技术保证两个线程只能有一个在执行
        int count = 2;
        //让包子铺一直生产包子
        while (true) {
            synchronized (bz) {
                //对包子的状态进行判断
                if (bz.flag == true) {
                    //包子铺进入等待状态
                    try {
                        bz.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                //被唤醒之后执行,包子铺生产包子(交替生产两种包子)
                if (count % 2 == 0) {
                    //生产 薄皮肉馅包子
                    bz.pier = "薄皮";
                    bz.xianer = "肉馅";
                } else {
                    //生产 薄皮青菜馅包子
                    bz.pier = "薄皮";
                    bz.xianer = "青菜";
                }
                count++;
                System.out.println("包子铺正在生产" + bz.pier + bz.xianer + "包子");
                //生产包子需要3秒钟
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //修改包子状态为true
                bz.flag = true;
                //唤醒吃货线程吃包子
                bz.notify();
                System.out.println("包子铺已经生产好了" + bz.pier + bz.xianer + "包子,吃货已经可以开始吃了");
            }
        }
    }
}
package com.hong.Day009.Demo03;

/*吃货(消费者)类:
        一个线程类,可以继承Thread
        重写run方法吃包子:
        对包子的状态进行判断:
        true --> 吃货进行吃包子状态 -->吃完包子修改包子的状态为false
        false --> 吃货进入wait状态*/
public class ChiHuo extends Thread {
    //1.需要在成员位置创建一个包子变量
    private Baozi bz;
    //2.使用带参数构造方法,为这个包子变量赋值

    public ChiHuo(Baozi bz) {
        this.bz = bz;
    }

    //吃包子
    @Override
    public void run() {
        //必须使用同步技术保证两个线程只能有一个在执行
        //让吃货一直吃包子
        while (true) {
            synchronized (bz) {
                //对包子的状态进行判断
                if (bz.flag == false) {
                    //吃货进入等待状态
                    try {
                        bz.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                //被唤醒之后执行,吃货开始吃包子
                System.out.println("吃货正在吃" + bz.pier + bz.xianer + "包子");
                //吃包子需要1秒钟
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //修改包子状态为false
                bz.flag = false;
                //唤醒包子铺线程吃包子
                bz.notify();
                System.out.println("吃货已经把" + bz.pier + bz.xianer + "的包子吃完了");
                System.out.println("=====================================================");
            }
        }

    }
}
package com.hong.Day009.Demo03;
/*资源类包子类
        属性:
        皮
        馅
        包子的状态:true  false*/
public class Baozi {
    String pier;
    String xianer;
    boolean flag = false;//包子资源是否存在 包子资源状态
}

线程状态

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uQgA3M9a-1615379510668)(C:\Users\asus\AppData\Roaming\Typora\typora-user-images\image-20210310153445370.png)]

/*
java.lang.Thread.State
线程状态:
    1.NEW 尚未启动的线程处于此状态。
    2.RUNNABLE 在Java虚拟机中执行的线程处于此状态。
    3.BLOCKED 被阻塞等待监视器锁定的线程处于此状态。
    4.WAITING 正在等待另一个线程执行特定动作的线程处于此状态。
    5.TIMED_WAITING 正在等待另一个线程执行动作达到指定等待时间的线程处于此状态。
    6.TERMINATED 已退出的线程处于此状态。
*/

等待唤醒案例

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YcNNvtn0-1615379510668)(C:\Users\asus\AppData\Roaming\Typora\typora-user-images\image-20210310154033226.png)]

package com.hong.Day009.Demo04;
/*
    等待唤醒案例:线程之间的通信
        创建一个顾客线程(消费者):告知老板要的包子种类和数量,调用wait方法,放弃CPU的执行,进入到WAITING状态
        创建一个老板线程(生产者):花了5秒做包子,做好包子之后,调用notify方法,唤醒顾客吃包子

    注意:
        顾客和老板线程必须使用同步代码块包裹起来,保证等待和唤醒只能有一个在执行
        同步使用的锁对象必须保证唯一
        只有锁对象才能调用wait和notify方法
如果要一直循环可以加while(true)作为死循环
*/
public class Demo01 {
    public static void main(String[] args) {
        //创建一个锁对象,保证唯一
        Object obj = new Object();
        //创建一个顾客线程(消费者)   (匿名内部类)
        new Thread(){
            @Override
            public void run() {
                //保证等待和唤醒只能有一个在执行,需要使用同步技术
                synchronized (obj){
                    System.out.println("告诉老板要的包子的种类和数量");
                    //调用wait方法
                    try {
                        obj.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //唤醒之后执行的代码
                    System.out.println("包子已经做好了,开吃");
                }
            }
        }.start();
        //创建一个老板线程(生产者)
        new Thread(){
            @Override
            public void run() {
                //花5秒做包子
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //保证等待和唤醒只能有一个在执行,需要使用同步技术
                synchronized (obj){
                    System.out.println("告知顾客,可以吃包子了");
                    //唤醒顾客吃包子
                    obj.notify();
                }
            }
        }.start();
    }

Object类(wait、notifyAll)

/*
    进入到TimeWating(计时等待)有两种方式
    1.使用sleep(long m)方法,在毫秒值结束后,线程睡醒进入到Runnable/Blocked状态
    2.使用wait(long m)方法,wait方法如果再毫秒值结束后,还没有被notify唤醒,就会自动醒来,线程睡醒进入到Runnable/Blocked状态

    notify()只能唤醒一个
    notifyAll()能唤醒全部
*/

线程池

概述

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rJ9oPWlh-1615379510669)(C:\Users\asus\AppData\Roaming\Typora\typora-user-images\image-20210310160858906.png)]

代码实现

package com.hong.Day009.Demo04;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/*  线程池:
    static ExecutorService newFixedThreadPool(int nThreads) 创建一个线程池,该线程池重用固定数量的从共享无界队列中运行的线程。
        参数:
            int nThreads:创建线程池中包含的线程数量
        返回值:
            返回的是ExecutorService接口的实现类对象
    1.Future<?> submit(Runnable task) 提交一个可运行的任务执行,并返回一个表示该任务的未来。
    2.void shutdown() 启动有序关闭,其中先前提交的任务将被执行,但不会接受任何新任务。

    线程池的使用步骤:
        1.使用线程池的工厂类 Executors里边提供的静态方法newFixedThreadPool生产一个指定线程池数量的线程池
        2.创建一个类,实现Runnable接口,重写run方法,设置线程任务
        3.调用ExecutorService中的方法submit,传递线程任务(实现类)
        4.调用ExecutorService中的方法shutdown销毁线程池(不推荐)
*/
public class Demo02 {
    public static void main(String[] args) {
        //1.使用线程池的工厂类 Executors里边提供的静态方法newFixedThreadPool生产一个指定线程池数量的线程池
        ExecutorService es = Executors.newFixedThreadPool(2);
        //3.调用ExecutorService中的方法submit,传递线程任务(实现类)
        es.submit(new ThreadRunnable());//pool-1-thread-2创建了一个新的线程执行
        es.submit(new ThreadRunnable());//pool-1-thread-1创建了一个新的线程执行
        es.submit(new ThreadRunnable());//pool-1-thread-2创建了一个新的线程执行
        //4.调用ExecutorService中的方法shutdown销毁线程池(不推荐)
        es.shutdown();
    }
}
package com.hong.Day009.Demo04;
/*
2.创建一个类,实现Runnable接口,重写run方法,设置线程任务
*/
public class ThreadRunnable implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "创建了一个新的线程执行");
    }
}

Lambda表达式

在数学中,函数就是有输入量,输出量的一套计算方法,也就是“拿什么东西做什么事情”,相对而言,面向对象过分强调“必须通过对象的形式来做事情”,而函数式思想则尽量忽略面向对象的复杂语法–强调做什么,而不是以什么形式做。

冗余的Runnable代码

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qDoHjnlr-1615379510670)(C:\Users\asus\AppData\Roaming\Typora\typora-user-images\image-20210310163910619.png)]

package com.hong.Day009;
/*
    实现Runnable接口的方式(可使用匿名内部类):
        1.创建Runnable接口的实现类
        2.覆盖重写Runnable接口的run方法
*/
public class Demo05 {
    public static void main(String[] args) {
        //匿名内部类
        Runnable task = new Runnable() {
            @Override
            public void run() {//覆盖重写run方法
                System.out.println(Thread.currentThread().getName() + "多线程任务执行");
            }
        };
        new Thread(task).start();//启动线程

        //再次简化
        new Thread(new Runnable() {
            @Override
            public void run() {//覆盖重写run方法
                System.out.println(Thread.currentThread().getName() + "多线程任务执行");
            }
        }).start();
    }
}

Lambda的更优写法

package com.hong.Day009.Demo04;

public class Demo06 {
    public static void main(String[] args) {
        //使用匿名内部类的方式实现多线程
        new Thread(new Runnable() {
            @Override
            public void run() {//覆盖重写run方法
                System.out.println(Thread.currentThread().getName() + "多线程任务执行");
            }
        }).start();
        //使用Lambda表达式,实现多线程
        new Thread(()-> {
                System.out.println(Thread.currentThread().getName() + "多线程任务执行");
            }
        ).start();
    }
}

Lambda标准格式

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Zv9QdemM-1615379510671)(C:\Users\asus\AppData\Roaming\Typora\typora-user-images\image-20210310164734177.png)]

/*
    Lambda表达式的标准格式:
        由三部分组成:
            a.一些参数
            b.一个箭头
            c.一段代码
        格式:
            (参数列表) -> {一些重写方法的代码};
*/

练习:使用Lambda标准格式(无参无返回)

给定一个厨子Cook接口,内含唯一的抽象方法makeFood,且无参数,无返回值,如下:

package com.hong.Day009.Demo05;

public interface Cook {
    void makeFood();
}
package com.hong.Day009.Demo05;
/*
    需求:
        给定一个厨子Cook接口,内含唯一的抽象方法makeFood,且无参数,无返回值
        使用Lambda的标准格式调用invokeCook方法,打印输出“吃饭啦!”字样
*/
public class Demo01 {
    public static void main(String[] args) {
        //调用invokeCook方法
        invokeCook(new Cook() {
            @Override
            public void makeFood() {
                System.out.println("吃饭啦!");
            }
        });

        //使用Lambda表达式
        invokeCook(()-> {
                System.out.println("吃饭啦!");
        });
    }
    //
    private static void invokeCook(Cook cook){
        cook.makeFood();
    }
}

练习:使用Lambda标准格式(有参数有返回)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1Q4zTEK8-1615379510672)(C:\Users\asus\AppData\Roaming\Typora\typora-user-images\image-20210310191525874.png)]

package com.hong.Day009.Demo06;

import java.util.Arrays;

public class Demo01 {
    public static void main(String[] args) {
        //使用数组存储多个Person对象
        Person[] arr = {
                new Person("柳岩",17),
                new Person("迪丽热巴",18),
                new Person("古力娜扎",19)
        };
        //对数组中的Person对象使用Arrays的sort方法通过年龄进行升序(前边-后边)排列
/*        Arrays.sort(arr, new Comparator<Person>() {
            @Override
            public int compare(Person o1, Person o2) {
                return o1.getAge() - o2.getAge();
            }
        });*/
        //使用Lambda方法,简化匿名类
        Arrays.sort(arr, (Person o1,Person o2)-> {
                return o1.getAge() - o2.getAge();
        });

        //遍历数组
        for (Person p : arr) {
            System.out.println(p);
        }

    }
}
package com.hong.Day009.Demo06;

public class Person {
    private String name;
    private int age;

    public Person() {
    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

练习:给定计算机接口

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UWz4m286-1615379510672)(C:\Users\asus\AppData\Roaming\Typora\typora-user-images\image-20210310192743807.png)]

package com.hong.Day009.Demo06;

public class Demo02 {
    public static void main(String[] args) {
        //调用invokeCalc方法,可以使用匿名内部类
        invokeCalc(10, 20, new Calculator() {
            @Override
            public int calc(int a, int b) {
                return a + b;
            }
        });
        //使用Lambda表达式简化匿名内部类
        invokeCalc(10, 20, (int a,int b)-> {
                return a + b;
        });
    }

    /*
        定义一个方法
        参数传递两个int类型的整数
        参数传递Calculator接口
        方法内部调用Calculator中的方法calc计算两个整数的和
    */
    private static void invokeCalc(int a,int b,Calculator calculator){
        int result = calculator.calc(a,b);
        System.out.println("结果是" + result);
    }
}
package com.hong.Day009.Demo06;

public interface Calculator {
    int calc(int a ,int b);
}

Lambda表达式可以省略的内容

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kJCRZGsB-1615379510673)(C:\Users\asus\AppData\Roaming\Typora\typora-user-images\image-20210310193943614.png)]

File类

基本构造方法

package com.hong.Day009.Demo07;

import java.io.File;

/*
    java.io.File
    文件和目录路径名的抽象表示。
    构造方法:
1.File(File parent, String child) 从父抽象路径名和子路径名字符串创建新的 File实例。
2.File(String pathname) 通过将给定的路径名字符串转换为抽象路径名来创建新的 File实例。
3.File(String parent, String child)从父路径名字符串和子路径名字符串创建新的 File实例。

重点:
    file 文件
    directory 文件夹
    path 路径
*/
public class Demo02 {
    public static void main(String[] args) {
        show01();
    }
/*
   File(String pathname) 通过将给定的路径名字符串转换为抽象路径名来创建新的 File实例。
   参数:
        String pathname:字符串的路径名称
        路径可以是以文件结尾,也可以是以文件夹结尾
        路径可以是相对路径,也可以是绝对路径
        路径可以是存在的,也可以是不存在的
        创建File对象,只是把字符串路径封装为File对象,不考虑路径的真实情况
*/
    private static void show01() {
        File f1 = new File("C:\\Users\\asus\\Desktop\\java博客");
        System.out.println(f1);//重写了Object中的toString方法
    }
/*
    File(String parent, String child)从父路径名字符串和子路径名字符串创建新的 File实例。
    参数:把参数分成了两部分
        String parent : 父路径
        String child :子路径
    好处:
        父路径和子路径,可以单独书写,使用起来非常灵活,父路径和子路径都可以改变
*/

/*
    File(File parent, String child) 从父抽象路径名和子路径名字符串创建新的 File实例。
    参数:把参数分成了两部分
        File parent : 父路径
        String child :子路径
    好处:
        父路径和子路径,可以单独书写,使用起来非常灵活,父路径和子路径都可以改变
        父路径是File类型,可以使用File的方法对路径进行一些操作,再使用路径创建对象
*/
}

静态成员变量

/*
    java.io.File
    文件和目录路径名的抽象表示。
    静态成员变量:
1.static String    pathSeparator(路径分隔符  Windows:分号 Linux:冒号)
与系统相关的路径分隔符字符,为方便起见,表示为字符串。
2.static char pathSeparatorChar
与系统相关的路径分隔符。
3.static String    separator(文件名称分隔符 Windows:反斜杠 Linux:正斜杠)
与系统相关的默认名称 - 分隔符字符,以方便的方式表示为字符串。
4.static char  separatorChar
与系统相关的默认名称分隔符。

     操作路径:路径不能写死了
     C:\deveiop\a\a.txt  Windows
     C:/deveiop/a/a.txt  Linux
     C:"+File.separator+"deveiop"+File.separator+"a"+File.separator+"a.txt
*/

绝对路径和相对路径

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xAty6Ot0-1615379510675)(C:\Users\asus\AppData\Roaming\Typora\typora-user-images\image-20210310195443994.png)]

获取功能方法

package com.hong.Day009.Demo07;
/*
1.String getAbsolutePath() 返回此抽象路径名的绝对路径名字符串。
2.String getName() 返回由此抽象路径名表示的文件或目录的名称。
3.String getPath() 将此抽象路径名转换为路径名字符串。
4.long length() 返回由此抽象路径名表示的文件的长度。
*/
public class Demo03 {
    public static void main(String[] args) {
    }
/*
    1.String getAbsolutePath() 返回此抽象路径名的绝对路径名字符串。
    获取的构造方法中的传递路径
    无论路径是绝对的还是相对的,返回都是绝对路径
*/
/*
    3.String getPath() 将此抽象路径名转换为路径名字符串。
    获取的构造方法中的传递路径
    传递的路径是什么类型返回的路径就是什么类型
*/
/*
    2.String getName() 返回由此抽象路径名表示的文件或目录的名称。
    返回路径的结尾,可以是文件也可以是文件夹
*/
/*
    4.long length() 返回由此抽象路径名表示的文件的长度。
    获取的是构造方法指定的文件的大小,以字节为单位
    文件夹没有大小的概念    
*/
}

判断功能方法

/*
1.boolean isDirectory() 测试此抽象路径名表示的文件是否为目录。
2.boolean isFile() 测试此抽象路径名表示的文件是否为普通文件。
3.boolean exists() 测试此抽象路径名表示的文件或目录是否存在。
*/

创建删除功能方法

/*
1.boolean createNewFile() 当且仅当具有该名称的文件尚不存在时,原子地创建一个由该抽象路径名命名的新的空文件。
2.boolean delete() 删除由此抽象路径名表示的文件或目录。
3.boolean mkdir() 创建由此抽象路径名命名的目录。
4.boolean mkdirs() 创建由此抽象路径名命名的目录,包括任何必需但不存在的父目录。
*/

遍历文件夹目录功能

/*
1.String[] list() 返回一个字符串数组,命名由此抽象路径名表示的目录中的文件和目录。
2.File[] listFiles() 返回一个抽象路径名数组,表示由该抽象路径名表示的目录中的文件。
*/
File.separator+"a"+File.separator+"a.txt
*/

绝对路径和相对路径

[外链图片转存中…(img-xAty6Ot0-1615379510675)]

获取功能方法

package com.hong.Day009.Demo07;
/*
1.String getAbsolutePath() 返回此抽象路径名的绝对路径名字符串。
2.String getName() 返回由此抽象路径名表示的文件或目录的名称。
3.String getPath() 将此抽象路径名转换为路径名字符串。
4.long length() 返回由此抽象路径名表示的文件的长度。
*/
public class Demo03 {
    public static void main(String[] args) {
    }
/*
    1.String getAbsolutePath() 返回此抽象路径名的绝对路径名字符串。
    获取的构造方法中的传递路径
    无论路径是绝对的还是相对的,返回都是绝对路径
*/
/*
    3.String getPath() 将此抽象路径名转换为路径名字符串。
    获取的构造方法中的传递路径
    传递的路径是什么类型返回的路径就是什么类型
*/
/*
    2.String getName() 返回由此抽象路径名表示的文件或目录的名称。
    返回路径的结尾,可以是文件也可以是文件夹
*/
/*
    4.long length() 返回由此抽象路径名表示的文件的长度。
    获取的是构造方法指定的文件的大小,以字节为单位
    文件夹没有大小的概念    
*/
}

判断功能方法

/*
1.boolean isDirectory() 测试此抽象路径名表示的文件是否为目录。
2.boolean isFile() 测试此抽象路径名表示的文件是否为普通文件。
3.boolean exists() 测试此抽象路径名表示的文件或目录是否存在。
*/

创建删除功能方法

/*
1.boolean createNewFile() 当且仅当具有该名称的文件尚不存在时,原子地创建一个由该抽象路径名命名的新的空文件。
2.boolean delete() 删除由此抽象路径名表示的文件或目录。
3.boolean mkdir() 创建由此抽象路径名命名的目录。
4.boolean mkdirs() 创建由此抽象路径名命名的目录,包括任何必需但不存在的父目录。
*/

遍历文件夹目录功能

/*
1.String[] list() 返回一个字符串数组,命名由此抽象路径名表示的目录中的文件和目录。
2.File[] listFiles() 返回一个抽象路径名数组,表示由该抽象路径名表示的目录中的文件。
*/
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值