Java基础最新教程,从api层面到底层原理解抛(下)

55 阅读27分钟

多线程

介绍

image-20220216085516800

线程五种状态

初始状态 -> 就绪状态 -> 运行状态 ->阻塞 -> 终止状态

其中运行状态可以使用sleep方法进行休眠(阻塞),休眠完成后回到就绪状态

运行状态也可以使用join方法进行等待线程执行完成后 回到就绪状态

进程

进程就是当前正在运行的程序,操作系统是支持多进程的可以同时执行多个进程通过PID进行区分

在我们单核CPU的时候同一时刻只能有一个进程,但是他也是支持多进程的,只是他时实时切换进程,我们感觉不到。

image-20220216090317801

线程

线程就好比是进程的一个执行路径,也是cpu来调度单位,一个进程是由一个或多个线程组成,彼此间完成不同的工作

image-20220216091106023

进程和线程区别

image-20220216091346734

线程组成

  1. CPU时间片,操作系统会为每个线程分配执行时间

  2. 运行数据

    1. 栈空间:存储线程需使用的局部变量,每个线程都拥有独立的栈
    2. 堆空间:存储线程需使用的对象,多个线程可以共享堆中的对象
  3. 线程逻辑代码

image-20220216091918615

线程特点

抢占式执行,假如现在有5个线程,100个任务,如果不用抢占式执行那么就全部安排给一个线程执行,效率就很慢

如果5个线程去抢着执行,那么效率就很高,这里如果是单核cpu那么就5个线程轮流执行,如果多核那么就可以同时

100个任务不会出现重复被抢去执行,因为cpu都分的有时间片。

image-20220216092839152

创建方式一

image-20220216093235845

image-20220216093851050

Thread

子线程

package 多线程.Thread;

/**
 * 创建子线程
 */
public class ThreadDemo1 extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("子线程....."+i);
        }
    }
}

主线程

package 多线程.Thread;

public class Test {
    public static void main(String[] args) {
        //执行子线程
        ThreadDemo1 thread = new ThreadDemo1();
        thread.start();
        //主线程循环
        for (int i = 0; i < 100; i++) {
            System.out.println("主线程======="+i);
        }

    }
}

结果

我们可以发现主线程和子线程循环执行

我们主线程和子线程争抢cpu,谁抢到谁执行

image-20220216095543898

获取线程的名称和ID

image-20220216095918983

方式一

通过获取this.线程ID和线程名称 这里的this调用的是当前继承的Thread中的

package 多线程.Thread;

/**
 * 创建子线程
 */
public class ThreadDemo1 extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("线程ID"+this.getId()+"====线程名称"+this.getName()+"====子线程....."+i);
        }
    }
}

image-20220216102158786

方式二

使用Thread中的一个currentThread方法获取当前线程然后调用getid和getname

package 多线程.Thread;

/**
 * 创建子线程
 */
public class ThreadDemo1 extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("线程ID"+Thread.currentThread().getId()+"====线程名称"+Thread.currentThread().getName()+"====子线程....."+i);
        }
    }
}

image-20220216102405324

修改线程名称

image-20220216102516750

方法一:

使用set方法设置线程名称

package 多线程.Thread;

public class Test {
    public static void main(String[] args) {
        //执行子线程
        ThreadDemo1 thread = new ThreadDemo1();
        thread.setName("我是线程一");
        thread.start();

        //执行子线程
        ThreadDemo1 thread1 = new ThreadDemo1();
        thread.setName("我是线程二");
        thread1.start();

        //主线程循环
        for (int i = 0; i < 100; i++) {
            System.out.println("主线程======="+i);
        }

    }
}

image-20220216112332876

方法二

使用构造器

package 多线程.Thread;

/**
 * 创建子线程
 */
public class ThreadDemo1 extends Thread{

    //使用构造方法然后super调用父类的构造方法
    //父类的单参构造指的是Thread的this.name=name
    public ThreadDemo1(String name){
        super(name);
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("线程ID"+Thread.currentThread().getId()+"====线程名称"+Thread.currentThread().getName()+"====子线程....."+i);
        }
    }
}

测试

package 多线程.Thread;

public class Test {
    public static void main(String[] args) {
        //执行子线程
        ThreadDemo1 thread = new ThreadDemo1("线程一");
        thread.start();

        //执行子线程
        ThreadDemo1 thread1 = new ThreadDemo1("线程二");
        thread1.start();

        //主线程循环
        for (int i = 0; i < 100; i++) {
            System.out.println("主线程======="+i);
        }

    }
}

案例

使用继承Thread类实现三个售票窗口各买100张票

image-20220216142930805

子线程

package 多线程.runnable;

public class shili implements Runnable{
    private int piao=10000;
    //创建一个锁,只要是引用类型的变量都可以
    private Object obj=new Object();

    @Override
    public void run() {
        synchronized (obj){
            while (piao>0){
                System.out.println(Thread.currentThread().getName()+"========售卖第"+piao+"票");
                piao--;
            }
        }
    }
}

测试

package 多线程.Thread;

public class Test {
    public static void main(String[] args) {
        ThreadDemo1 threadDemo1 = new ThreadDemo1("窗口一");
        ThreadDemo1 threadDemo2 = new ThreadDemo1("窗口二");
        ThreadDemo1 threadDemo3 = new ThreadDemo1("窗口三");
        threadDemo1.start();
        threadDemo2.start();
        threadDemo3.start();
    }
}

案例二

小明存钱,小美取钱

钱这个对象

package 多线程.runnable.atm;

public class car {
    private int money;

    public int getMoney() {
        return money;
    }

    public void setMoney(int money) {
        this.money = money;
    }
}

存钱

package 多线程.runnable.atm;

/**
 * 存钱
 */
public class add implements Runnable{
    //这里并没有拿到car这个对象,因为我们没有new,所以我们用构造器来设置car对象
    private car car;
    public add(car car){
        this.car=car;
    }
    @Override
    public void run() {
        synchronized (car) {
            for (int i = 0; i < 10; i++) {
                car.setMoney(car.getMoney() + 1000);
                System.out.println("存钱成功,当前余额" + car.getMoney());
            }
        }
    }
}

取钱

package 多线程.runnable.atm;

public class remove implements Runnable{
    private car car;
    public remove(car car){
        this.car=car;
    }
    @Override
    public void run() {
        synchronized (car) {
            for (int i = 0; i < 10; i++) {
                if (car.getMoney() >= 1000) {
                    car.setMoney(car.getMoney() - 1000);
                    System.out.println("取钱成功,当前还剩余" + car.getMoney());
                } else {
                    //为了当银行卡钱全部取完,就一直执行取钱
                    i--;
                    System.out.println("取钱失败,请存钱");
                }
            }
        }
    }
}

测试

package 多线程.runnable.atm;

public class test {
    public static void main(String[] args) {
        car car = new car();
        add add = new add(car);
        remove remove = new remove(car);
        Thread thread = new Thread(add,"小明");
        Thread thread1 = new Thread(remove,"小美");
        thread.start();
        thread1.start();
    }
}

image-20220216204810839

创建方式二

实现Runnable接口

image-20220216143142405

子线程

package 多线程.runnable;

public class Demo1 implements Runnable{
    /**
     * 实现Runnable接口,实现里面的run方法
     */
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName()+"======"+i);
        }
    }
}

测试

package 多线程.runnable;

public class test {
    public static void main(String[] args) {
        //先创建子线程对象
        Demo1 demo1 = new Demo1();
        //创建一个Thread对象,然后将我们子线程放进去
        Thread thread = new Thread(demo1,"线程一");
        //最后启动Thread的对象
        thread.start();
    }
}

使用内部类创建

package 多线程.runnable;

public class test {
    public static void main(String[] args) {
        //使用匿名内部类创建子线程
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    System.out.println(Thread.currentThread().getName()+"======"+i);
                }
            }
        };

        Thread thread = new Thread(runnable,"线程一");
        thread.start();
    }
}

案例

image-20220216144753071

创建方式三

Callable接口和Runnable接口区别

  1. callable接口有返回值,runnable没有返回值
  2. callable接口可以抛出异常,runnable不能抛出异常

Callable

image-20220217160425711

创建callable

package 多线程.Callable;

import javafx.concurrent.Task;

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

public class Demo1 {
    public static void main(String[] args) throws Exception {
        /**
         * Callable接口和Runnable接口区别
         * 1.Callable接口有返回值,runnable没有
         * 2.callable接口可以抛出异常,runnable没有
         */

        /**
         * 功能需求
         * 使用callable实现1-100的和
         */
        //1.创建一个callable对象
        Callable<Integer> callable = new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                int num=0;
                for (int i = 1; i <= 100; i++) {
                    num=num+i;
                }
                return num;
            }
        };

        //他不跟runnable一样,runnable直接放进Thread就可以直接启动线程
        //callable需要先创建FutureTask对象将callable线程传递进去,变成FutureTask对象
        FutureTask<Integer> task = new FutureTask<>(callable);

        //接着我们创建Thread线程对象将FutureTask传递进去,开启线程
        Thread thread = new Thread(task);
        thread.start();

        //我们怎么拿到callable对象返回值呢
        //我们通过
        Integer num= task.get();
        System.out.println(num);


    }
}

完美结合线程池

使用线程池可以完美配合线程池使用

package 多线程.Callable;

import java.util.concurrent.*;

public class Demo2 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //使用callable结合线程池输出1-100的和

        ExecutorService es = Executors.newFixedThreadPool(1);
        Future<Integer> future = es.submit(new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                int num = 0;
                for (int i = 0; i <= 100; i++) {
                    num = num + i;
                }
                return num;
            }
        });
        es.shutdown();
        //获取任务结果,等待任务执行完毕才执行
        System.out.println(future.get());
    }
}

常见方法

image-20220216153357339

sleep休眠

指当前线程进行暂停1000ms在继续执行

子线程

package 多线程.runnable;

public class demo2 implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName()+"===="+i);
            try {
                //使用sleep进行休眠
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
}

测试

package 多线程.runnable;

public class test {
    public static void main(String[] args) {
        demo2 demo2 = new demo2();
        Thread thread = new Thread(demo2,"子线程");
        thread.start();
    }
}

yieId放弃

指当前抢到cpu的时间片后放弃了,让给别人了

package 多线程.runnable;

public class demo2 implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName()+"===="+i);
            //打印完成后放弃本次执行机会,给别人
            Thread.yield();
        }

    }
}

测试

package 多线程.runnable;

public class test {
    public static void main(String[] args) {
        demo2 demo2 = new demo2();
        Thread thread = new Thread(demo2,"线程一");
        thread.start();
        Thread thread1 = new Thread(demo2,"线程二");
        thread1.start();
    }
}
线程一====0
线程一====1
线程二====0
线程二====1
线程一====2
线程二====2
线程一====3
线程二====3
线程二====4
线程一====4
线程一====5
线程一====6
线程一====7
线程二====5
线程二====6
线程二====7
线程二====8
线程二====9
线程二====10
线程二====11
线程二====12
线程二====13

join加入当前线程

加入到(当前线程)后,当前线程进行阻塞,直到调用join方法的线程执行完毕,在执行main线程 当前线程是main主线程,并不是thread。我们thread线程只是main方法创建的

package 多线程.runnable;

public class demo2 implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName()+"===="+i);
        }

    }
}

测试

package 多线程.runnable;

public class test {
    public static void main(String[] args) throws InterruptedException {
        demo2 demo2 = new demo2();
        Thread thread = new Thread(demo2,"子线程");
        thread.start();

        //加入到(当前线程)后,当前线程进行阻塞,直到调用join方法的线程执行完毕,在执行main线程
        //当前线程是main主线程,并不是thread。我们thread线程只是main方法创建的
        thread.join();

        for (int i = 0; i < 100; i++) {
            System.out.println("主线程===="+i);
        }


    }
}

线程优先级

image-20220216160853041

按道理,我们线程1先启动的,线程3是最后启动的,线程3应该是最后执行完成

我们可以给线程3设置优先级让线程3优先执行

以上都是在通常情况下是可能的,但是绝对不是一定的

没有设置优先级

package 多线程.runnable;

public class test {
    public static void main(String[] args) throws InterruptedException {
        demo2 demo1 = new demo2("线程1");
        demo1.start();
        demo2 demo2 = new demo2("线程2");
        demo2.start();
        demo2 demo3 = new demo2("线程3");
        demo3.start();
    }
}

执行结果

image-20220216162051438

当我们设置线程优先级后

package 多线程.runnable;

public class test {
    public static void main(String[] args) throws InterruptedException {
        demo2 demo1 = new demo2("线程1");
        demo1.start();
        demo2 demo2 = new demo2("线程2");
        demo2.start();
        demo2 demo3 = new demo2("线程3");
        demo3.start();

        //设置线程优先级,1是最小,10最大
        demo1.setPriority(1);
        demo2.setPriority(5);
        demo3.setPriority(10);
    }
}

执行结果

image-20220216162205219

线程安全

image-20220216192214570

方式一

image-20220217085345269

package 多线程.runnable;

import java.util.Arrays;

public class test {
    //定义数组默认下标
    private static int index=0;
    public static void main(String[] args) throws InterruptedException {
        //使用同步代码块给数组添加元素
        String[] str = new String[5];
        Runnable r1 = new Runnable() {
            @Override
            public void run() {
                //这里使用同步代码块,将我们资源对象加锁
                //这样我们在执行的时候,就可以将这个资源锁住不让别人动
                synchronized (str){
                    str[index]="Hello";
                    index++;
                }
            };
        };

        Runnable r2 = new Runnable() {
            @Override
            public void run() {
                synchronized (str){
                    str[index]="world";
                    index++;
                }
            };
        };
        Thread thread = new Thread(r1,"线程一");
        Thread thread1 = new Thread(r2,"线程二");
        thread.start();
        thread1.start();
        //这里将主线程等待我们子线程执行完成在执行
        thread.join();
        thread1.join();
        System.out.println(Arrays.toString(str));
    }
}

加锁

package 多线程.runnable;

public class shili implements Runnable{
    private int piao=10000;
    //创建一个锁,只要是引用类型的变量都可以
    private Object obj=new Object();

    @Override
    public void run() {
        //这里不能使用new Object()。因为obj在堆空间里面只有一个,如果使用new的话那么每个线程执行的时候都创建了一个object
        synchronized (obj){
            while (piao>0){
                System.out.println(Thread.currentThread().getName()+"========售卖第"+piao+"票");
                piao--;
            }
        }
    }
}

方式二

同步方法

image-20220216205334515

package 多线程.runnable;

public class shili implements Runnable{
    private int piao=10000;
    @Override
    public void run() {
            while (true){
                //判断方法是否返回false
                if(!sale()){
                    //返回false后进行break
                    break;
                }
            }
    }
    //这里将我们同步代码进行单独取出成方法,在方法返回类型上加一个synchronized
    //这里的synchronized锁的是当前对象
    public synchronized boolean sale(){
        if (piao>0){
            System.out.println(Thread.currentThread().getName()+"========售卖第"+piao+"票");
            piao--;
            return true;
        }
        return false;
    }
}

如果我们将同步方法变成static静态方法,那么静态方法的锁就是当前类 xxx.class

为什么可以使用类?因为类只有一个,所以可以当锁

同步规则

image-20220217085414574

死锁

假如我跟我女朋友一起去餐厅吃饭,但是餐厅只给我和我女朋友一人一根筷子,吃饭需要两根筷子,这个时候,我在等我女朋友给我吧筷子给我这样我就可以去吃饭,然而呢我女朋友在等筷子给她这样她就可以吃饭。这个时候我不让筷子,她也不让筷子,就照成了死锁。

如果写了死锁的代码,就可以在第一个线程开启后使用sleep休眠一会。

image-20220217090535975

线程通信

我们在案例中的银行取钱,会出现一直余额不足,是因为我们存钱的线程一直抢不到时间片,所以我们使用线程通信,在我们存完钱后进行阻塞,等我们的取钱线程执行后通知存钱线程让他苏醒,然后在将取钱线程阻塞,一直循环就可以达到按顺序执行线程

image-20220217092052919

完善存钱取钱案例

Car

package 多线程.通信;

public class Car {
    //创建一个余额变量
    private double money=0;
    //标记
    private boolean flag=false;

    //因为我们没有办法控制cpu先让取钱方法先执行还是存钱方法先执行
    //所以我们用标记来判断谁先执行,如果为true就表示有钱,就不能在执行存钱了
    //需要执行取钱,如果为false就表示没钱,就可以执行存钱,不能执行取钱


    //存钱方法
    public synchronized void add(double x){
    //判断余额是否有钱,如果为true就表示有钱那么就进入等待中,先让取钱执行
        if (flag){
            try {
                //进入等待队列中,释放锁和cpu时间片
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //如果判断没有钱,那么就执行存钱操作
        money=money+x;
        System.out.println(Thread.currentThread().getName()+"====存入"+x+"====当前余额"+money);
        //存完钱后修改标识
        flag=true;
        //随机唤醒正在等待的一个线程
        this.notify();
    }

    //取钱方法
    public synchronized void remove(double x){
        if (!flag){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        money=money-x;
        System.out.println(Thread.currentThread().getName()+"====取钱"+x+"====当前余额"+money);
        flag=false;
        this.notify();
    }}

Add

package 多线程.通信;

public class Add extends Thread{
    private Car car;

    public Add(Car car,String name){
        super(name);
        this.car=car;
    }


    @Override
    public void run() {
        //这里调用car中的存钱方法
        for (int i = 0; i < 10; i++) {
            car.add(1000);
        }

    }
}

Remove

package 多线程.通信;

public class Remove extends Thread{
    private Car car;
    public Remove(Car car,String name){
        super(name);
        this.car=car;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            car.remove(1000);
        }
    }
}

测试

package 多线程.通信;

public class test {
    public static void main(String[] args) {
        Car car = new Car();
        Add add = new Add(car,"小明");
        Remove remove = new Remove(car, "小美");

        add.start();
        remove.start();
    }
}
小明====存入1000.0====当前余额1000.0
小美====取钱1000.0====当前余额0.0
小明====存入1000.0====当前余额1000.0
小美====取钱1000.0====当前余额0.0
小明====存入1000.0====当前余额1000.0
小美====取钱1000.0====当前余额0.0
小明====存入1000.0====当前余额1000.0
小美====取钱1000.0====当前余额0.0
小明====存入1000.0====当前余额1000.0
小美====取钱1000.0====当前余额0.0
小明====存入1000.0====当前余额1000.0
小美====取钱1000.0====当前余额0.0
小明====存入1000.0====当前余额1000.0
小美====取钱1000.0====当前余额0.0
小明====存入1000.0====当前余额1000.0
小美====取钱1000.0====当前余额0.0
小明====存入1000.0====当前余额1000.0
小美====取钱1000.0====当前余额0.0
小明====存入1000.0====当前余额1000.0
小美====取钱1000.0====当前余额0.0

这里出现了一个问题,如果我们创建四个线程,两个存钱,两个取钱,就会出现bug

image-20220217104320652

这是因为执行顺序

image-20220217104758531

想要解决这个问题其实很简单,吧if换成while,因为while循环不能跳出,是一个闭环。

执行后,为什么四个线程都进入了等待队列?

image-20220217110104970

四个都进入等待队列图

image-20220217105859426

问题原因就是,每个线程只能唤醒一个线程,改成唤醒全部就可以了

完整逻辑代码

package 多线程.通信;

public class Car {
    //创建一个余额变量
    private double money=0;
    //标记
    private boolean flag=false;

    //因为我们没有办法控制cpu先让取钱方法先执行还是存钱方法先执行
    //所以我们用标记来判断谁先执行,如果为true就表示有钱,就不能在执行存钱了
    //需要执行取钱,如果为false就表示没钱,就可以执行存钱,不能执行取钱


    //存钱方法
    public synchronized void add(double x){
    //判断余额是否有钱,如果为true就表示有钱那么就进入等待中,先让取钱执行
        while (flag){
            try {
                //进入等待队列中,等待唤醒
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //如果判断没有钱,那么就执行存钱操作
        money=money+x;
        System.out.println(Thread.currentThread().getName()+"====存入"+x+"====当前余额"+money);
        //存完钱后修改标识
        flag=true;
        //唤醒正在等待对象监视器的单个线程
        this.notifyAll();
    }

    //取钱方法
    public synchronized void remove(double x){
        while (!flag){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        money=money-x;
        System.out.println(Thread.currentThread().getName()+"====取钱"+x+"====当前余额"+money);
        flag=false;
        this.notifyAll();
    }}

执行结果

image-20220217110821185

很明显啊,执行效率变低了,但是执行数据代码变得很安全,线程锁也是一把双刃剑。

生产者消费者

image-20220217111241527

产品

package 多线程.生产者消费者;

public class Bread {
    private int id;//面包id
    private String name;//生产者名称

    public Bread() {
    }

    public Bread(int id, String name) {
        this.id = id;
        this.name = name;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

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

工厂

package 多线程.生产者消费者;

public class Plant {
    /**
     * 工厂,用来生产和消费
     */
    //工厂大小
    private Bread[] breads = new Bread[6];
    //剩余数量下标
    private int index=0;

    //生产
    public synchronized void add(Bread bread){
        //判断工厂容器是否满了,这个6取决于厂库能放多少面包
        while (index>=6){
            try {
                //线程进入等待队列中,释放锁和cpu
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //添加面包
        breads[index]=bread;
        System.out.println("生产成功====ID:"+bread.getId()+"===生产者名称:"+bread.getName());
        //下标+1
        index++;
        //唤醒所有线程
        this.notify();
    }

    //消费
    public synchronized void remove(){
        while (index<=0){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //这里为什么要--呢,因为数组下标是从0开始
        index--;
        System.out.println("消费成功===="+breads[index].getId()+"==消费者名称=="+breads[index].getName());
        breads[index]=null;
        this.notify();
    }

}

生产者

package 多线程.生产者消费者;

public class Production extends Thread{
    /**
     * 生产者
     */
    private Plant plant;
    public Production(Plant plant,String name){
        super(name);
        this.plant=plant;
    }

    @Override
    public void run() {
        for (int i = 0; i < 30; i++) {
            plant.add(new Bread(i,Thread.currentThread().getName()));
        }
    }
}

消费者

package 多线程.生产者消费者;

public class Consumption extends Thread{
    /**
     * 消费者
     */
    private Plant plant;
    public Consumption(Plant plant,String name){
        super(name);
        this.plant=plant;
    }
    @Override
    public void run() {
        for (int i = 0; i < 30; i++) {
            plant.remove();
        }
    }
}

测试

package 多线程.生产者消费者;

public class Test {
    public static void main(String[] args) {
        Plant plant = new Plant();
        //生产者
        Production production = new Production(plant,"生产者");
        //消费者
        Consumption consumption = new Consumption(plant,"消费者");
        production.start();
        consumption.start();
    }
}

image-20220217151033662

小结

image-20220217151218500

高级多线程

线程池

image-20220217151728550

线程池原理

当前线程池有三个线程,我们有4个任务,这个时候1-3将分配线程池中的线程,一人一个,这个时候第四个任务就待等待了,当我们线程池中第一个线程执行完成后,第四个任务就会分配到第一个线程进行执行,达到重复利用。

image-20220217151918515

创建线程池

e ke si q te

image-20220217155030179

  1. Executor:线程池的根接口,executor()

  2. ExecutorService:包含管理线程池的一些方法,submit shutdown

    ExecutorService包含以下接口

    1. ThreadPoolExecutor

      scheduledThreadPoolExecutor

Executors:创建线程池的工具类

  1. 创建固定线程池的工具类
  2. 创建缓存线程池,由任务的多少绝对
  3. 创建单线程池
  4. 创建调度线程池,调度:周期,定时任务

使用线程池实现卖票任务

package 多线程.生产者消费者;

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

public class Test {
    public static void main(String[] args) {

        Runnable runnable = new Runnable() {
            //为什么这里可以创建private私有属性?因为这是匿名内部类
            private int piao=1000;
            @Override
            public synchronized void run() {
                while (true){
                    if (piao<=0){
                        break;
                    }
                    System.out.println(Thread.currentThread().getName()+"===成功售卖了第"+piao+"张");
                    piao--;
                }
            }
        };

        //创建线程池,设置默认大小为4
        ExecutorService ex = Executors.newFixedThreadPool(4);
        //将线程任务添加进线程池
        ex.submit(runnable);
        //关闭线程池
        ex.shutdown();

    }
}

Future类

fei you que

Future类表示将要完成任务的结果,配合Callable线程使用

future.get是获取任务结果,等待任务执行完毕才执行的。

案例

image-20220217171614031

使用两个线程,一个线程执行1-50的和,一个线程执行51-100的和,两个线程执行的结果加在一起统计

方法一

package 多线程.Callable;

import javafx.concurrent.Task;

import java.util.concurrent.*;

public class Future {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
       //使用两个线程,一个线程执行1-50的和,一个线程执行51-100的和,两个线程执行的结果加在一起统计

        Callable<Integer> callable1 = new Callable<Integer>(){
            @Override
            public Integer call() throws Exception {
                int num=0;
                for (int i = 0; i <=50; i++) {
                    num=num+i;
                }
                return num;
            }
        };
        Callable<Integer> callable2 = new Callable<Integer>(){
            @Override
            public Integer call() throws Exception {
                int num=0;
                for (int i = 51; i <=100; i++) {
                    num=num+i;
                }
                return num;
            }
        };

        FutureTask<Integer> task1 = new FutureTask<>(callable1);
        FutureTask<Integer> task2 = new FutureTask<>(callable2);

        ExecutorService es = Executors.newFixedThreadPool(2);
        es.submit(task1);
        es.submit(task2);

        //获取结果
        System.out.println(task1.get()+ task2.get());

        //关闭
        es.shutdown();
    }
}

方法二

package 多线程.Callable;

import javafx.concurrent.Task;

import java.util.concurrent.*;

public class future {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
       //使用两个线程,一个线程执行1-50的和,一个线程执行51-100的和,两个线程执行的结果加在一起统计

        ExecutorService es = Executors.newFixedThreadPool(2);

        Future<Integer> future = es.submit(new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                int num=0;
                for (int i = 0; i <= 50; i++) {
                    num=num+i;
                }
                return num;
            }
        });

        Future<Integer> future1 = es.submit(new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                int num=0;
                for (int i = 51; i <= 100; i++) {
                    num=num+i;
                }
                return num;
            }
        });

        //关闭线程池
        es.shutdown();
        //打印线程池输出
        System.out.println(future.get()+future1.get());

    }
}

线程同步、异步

image-20220217190509660

同步

只要有等待就是同步,synchronized就是同步

image-20220217190633871

异步

一起执行的就是异步

image-20220217190803492

Lock接口

我们在使用synchonized的时候,因为是同步的所以效率很低,然后就出现了我们lock锁,他比synchonized功能更强大

image-20220217191020699

重入锁

重入锁表示,当前可以多次拿到锁

image-20220217191922246

案例
锁方法
package 多线程.lock;

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

public class Demo1 {
    //创建lock锁
    private Lock lock = new ReentrantLock();
    //创建数组
    private String[] str={"A","B","","",""};
    //数组大小
    private int index=2;

    //添加数组元素方法
    public void add(String value){
        //因为我们添加方法要保证原子性所以要加锁
        lock.lock();
        try {
            //执行添加方法
            str[index]=value;
            index++;
        }finally {
            //释放锁
            lock.unlock();
        }
    }

    //给外界写一个获取str数组的方法
    public String[] getstr(){
        return str;
    }


}

测试

package 多线程.lock;

import java.util.Arrays;

public class Test {
    public static void main(String[] args) throws InterruptedException {
        //我们创建两个线程去执行添加数组元素方法
        Demo1 demo1 = new Demo1();

        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                System.out.println("1");
                demo1.add("Hello");
            }
        };
        Runnable runnable1 = new Runnable() {
            @Override
            public void run() {
                System.out.println("2");
                demo1.add("World");
            }
        };

        Thread thread = new Thread(runnable);
        Thread thread1 = new Thread(runnable1);

        thread.start();
        thread1.start();

        //这里使用join方法是为了避免先打印tostring方法,
        //所以我们先让main线程阻塞等待我们子线程执行添加完成后,在打印tostring
        thread.join();
        thread1.join();

        System.out.println(Arrays.toString(demo1.getstr()));

    }
}

/**结果
1
2
[A, B, Hello, World, ]
买票
package 多线程.lock.买票;

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

public class Demo1 implements Runnable{
    //创建一个lock锁
    private Lock lock = new ReentrantLock();
    //票总数
    private int piao=100;


    @Override
    public void run() {
        lock.lock();
        try {
            while (piao>0) {
                System.out.println(Thread.currentThread().getName() + "成功售票第======" + piao);
                piao--;
            }
        }finally {
            lock.unlock();
        }
    }
}

测试

package 多线程.lock.买票;

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

public class Demo2 {
    public static void main(String[] args) {
        Demo1 demo1 = new Demo1();
        ExecutorService ex = Executors.newFixedThreadPool(4);
        for (int i = 0; i < 4; i++) {
            ex.submit(demo1);
        }
        ex.shutdown();
    }
}
读写锁

简单来说就是,读写分离,读用读的方法,写用写的方法,互不干扰。

他跟我们互斥锁lock区别是,lock是读一次写一次效率慢,读写锁读和写是分开的,效率很快

image-20220217201919763

读写锁优势

image-20220217202136224

读写锁案例

读写锁

package 多线程.读写分离;

import java.util.concurrent.locks.ReentrantReadWriteLock;

public class Demo1 {
    //创建读写锁
    private ReentrantReadWriteLock rrl = new ReentrantReadWriteLock();
    //通过读写锁获取读锁
    private ReentrantReadWriteLock.ReadLock readLock = rrl.readLock();
    //通过读写锁获取写锁
    private ReentrantReadWriteLock.WriteLock writeLock=rrl.writeLock();
    //用来存放读和写的变量
    private String str;


    //读取
    public String getstr(){
        //开启读锁
        readLock.lock();
        try {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("读取成功"+this.str);
            return this.str;
        }finally {
            readLock.unlock();
        }
    }
    //写入
    public void setstr(String str){
        writeLock.lock();
        try {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            this.str=str;
            System.out.println("写入成功"+str);
        }finally{
            writeLock.unlock();
        }
    }
}

测试

package 多线程.读写分离;


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

public class Demo2 {
    public static void main(String[] args) {
        long s=System.currentTimeMillis();
        Demo1 demo1 = new Demo1();
        Runnable runnable = new Runnable(){
            @Override
            public void run() {
                demo1.getstr();
            }
        };
        Runnable runnable1 = new Runnable() {
            @Override
            public void run() {
                demo1.setstr("hello");
            }
        };
        //创建一个线程池大小为20;
        ExecutorService es=Executors.newFixedThreadPool(20);
        //分配给18个读线程,2个写线程
        for (int i = 0; i < 2; i++) {
            es.submit(runnable1);
        }
        for (int i = 0; i < 18; i++) {
            es.submit(runnable);
        }
        es.shutdown();

        //这里使用while循环是为了判断当前线程池的线程全部执行完成后,在打印运行耗时
        while (!es.isTerminated()){
        }
        long r=System.currentTimeMillis();
        System.out.println(r-s);


    }

}

结果

写入成功hello

写入成功hello
    
读取成功hello
读取成功hello
读取成功hello
读取成功hello
读取成功hello
读取成功hello
    
读取成功hello
读取成功hello
读取成功hello
读取成功hello
读取成功hello
读取成功hello
读取成功hello
读取成功hello
读取成功hello
    
读取成功hello
读取成功hello
读取成功hello
3051

Process finished with exit code 0

可以发现读写锁分离,读会很快

线程安全集合

Collection体系

Set:CopyOnwriteAyyaySet

List:Vector(线程安全但是效率低,不常用),CopyOnWriteList

新增Queue:ConcurrentLinkedQueue

  BlockingQueue:ArrayBlockingQueue,LinkedBlockingQueue

image-20220218091148244

Map体系

ConcurrentSkipListMap:跟TreeMap一样;线程安全

HashTable,Properties,线程安全,但是用的人很少

新增 ConcurrentHashMap

image-20220218091130136

演示线程不安全集合

ArrayList:线程不安全,我们并发执行的时候,会出现线程不安全异常

image-20220218094714414

解决

Collections工具类提供了很多可以获得线程安全集合的方法都是以synchonized开头的

  1. 在jdk1.5之前没有线程安全的集合,可以使用工具类将我们不安全的线程转换成安全的线程

image-20220218094918465

image-20220218100021322

在jdk1.5之后,我们可以使用线程安全的集合

image-20220218100135022

CopyOnWriteArraylist

CopyOnWriteArrayList他是写锁,没有读锁,他线程安全是因为在写的时候先让copy一份,然后在添加新元素,最后替换

通过消耗资源来达到线程安全

image-20220218101900405

使用20个线程添加200个安全集合元素

package 多线程.安全集合;

import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Demo1 {
    public static void main(String[] args) {
        //使用线程安全的集合
        CopyOnWriteArrayList<String> arraylist = new CopyOnWriteArrayList<>();
        //使用线程池创建20个线程同时添加元素达到并发
        ExecutorService ex= Executors.newFixedThreadPool(20);

        for (int i = 0; i < 20; i++) {
            ex.submit(new Runnable() {
                @Override
                public void run() {
                    for (int j = 0; j < 10; j++) {
                        arraylist.add(Thread.currentThread().getName()+"===="+j);
                    }
                }
            });
        }
        ex.shutdown();
        //判断线程池中是否全部关闭,如果没有全部关闭就一直等待
        while (!ex.isTerminated()){

        }
        //打印并发后添加的总数
        System.out.println(arraylist.size());
    }
}

CopyOnWriteArraySet

线程安全的set,底层使用的是CopyOnWriteAyyayList实现

唯一不同在于,使用addifAbsent()添加元素,会遍历数组,

如存在元素,则不会添加(直接扔掉副本)

他跟set集合不同的是他是有序的

image-20220218105207622

package 多线程.安全集合;

import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Demo1 {
    public static void main(String[] args) {
        //使用线程安全的集合
        //底层调用的是CopyOnWriteArrayList。
        //他在添加元素的时候,会先去遍历整个数组有没有重复的元素
        CopyOnWriteArraySet<String> set = new CopyOnWriteArraySet<>();
        set.add("pingguo");
        set.add("xiaomi");
        set.add("huawei");
        set.add("huawei");
        System.out.println(set.size());
        System.out.println(set.toString());
    }
}

image-20220218105701358

Queue接口(队列)

Queue是Collection的子接口

队列:跟排队一样,先进先出,栈:先进后出(后进后出)

image-20220218110228266

使用
package 多线程.安全集合;
import java.util.*;

public class Demo1 {
    public static void main(String[] args) {
        /**
         * 什么是入队出队,就是一个队列入队就多一个,出队就少一个,还是有序的
         */
        
        //使用Queue进行入队和出队
        Queue<String> queue = new LinkedList<>();
        queue.offer("苹果");
        queue.offer("小米");
        queue.offer("榴莲");
        queue.offer("香蕉");

        System.out.println("当前队列大小"+queue.size());
        //开始出队
        int size=queue.size();
        for (int i = 0; i < size; i++) {
            //调用poll方法会打印并出队
            System.out.println(queue.poll());
            //调用peek方法会打印但不会出队
            System.out.println(queue.peek());
        }
        
        //查询队列大小
        System.out.println("当前队列大小"+queue.size());
    }
}

image-20220218111202651

线程安全的队列

非阻塞队列

ConcurrentLinkedQueue(ken karente qs)

表示当我们当前的数据先复制出来一份,然后新的值赋值给当前数据的时候会判断原始的值是不是跟覆盖的值一样,如果一样就更改,如果不一样就表示并发的时候这个值有问题就不更改

image-20220218111553593

image-20220218111623584

package 多线程.安全集合;

import java.lang.reflect.Array;
import java.util.*;
import java.util.concurrent.*;

public class Demo1 {
    public static void main(String[] args) throws InterruptedException {
        /**
         * 什么是入队出队,就是一个队列入队就多一个,出队就少一个,还是有序的
         */
        ConcurrentLinkedDeque<Integer> queue = new ConcurrentLinkedDeque<>();
        //创建两个线程,同时堆queue进行添加元素
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 5; i++) {
                    queue.offer(i);
                }
            }
        });

        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 5; i < 10; i++) {
                    queue.offer(i);
                }
            }
        });

        //开启线程开始入队
        thread.start();
        thread1.start();

        //出队操作
        //为了保证先执行玩入队在执行出队,我们调用join方法
        thread.join();
        thread1.join();
        System.out.println("===========开始出队============");
        int size=queue.size();
        for (int i = 0; i < size; i++) {
            System.out.println(queue.poll());
        }

    }
}

image-20220218112638886

阻塞队列

BlockingQueue

image-20220218152541750

image-20220218152640261

有界队列

package 多线程.阻塞队列;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

public class Demo1 {
    public static void main(String[] args) throws InterruptedException {
        //阻塞队列ArrayBlcokQueue
        //创建一个阻塞队列,大小为5
        ArrayBlockingQueue<String> arr = new ArrayBlockingQueue<String>(5);
        //然后我们想阻塞队列里面添加元素
        arr.put("aaa");
        arr.put("bbb");
        arr.put("ccc");
        arr.put("ddd");
        arr.put("eee");
        System.out.println(arr.toString());//[aaa, bbb, ccc, ddd, eee]
        //让我们执行到这里的时候会发现不执行了一直阻塞中
        //是因为超出他了范围了,一直阻塞在,如果集合中元素减少一个,这时就可以添加进去了。
        //arr.take();表示将集合第一个元素取走,然后清除,清除后集合大小就是4个,然后下面那个元素就可以加进去了。
        arr.put("fff");

        System.out.println(arr.toString());//[bbb, ccc, ddd, eee, fff]

    }
}

实现生产者消费者

package 多线程.生产者消费者;

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

public class Test {
    public static void main(String[] args) throws InterruptedException {
        //创建一个线程队列
        ArrayBlockingQueue<Integer> arr = new ArrayBlockingQueue<Integer>(5);

        //创建两个线程进行添加和取出
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 20; i++) {
                    try {
                        arr.put(i);
                        System.out.println("生成成功"+Thread.currentThread().getName()+"====="+i);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        },"生产者").start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 20; i++) {
                    try {
                        arr.take();
                        System.out.println("消费成功"+i);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
    }
}
线程安全的HashMap

ConCurrentHashMap

jdk1.7之前是分段锁设计,生成16个段,每个段是独立的,锁也是锁当前这个段,当多个对象存入同一个段时,才需要互斥

1.8之后就是CAS无锁算法参考ConcurrentLinkedQueue(非阻塞队列)

image-20220218160053651

package 多线程.安全集合;

import java.util.concurrent.ConcurrentHashMap;

public class ConcurrenthashMap {
    public static void main(String[] args) {
        //线程安全的HashMap(concurrenthashmap)

        ConcurrentHashMap<String, String> hashMap = new ConcurrentHashMap<>();
        //创建5个线程
        for (int i = 0; i < 5; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    //分别想集合中添加元素
                    for (int j = 0; j < 20; j++) {
                        hashMap.put(Thread.currentThread().getName()+"=="+j,j+"");
                        //打印
                        System.out.println(hashMap);
                    }
                }
            }).start();
        }
        //执行后就没有出现并发出错,因为concurrenthashmap是一个安全集合框架
    }
}

总结

image-20220218162512103

网络编程

image-20220219154024585

什么是网络

image-20220219154250931

什么是计算机网络

局域网:一个教室,一栋楼,一个学校

城域网:一个城市

广域网:互联网

  	1. 万维网:网站

image-20220219154503565

网络模型

image-20220219155946425

模型演示

image-20220219160544536

image-20220219160704625

TCP

相当于打电话,需要保证对方在线

image-20220219161058240

建立链接三次握手

image-20220219161328816

断开链接四次挥手

  1. A向B发送:我们分手
  2. B向A发送:让我考虑一下
  3. B向A发送:我同意分手
  4. A向B发送:好的

image-20220219161556083

TCP

也相当于是短信,发送短信不需要对方是否在线,可以直接发送过去,

image-20220219161749109

IP

IPV4是由4个字节,32整数组成,每个字节不可操作255,因电脑数量越来越多,导致IPv4数量不够

IPV6是由16个字节128位数,他的数量可以说很大,可以给地球每个沙子分配一个IP

image-20220219162217850

IPV4字段分类

image-20220219162441826

端口号

image-20220219162729714

常用方法

InteAddress类

image-20220219162806808

使用

package 网络编程.Ip类;

import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;

/**
 * InetAddress类的使用
 * 1.创建本机对象
 * 2.创建局域网对象
 * 3.创建广域网对象
 */
public class Demo1 {
    public static void main(String[] args) throws IOException {
        System.out.println("======创建本机IP对象============");
        //1.1通过getlocalhost方法获取本机对象
        InetAddress localHost = InetAddress.getLocalHost();
        System.out.println("本机IP:"+localHost.getHostAddress()+"本机名称:"+localHost.getHostName());
        //1.2通过getbyname(ip地址)方法获取本机对象
        InetAddress byName = InetAddress.getByName("172.18.2.7");
        System.out.println("本机IP:"+byName.getHostAddress()+"本机名称:"+byName.getHostName());
        //1.3通过getbyname(127.0.0.1)方法获取本机对象
        InetAddress byName1 = InetAddress.getByName("127.0.0.1");
        System.out.println("本机IP:"+byName1.getHostAddress()+"本机名称:"+byName1.getHostName());
        //1.4通过getbyname(localhost)
        InetAddress byName2 = InetAddress.getByName("localhost");
        System.out.println("本机IP:"+byName2.getHostAddress()+"本机名称:"+byName2.getHostName());
        System.out.println("======创建局域网IP对象============");
        //如果找不到就会返回ip地址和主机名为输入的ip地址
//        InetAddress juyu = InetAddress.getByName("172.18.2.8");
//        System.out.println("本机IP:"+juyu.getHostAddress()+"本机名称:"+juyu.getHostName());
//        //通过创建的对象.isReachable(时间),来判断是否存在,返回true或false
//        System.out.println(juyu.isReachable(2000));
        System.out.println("======创建广域网IP对象");
        //获取单个IP
        InetAddress guangyu = InetAddress.getByName("www.baidu.com");
        System.out.println("本机IP:"+guangyu.getHostAddress()+"本机名称:"+guangyu.getHostName());
        //获取全部IP
        System.out.println("====获取全部IP====");
        InetAddress[] guangyu1=InetAddress.getAllByName("www.baidu.com");
        for (InetAddress g :guangyu1) {
            System.out.println(g.getHostAddress());
        }
    }
}

基于TCP网络协议编程

image-20220219171657486

开发步骤

image-20220219173246013

流程

  1. TCP三次握手:先创建服务器serversocket然后调用accept方法进行监听客户端链接
  2. 客户端socket开启并输入服务器的ip+端口与服务器建立链接
  3. 这时服务器的accept方法就返回socket对象负责与客户端的socket进行通信
  4. 这个时候服务器的serversocket就没用了,服务器的serversocket就是建立客户端的三次握手
  5. 客户端通过输出流向服务器发送数据,服务器通过输入流获取
  6. 服务器通过输出流向客户端发送数据,客户端通过输入流获取
  7. 发送完成后,就可以调用close进行关闭资源了

image-20220219175614010

案例一

客户端向服务器端发送一句话

服务器端

package 网络编程.TCP编程;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

public class Demo1 {
    public static void main(String[] args) throws IOException {
        /*
        服务器开发步骤
        1.先创建serversocket对象,并传入端口号
        2.通过accept等待客户端链接
        3.使用输入流,接收客户端发送的数据
        4.使用输出流,发送给客户端数据
        5.关闭流,关闭socket对象
         */

        //1
        ServerSocket serverSocket = new ServerSocket(8899);
        System.out.println("服务器启动成功...");
        //2,这里等待客户端链接,如果没有链接那就阻塞状态
        Socket socket = serverSocket.accept();
        //3,
        InputStream inputStream = socket.getInputStream();
        //3.1因为字节流只能传递字节,不能接收中文,所以我们使用转换流来接收
        InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
        //3.2我们使用字符缓存流
        BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
        //通过readLine获取客户端输出第一行
        System.out.println(bufferedReader.readLine());
        //4 使用输出流发送给客户端数据

        //5 关闭流
        bufferedReader.close();
        socket.close();
        serverSocket.close();

    }
}

客户端

package 网络编程.TCP编程;

import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.Socket;

public class 客户端 {
    public static void main(String[] args) throws IOException {
        /**
         * 客户端开发步骤
         * 1.创建socket对象执行ip+端口
         * 2.通过输出流发送给服务器数据
         * 3.通过输入流获取服务器发送的数据
         * 4.释放资源
         */
        //1
        Socket server = new Socket("localhost", 8899);
        //2
        OutputStream outputStream = server.getOutputStream();
        //2.1
        BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream));
        bufferedWriter.write("你好服务器");
        //3
        //4
        bufferedWriter.close();
        server.close();
    }
}

结果

服务器image-20220219180218988

客户端image-20220219180228018

案例二

客户端向服务器发送一个文件

服务器

package 网络编程.TCP编程.案例二;

import java.io.BufferedInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class 服务器 {
    public static void main(String[] args) throws Exception {
        //1通过serverSocket建立客户端链接的对象
        ServerSocket serverSocket = new ServerSocket(8899);
        //2通过accept等待客户端链接
        Socket socket = serverSocket.accept();
        //3接收客户端发送的数据文件
        InputStream inputStream = socket.getInputStream();
        //创建字节输出流,输出到我们硬盘上
        FileOutputStream fileOutputStream = new FileOutputStream("C:\\Users\\杨贵强\\Desktop\\JavaSE\\aa.png");
        //我们一边读取客户端发送的字节,一边通过字节输出流写到我们硬盘上
        int count=0;
        byte[] bytes = new byte[1024];
        while((count=inputStream.read(bytes))!=-1){
            fileOutputStream.write(bytes,0,count);
        }


        fileOutputStream.close();
        inputStream.close();
        socket.close();
        serverSocket.close();
    }
}

客户端

package 网络编程.TCP编程.案例二;

import java.io.*;
import java.net.Socket;

public class 客户端 {
    public static void main(String[] args) throws IOException {
        Socket socket = new Socket("localhost", 8899);
        OutputStream outputStream = socket.getOutputStream();
        FileInputStream fileInputStream = new FileInputStream("C:\\Users\\杨贵强\\Desktop\\JavaSE\\asdad.png");
        //一边读文件字节一边发送给服务器
        int count;
        byte[] bytes = new byte[1024];
        while ((count=fileInputStream.read(bytes))!=-1){
            outputStream.write(bytes,0,count);
        }

        fileInputStream.close();
        outputStream.close();
        socket.close();
    }
}

反射

image-20220219193031676

什么是类的对象

image-20220219193927867

image-20220219193439467

image-20220219193840404

获取类对象三种方式

image-20220219194226379

推荐使用第三种方式,因为前面两种依赖性很强,如果没有这个类就直接报错。

package 反射;

public class Demo1 {
    public static void main(String[] args) throws ClassNotFoundException {
        //获取类对象
        //1 通过new对象.class获取
        Student student = new Student();
        Class<?> s1 = student.getClass();
        //打印这个类的hashcode
        System.out.println(s1.hashCode());
        //2,通过类名的class
        Class<?> s2= Student.class;
        System.out.println(s2.hashCode());
        //3通过class.forName获取,包名.类名
        Class<?> a3 = Class.forName("反射.Student");
        System.out.println(a3.hashCode());
    }
}

反射常用方法

  1. 获取类名
  2. 获取包名
  3. 获取当前类的父类
  4. 获取当前类实现接口(返回的是个数组,因为一个类可能实现有多个接口)
  5. 通过构造方法创建对象
  6. 通过当前对象创建一个实例
  7. 获取当前类对象的方法
  8. 获取字段或属性

image-20220220192309794

package 反射;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class Demo1 {
    public static void main(String[] args) throws Exception{
        
        Class<?> aClass = Class.forName("反射.Student");
        Student Student = (Student)aClass.newInstance();
        //这里调用set方法赋值
        day4(Student,"setName",new Class[]{String.class},"星辰");
        //这里直接调用对象的toString方法即可
        System.out.println(Student.toString());
    }

    //三种获取类对象的方法
    public static void day1() throws Exception{
        Class<?> student = Class.forName("反射.Student");
        //获取类名
        System.out.println(student.getName());
        //获取包名
        System.out.println(student.getPackage());
        //获取当前类的父类
        System.out.println(student.getSuperclass());
        //获取当前类实现的接口
        System.out.println(student.getInterfaces());
    }
    //通过反射调用构造器创建对象
    public static void day2() throws Exception{
        Class<?> student = Class.forName("反射.Student");
//        方法一
        Constructor con=student.getConstructor();
        Student ss = (Student) con.newInstance();
        System.out.println(ss.toString());
//        方法二
        Student st = (Student) student.newInstance();
        st.setName("武汉");
        st.setAge(18);
        System.out.println(st.toString());
//        通过带参构造方法创建对象
        Constructor con1=student.getConstructor(String.class,int.class);
        Student ss1 = (Student) con.newInstance("形成", 10);
        System.out.println(ss1.toString());
    }
    //通过反射获取类中的方法,并调用方法
    public static void day3() throws Exception{
        //getmethods只能获取公共的方法和继承父类的方法
//        Method[] methods = student.getmethods();
//        for (Method method : methods) {
//            System.out.println(method);
//        }
        //getDeclaredMethods获取的是类中的所有方法(包括私有)不包含继承父类方法
        Class<?> student = Class.forName("反射.Student");
//        Method[] methods = student.getDeclaredMethods();
//        for (Method method : methods) {
//            System.out.println(method);
//        }

        //获取带参方法eat
        //获取类中的方法,getDeclaredMethod(方法名称,方法参数类型)
        Method eat = student.getDeclaredMethod("eat", String.class);
        //创建类对象
        Student student1 = (Student) student.newInstance();
        //调用方法.invoke,传递对象和参数
        eat.invoke(student1,"sd");

        //获取私有方法
        Method cat = student.getDeclaredMethod("cat", int.class);
        //创建类对象
        Student student2 = (Student) student.newInstance();
        //因为cat方法是私有的,所以必须使用setAccessible改为true,表示设置私有修饰符无效
        cat.setAccessible(true);
        //如果不将修饰符改为无效,那么就会抛出异常
        cat.invoke(student2,18);
    }
    //通过反射实现一个调用任何对象的一个工具类,参数一:对象,参数二:方法名称,
    public static Object day4(Object object,String methods,Class<?>[] classes,Object... msg) throws Exception{
        //获取类对象
        Class<?> aClass = object.getClass();
        //通过类对象获取方法
        Method declaredMethod = aClass.getDeclaredMethod(methods, classes);
        //最后调用类对象.invoke方法
        return declaredMethod.invoke(object,msg);
    }
    //通过反射获取对象中的属性
    public static void day5() throws Exception{
        Class<?> aClass = Class.forName("反射.Student");
        Student student =(Student) aClass.newInstance();

        //获取public修饰的全部属性,包括父类继承的
        Field[] fields = aClass.getFields();
        //获取全部属性,包括私有,默认,公共,不包含父类继承
        Field[] declaredFields = aClass.getDeclaredFields();
        for (Field de:declaredFields) {
            System.out.println(de);
        }
        //获取特定属性并赋值
        Field name = aClass.getDeclaredField("name");
        //设置对象修饰符无效
        name.setAccessible(true);
        //参数(操作对象,属性值)
        name.set(student,"xc");
        //打印(对象)
        System.out.println(name.get(student));
    }
}


设计模式

image-20220221104818167

工厂模式

就是创建这个工厂类的时候传递一个class.forname就可以直接获取到这个类中的方法

image-20220221104841423

Usb

package 反射.工厂设计模式;

public interface Usb {
    void service();
}

键盘

package 反射.工厂设计模式;

public class 键盘 implements Usb{
    @Override
    public void service() {
        System.out.println("键盘启动");
    }
}

鼠标

package 反射.工厂设计模式;

public class 鼠标 implements Usb{
    @Override
    public void service() {
        System.out.println("鼠标启动");
    }
}

工厂类

package 反射.工厂设计模式;

public class 工厂类 {
    public Usb facy(String type){//
        Usb usb=null;
        try {
            //返回类对象
            Class<?> classs = Class.forName(type);
            return usb = (Usb) classs.newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return usb;
    }
}

测试

package 反射.工厂设计模式;

public class Test {
    public static void main(String[] args) {
        工厂类 gc = new 工厂类();
        Usb facy = gc.facy("反射.工厂设计模式.鼠标");
        facy.service();
    }
}

这里我们如果在创建一个对象,我们就不用修改其他代码,直接在客户端修改你想要的对象即可

单例模式

只允许创建一个该类对象

image-20220221143137379

饿汉模式

优点:线程安全,生命周期长类加载的时候就有了,

缺点:浪费内存空间

package 反射.单例设计模式;
/*
1.首先创建一个常量
2.构造器改为私有的,类外部不能直接创建对象
3.通过一个公开的方法,返回这个对象
*/

public class ehan {
    //饿汉:在初始化的时候就创建对象了
    private static final ehan Instance =new ehan();
    //将无参构造私有化,这样在外部就不能直接new对象
    private ehan(){};
    //给外部开放一个公共静态的方法,直接调用这个方法返回值就是这个对象
    public static ehan getInstance(){
        return Instance;
    }
}

image-20220221145840453

懒汉模式

优点:生命周期短,需要这个类的时候在创建,不会浪费空间

缺点:线程不安全,需要加sychronized锁

package 反射.单例设计模式;
/*
1.首先创建一个对象,赋值为null
2.构造方法改为私有,类外部不能创建对象
3.通过一个公开的方法,返回这个对象
*/
public class lanhan {
    //懒汉设计模式,用这个对象的时候在创建
    private static lanhan instance = null;
    private lanhan(){};
    //这里要加锁,防止两个线程同时进行赋值
    public static synchronized lanhan getlanhan(){
        //这里判断当前这个instance有没有被赋值对象
            if (instance==null){
                instance= new lanhan();
        }
            return instance;
    }
}

全能写法

package 反射.单例设计模式;

/**
 * 使用静态内部类写法
 * 既有饿汉式优点,也有懒汉式优点
 * 我们new的时候就是一个线程安全的写法
 */
public class quannneg {
    private quannneg(){};
    //这里静态内部类并不是跟我们主类一起执行的,
    //我们在使用的内部类的时候,才会执行内部类里面的代码
    private static class quan{
       static quannneg q=new quannneg();
    }
    public static quannneg neng(){
        return quan.q;
    }
}

枚举

枚举是一种引用类型

image-20220221153634211

package 反射.枚举;

public enum meiju {
    //枚举中必须要定义常量,并且在最前面
    WUHAN,BEIJING,QINGDAO;
    //枚举中也可以定义属性,方法,私有构造器。
//    private String name;
//    private meiju(){};
//    public static void c(){};
//    public void ss(){};
}

测试

image-20220221161108041

枚举集合switch匹配语句

package 反射.枚举;

public class Test {
    public static void main(String[] args) {
        meiju meiju= 反射.枚举.meiju.WUHAN;
        switch (meiju) {
            case WUHAN:
                System.out.println("武汉");
                break;
            case BEIJING:
                System.out.println("北京");
                break;
        }
    }
}

枚举类是本质

image-20220221161046943

注解

注解的本质就是接口,他继承了Annotation,它里面定义的属性也变成了方法

image-20220221162058760

image-20220221162540625

定义注解

注意:我们通过反射获取自定义注解属性信息的时候,必须要使用元注解提高我们自定义注解的生命周期的

注解

package 反射.注解;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
//提升注解生命周期,class表示运行的时候,jvm不会保留,runtime表示jvm会保留,可以通过反射获取该注解属性,source:编译时直接丢弃
@Retention(value = RetentionPolicy.RUNTIME)
public @interface Yyy {
    String name();
    int age();
}

主机使用方法

@Yyy(name="xc",age=18)
public static void show(String name,int age){
    System.out.println(name+"==="+age);
}

通过反射获取注解信息

package 反射.注解;

import java.lang.reflect.Method;

public class Test {
    public static void main(String[] args) throws Exception{
        //获取类对象
        Class<?> aClass = Class.forName("反射.注解.Demo");
        //获取类中的方法
        Method show = aClass.getMethod("show", String.class, int.class);
        //通过类中的方法获取反射对象
        Yyy annotation = show.getAnnotation(Yyy.class);
        //打印发射中的属性,这里报空指针是因为注解在加载的时候作用域太小,导致加载后就直接失效
        System.out.println(annotation.name());
        System.out.println(annotation.age());
    }
}

元注解:提高注解生命周期

image-20220221165936998

我们还可以使用@Target表示当前注解可以使用在哪里,type:方法上面,package:包上面,class:类上面

image-20220221170053039