java基础-8


135~final

多线程

什么是多线程

线程是操作系统能够尽心运算调度的最小单位。他被包含在进程之中,是进程中的实际运作单位
简单理解:应用软件中互相独立,可以同时运行的功能

进程:
进程是程序的基本执行实体

多线程的应用场景

记载大量的资源文件
拷贝迁移大文件
软件中的耗时操作
所有的聊天软件
所有的后台服务器

小总结

1、什么是多线程
有了多线程,我们就可以让程序同时做很多事情

2、多线程的作用
提高效率

3、多线程的应用场景
只要你想让多个事情同时运行就需要用到多线程
比如:软件中的耗时操作、所有的聊天软件、所有的服务器

并发和并行

并发:在同一时刻,有多个指令在单个cpu上交替执行
并行:在同一时刻,有多个指令在cpu上同时执行

并发

强调交替执行
一边抽烟一边喝可乐
嘴巴只有一张,所有抽烟和喝可乐只能执行一个,两者交替进行

并行

cpu有2核4线程,4核8线程,4核8线程,8核16线程,32核64线程
这里面的线程表示的就是能够同时进行的线程

多线程的实现方式

1、继承Thread类的实现方式
2、实现Runnable接口的方式进行实现
3、利用Callable接口和Futrue接口方式实现

关于Thread类

线程是程序中的执行线程。Java虚拟机允许应用程序并发地运行多个线程
每一个Thread类都是一个并发的线程
创建新执行线程有两种方法。
一种方法是将类声明为Thread的子类。该子类应重写Thread类的run方法。接下来可以分配并运行该子类的实例。

package demo;

import java.util.Scanner;

public class Test {
    public static void main(String[] args) {
        //多线程的第一种启动方式:
        //1、自己定义一个类继承Thread
        //2、重写run方法
        //3、创建子类的对象,并启动线程
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        t1.setName("多线程1");
        t2.setName("多线程2");
        t1.start();t2.start();
    }
}

package demo;

public class MyThread extends Thread{
    @Override
    public void run() {
        //书写线程要执行代码
        for (int i = 0; i < 100; i++) {
            System.out.println(getName()+"Hello World");
        }
    }
}

关于Runable接口

创建线程的另一种方法是声明实现Runable接口的类。该类然后实现run方法。然后可以分配该类的实例,在创建Thread时作为一个参数来传递并启动。

package demo;

import java.util.Scanner;

public class Test {
    public static void main(String[] args) {
        //多线程的第二种启动方式
        //1、自己定义一个类实现Runnable接口
        //2、重写里面的方法
        //3、创建自己的类的对象
        //4、创建一个Thread类的对象,并开始线程

        //创建MyRun的对象
        //表示多线程要执行的任务
        MyRun mr = new MyRun();
        //创建线程对象
        Thread t1 = new Thread(mr);
        Thread t2 = new Thread(mr);
        //给线程设置名字
        t1.setName("线程1");
        t2.setName("线程2");
        //开启线程
        t1.start();
        t2.start();
    }
}

package demo;

public class MyRun implements Runnable{

    @Override
    public void run() {
        //书写线程要执行的代码
        for (int i = 0; i < 100; i++) {
            //获取到当前线程的对象
            System.out.println(Thread.currentThread().getName()+"HelloWorld!");
        }
    }
}

关于Callable接口和Future接口

特点:可以获取到多线程运行的结果

1、创建一个类MyThread实现Callable
2、重写call(是由返回值的,表示多线程运行的结果)
3、创建MyCallable的对象(表示多线程要执行的任务)
4、创建FutureTask的对象(作用管理多线程运行的结果)
5、创建Thread类的对象,并启动(表示线程)

package demo;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;

public class Test {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //创建MyCallable的对象(表示多线程要执行的任务)
        MyCallable mc = new MyCallable();
        //创建FutureTask的对象(作用管理多线程运行的结果)
        FutureTask<Integer> ft = new FutureTask<>(mc);
        //创建线程的对象
        Thread t1 = new Thread(ft);
        //启动线程
        t1.start();

        //获取多线程运行的结果
        Integer result = ft.get();
        System.out.println(result);
    }
}

package demo;

import java.util.concurrent.Callable;

public class MyCallable implements Callable<Integer> {

    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for (int i = 0; i <= 100; i++) {
            sum+=i;
        }
        return sum;
    }
}

多线程三种实现方式对比

继承Thread类
优点:编程比较简单,可以直接使用Thread类中的方法
缺点:可以拓展性差,不能再继承其他类

实现Runable接口
优点:拓展性强,实现该接口的同时还可以继承其他的类
缺点:编程相对复杂,不能直接使用Thread类中的方法

实现Callable接口
优点:拓展性强,实现该接口的同时还可以继承其他的类
缺点:编程相对复杂,不能直接使用Thread类中的方法

常用方法

String getName()//返回此线程的名称
void setName(String name)//设置线程的名字(构造方法也可以设置名字)
static Thread currentThread()//获取当前线程的对象
static void sleep(long time)//让线程休眠指定时间,单位为毫秒
setPriority(int newPriority)//设置线程的优先级
final int getPriority()//获取线程的优先级
final void setDaemon(boolean on)//设置为守护线程(备胎线程)
public static void yield()//出让线程/礼让线程
public static void join()//插入线程/插队线程

细节:如果没有给线程设置名字,线程也是有默认的名字的
格式:Thread-X(X序号,从0开始的)
Thread构造方法也可以设置Thread的名字

public class Test {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        MyThread t1 = new MyThread("飞机");
        MyThread t2 = new MyThread("坦克");
        t1.start();
        t2.start();
    }
}
public class MyThread extends Thread {
    public MyThread() {
    }

    public MyThread(String name) {
        super(name);
    }
    public void run(){
        for (int i = 0; i < 100; i++) {
            System.out.println(getName()+"@"+i);
        }
    }
}

关于sleep细节:
哪条线程执行到这个方阿飞,那么哪条线程就会在这里停留对应的时间
方法的参数:就表示睡眠的时间,单位毫秒
1秒等于1000毫秒
当时间到了以后,线程会自动的醒来,继续执行下面的其他代码

线程的优先级

about调度
抢占式调度:抢夺cpu的执行权(就是个随机,谁抢到算谁的,时间长短也随机
非抢占式调度

java中是抢占式调度
而优先级越大,那么抢到的几率就一样

MyRunnable mr = new MyRunnable();
Thread t1 = new Thread(mr,"");
Thread t2 = new Thread(mr,"");
t1.setProiority(1);
t2.setProiority(1);
t1.start();
t2.start();

守护线程(备胎线程)

final void setDaemon(boolean on)
MyThread t1 = new MyThread1();
MyThread t2 = new MyThread2();
t1.setName("女神");
t2.setName("备胎");
//把第二个线程设置为备胎线程
t2.setDaemon(true);
t1.start();
t2.start();

细节:
当其他的非守护线程执行完毕之后,守护线程会陆续结束(不是直接结束)
通俗易懂
当女神进程结束了,那么备胎也没有存在的必要了

礼让线程

//main函数
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
t1.setName("飞机");
t2.setName("坦克");
t1.start();
t2.start();
//MyThread
public void run(){
	for (int i = 0; i < 100; i++) {
        System.out.println(getName()+"@"+i);
        //表示出让当前CPU执行权,尽可能让结果平均一点
        Thread.yield();
    }
}

插入线程

MyThread t1 = new MyThread("土豆");
t1.start();
t1.join();
for (int i = 0; i < 10; i++) {
    System.out.println("main线程"+i);
}

小细节:join要放在start的后面才能插队

线程的生命周期

创建线程对象(新建状态,小宝子)----start()----->有执行资格但是没有执行权(就绪状态,不停的抢CPU,)------抢到CPU的执行权------>有执行资格并且有执行权(运行状态,线程运行代码)-----run()执行完毕---->线程死亡,变成垃圾(死亡状态)
备注:如果还是不能理解,可以购买《码农翻身》,个人推荐,感觉不错,讲故事一样描述电脑硬件各部分功能

如果被其他线程抢走CPU的执行权,那么运行状态的线程会重新返回就绪状态(有执行资格没有执行权)

如果在运行状态时遇到了sleep()或者其他的阻塞式方法,就会变成没有执行资格也没有执行权的阻塞状态(给爷等着),阻塞状态结束会重新变成就绪状态去抢进程

问:sleep方法会让线程睡眠,睡眠时间到了之后,立马就会执行下面的代码吗?
no,no,no,睡眠结束后是就绪状态不是运行状态,得重新抢到执行权才会执行下面的代码

线程的安全问题&同步代码块

当多个线程操作同一个数据的时候,会出现问题
问题1:相同的票出现了多次
问题2:出现了超出范围的票
原因1:线程在执行代码的时候,CPU的执行全随时有可能会被其他线程抢走
线程执行时,有随机性
原因2:线程执行的时候有随机性,CPU的执行权有可能会被其他线程抢走

所以俺们要把操作共享数据的代码锁起来——synchronized(锁对象)
特点一:锁默认时打开的,有一个线程进去了,锁自动关闭
特点二:里面的代码全部执行完毕,线程出来,所自动打开

static int ticket = 0;
static Object obj = new Object();
@Overrride
public void run(){
	while(true){
		//同步代码块
		synchronized(obj){
			if(ticket < 100){
				try{
					Thread.sleep(100);
				}catch (InterruptedException e){
					e.printStackTrace();
				}
				ticket++;
				System.out,println(getName()+"正在卖第"+ticket+"张票!!!");
			}else{
				break;
			}
		}
	}
}

有关同步代码快的小细节

synchronized不能写在循环外面
不然就是一个线程把全部的票卖完了才出来
synchronized的锁对象一定是唯一的
一般的锁会写成当前类的字节码文件

同步方法

同步方法就是把synchronized关键字加到方法上

格式
修饰符 synchronized 返回值类型 方法名 (方法参数){…}
特点一:同步方法是锁住方法里面所有的代码
特点二:锁对象不能自己指定(非静态:this,静态:当前类的字节码文件对象)

public class MyRunnable implements Runnable{
	int ticket = 0;
	@Override
	public void run(){
		//1、循环
		while(true){
			//2、同步代码块(同步方法)
			synchronized(MyRunnable.class){
				//3、判断共享数据是否到了末尾,如果到了末尾
				if(ticket == 100){
					break;
				}else{
					//4、判断共享数据是否到了末尾,如果没有到末尾
					ticket++;
					System.out.println(Thread.currentThread().getName()+"在卖第"+ticket+"张票!!!");
				}
			}
		}
	}
}

当不需要考虑多线程当中数据安全的情况的就用StringBuilder
当是多线程环境下,需要考虑到数据安全就选择stringBuffer

lock

虽然我们理解同步代码块和同步方法的锁对象问题
但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,
为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象lock

lock是西安提供比synchronized方法和语句可以获得更广泛的锁定操作(手动上锁和释放锁)
lock中提供了获得锁和释放锁的方法
void lock():获得锁
void unlock():释放锁

lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化
ReentrantLock的构造方法
ReentrantLock():创建一个ReentrantLock的实例

public class MyThread extends Thread{
	static int ticket = 0;
	static Lock lock = new ReentrantLock();
	@Override
	public void run(){
		while(true){
			try{
				if(ticket == 100){
					break;
				}else{
					Thread.sleep(10);
					ticket++;
					System.out.println(getName()+""+ticket+"");
				}
			} catch(InterruptedException e){
				e.printStackTrace();
			}finally{
				lock.unlock();
			}
		}
}

死锁(这是一种错误)

public class MyThread extends Thread {
    static Object obja = new Object();
    static Object objb = new Object();
    @Override
    public void run(){
        while(true){
        if ("线程A".equals(getName())){
            synchronized (obja){
                System.out.println("线程A拿到了A锁,准备拿B锁");
                synchronized (objb){
                    System.out.println("线程A拿到了B锁,顺利执行玩一轮");
                }
            }
        }else if ("线程B".equals(getName())){
            synchronized (objb){
                System.out.println("线程B拿到了B锁,准备拿A锁");
                synchronized (obja){
                    System.out.println("线程B拿到了A锁,顺利执行玩一轮");
                }
            }
        }
        }
    }
}

生产者和消费者(等待唤醒机制)

生产者消费者是一个十分静待你的多线程协作的模式
消费者——消费数据
生产者——生产数据
核心思想:利用桌子来控制线程的执行

吃货是消费者,厨师是生产者
桌子上有吃的就是消费者去吃
桌子上没吃的就是厨师去烧菜

理想状态就是先是厨师抢到CPU执行权,做了一碗吃的,然后吃货来吃,吃货吃一碗,厨师做一碗

两种情况可能会出现(现实版)
1、消费者等待
一开始不是厨师抢到执行权而是吃货抢到,桌子上没东西(桌子不能吃),所以吃货只能等待,那么执行权就会被厨师抢到,做完以后厨师会唤醒在等待的吃货去吃
消费者:1、判断桌子上是否有食物,2、如果没有就等待
生产者:1、制作食物,2、把食物放在桌子上,3、叫醒等待的消费者开吃

2、生产者等待
第一时间是厨师抢到CPU执行权,然后做好吃的。
第二次还是厨师抢到CPU的执行权,因为桌子上有吃的,所以不会去做吃的,会”喊一嗓子“,进入等待状态,等待吃货抢到CPU执行权,吃掉桌子上的东西以后才会继续让厨师运作起来
消费者:1、判断桌子上是否有食物,2、如果没有就等待,3、如果有就开吃,4、吃完之后,唤醒厨师继续做
生产者:1、判断桌子上是否有食物,2、有:等待,3、没有:制作食物,4、把食物放在桌子上,5、叫醒等待的消费者开吃

void wait()//当前线程等待,知道被其他线程唤醒
void notify()//随机唤醒单个线程
void notifyAll()//唤醒所有线程

消费者和生产者代码实现

消费者

public class Foodie extends Thread{
    @Override
    public void run() {
        //1、循环
        //2、同步代码块
        //3、判断共享数据是否到了末尾(到了末尾)
        //4、判断共享数据是否到了末尾(没有到末尾,执行核心逻辑)
        while(true){
            synchronized (Desk.lock){
                if(Desk.count == 0){
                    break;
                }else{
                    //先判断桌子上是否有面条
                    if (Desk.foodFlag == 0){
                        //如果没有,就等待
                        try {
                            Desk.lock.wait();//让当前线程跟锁进行绑定
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }else{
                        //把吃的总数-1
                        Desk.count--;
                        //如果有,就开吃
                        System.out.println("吃货在吃面条,还能再炫"+Desk.count+"碗!!!");
                        //吃完之后,唤醒厨师继续做
                        Desk.lock.notifyAll();
                        //修改桌子的状态
                        Desk.foodFlag = 0;
                    }
                }
            }
        }
    }
}

生产者

public class Cook extends Thread{
    @Override
    public void run() {
        //1、循环
        //2、同步代码快
        //3、判断共享数据是否到了末尾(到了末尾)
        //4、判断共享数据是否到了末尾(没有到末尾,执行核心逻辑)
        while (true){
            synchronized (Desk.lock){
                if(Desk.count == 0){
                    break;
                }else{
                    //判断桌子上是否有食物
                    if (Desk.foodFlag == 1){
                        //如果有,就等待
                        try {
                            Desk.lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }else {
                        //如果没有,就制作食物
                        System.out.println("厨师做了一碗面条");
                        //修改桌子上的食物状态
                        Desk.foodFlag = 1;
                        //叫醒等待的消费者开吃
                        Desk.lock.notifyAll();
                    }
                }
            }
        }
    }
}

桌子

public class Desk {
    //作用:控制生产者和消费者的执行
    public static int foodFlag = 0;//为什么是int类型而不是bool类型,因为bool类型只能控制两种情况,而int就能控制多个了
    //总个数
    public static int count = 10;
    //锁对象
    public static Object lock = new Object();

}

测试运行

public static void main(String[] args) {
    Cook c = new Cook();
    Foodie f = new Foodie();
    c.setName("厨师");
    f.setName("吃货");
    c.start();
    f.start();
}

阻塞队列方式实现

请添加图片描述

阻塞队列的继承结构

请添加图片描述

需求:利用阻塞队列完成生产者和消费者(等待唤醒机制)的代码
细节:生产者和消费者必须使用同一个阻塞队列

import java.util.concurrent.ArrayBlockingQueue;

public class Cook extends Thread{
    ArrayBlockingQueue<String> queue;

    public Cook(ArrayBlockingQueue<String> queue) {
        this.queue = queue;
    }

    @Override
    public void run() {
        while (true){
            //不断地把面条放到阻塞队列当中
            try {
                queue.put("面条");
                System.out.println("厨师放了一碗面条");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
    }
}

import java.util.concurrent.ArrayBlockingQueue;

public class Foodie extends Thread{
    ArrayBlockingQueue<String> queue;

    public Foodie(ArrayBlockingQueue<String> queue) {
        this.queue = queue;
    }
    @Override
    public void run() {
        while (true){
            //不断地从阻塞队列当中获取面条
            try {
                String food = queue.take();
                System.out.println(food);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
    }
}

public static void main(String[] args) {
    //1、创建阻塞队列
    ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(1);
    //2、创建线程的对象,并把阻塞队列传递过去
    Cook c = new Cook(queue);
    Foodie f =new Foodie(queue);
    c.start();
    f.start();
}

线程的六种状态

请添加图片描述

java没有定义运行状态,理解时添加的

新建状态(NEW)———>创建线程对象
就绪状态(RUNNABLE)———>start方法
阻塞状态(BLOCKED)———>无法获得锁对象
等待状态(WAITING)———>wait方法
计时等待(TIMED_WAITING)———>sleep方法
结束状态(TERMINATED)———>全部代码运行完毕

综合小练习

抢红包

假设:100块钱,分为了三个包,现在有五个人去抢
其中,红包是共享数据.
五个人是五条线程
打印结果如下:
XXX抢到了XXX元
XXX抢到了XXX元
XXX抢到了XXX元
XXX没抢到
XXX没抢到

public class MyThread extends Thread {
    //共享数据
    //100块,分了三个包
    static double money = 100;
    static int count = 3;
    static final double MIN = 0.01;

    @Override
    public void run() {
        //同步代码块
        synchronized (MyThread.class){
            if (count == 0){
                //判断,共享数据是否已经到了末尾(已经到了末尾)
                System.out.println(getName()+"没有抢到红包!");
            }else {
                //判断,共享数据是否到了末尾(没有到末尾)
                //定义一个变量,表示中奖的金额
                double prize = 0;
                if (count == 1){
                    //表示此时是最后一个红包
                    //就无需随机,剩余所有的钱都是中将金额
                    prize = money;
                }else {
                    //表示第一次,第二次(随机)
                    Random r =new Random();
                    double bounds = money-(count-1)*MIN;
                    prize = r.nextDouble(bounds);
                    if (prize < MIN){
                        prize = MIN;
                    }
                }
                money = money - prize;
                count--;
                System.out.println(getName() + "抢到了" + prize + "元");

            }
        }
    }
}

public static void main(String[] args) throws IOException {
    MyThread t1 = new MyThread();
    MyThread t2 = new MyThread();
    MyThread t3 = new MyThread();
    MyThread t4 = new MyThread();
    MyThread t5 = new MyThread();


    t1.setName("A");
    t2.setName("B");
    t3.setName("C");
    t4.setName("D");
    t5.setName("E");

    t1.start();
    t2.start();
    t3.start();
    t4.start();
    t5.start();
}

抽奖箱抽奖

有一个抽奖池中存放了奖励的金额,该抽奖池中的奖项为{10,20,50,100,200,500,800,2,80,300,700}
创建两个抽奖箱(线程_设置线程名称为抽奖箱1,抽奖箱2
随机从抽奖池中获取获奖元素并打印在控制台上,格式如下:
每次抽出一个奖项就打印一个(随机)
抽奖箱1又产生了一个10元大奖
抽奖箱1又产生了一个100元大奖
抽奖箱1又产生了一个20元大奖
抽奖箱1又产生了一个200元大奖
抽奖箱1又产生了一个500元大奖
抽奖箱2又产生了一个700元大奖

public class MyThread extends Thread {
    ArrayList<Integer> list;
    public MyThread(ArrayList<Integer> list){
        this.list=list;
    }

    @Override
    public void run() {
        while (true){
            synchronized (MyThread.class){
                if (list.size() == 0){
                    break;
                }else {
                    Collections.shuffle(list);
                    int prize = list.remove(0);
                    System.out.println(getName()+"又产生了一个"+prize+"元大奖");
                }
            }
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}
public class Text {
    public static void main(String[] args) throws IOException {
        ArrayList<Integer> list = new ArrayList<>();
        Collections.addAll(list,10,5,20,50,100,200,500,800,2,80,300,700);
        MyThread t1 = new MyThread(list);
        MyThread t2 = new MyThread(list);
        t1.setName("抽奖箱1");
        t2.setName("抽奖箱2");
        t1.start();
        t2.start();
    }
}

多线程统计并求出最大值

在上题基础上继续完成如下需求:
每次抽的过程中,不打印,抽完时一次性打印(随机)
在此次抽奖过程中,抽奖箱1总共产生了六个奖项
分别为10,20,100,500,2,300最高奖项为300元,总计额为932元
在此次抽奖过程中,抽奖箱2总共产生了六个奖项
分别为5,5,200,800,80,700最高奖项为800元,总计额为1835元

第一种写法

public class Text {
    public static void main(String[] args) throws IOException {
        ArrayList<Integer> list = new ArrayList<>();
        Collections.addAll(list,10,5,20,50,100,200,500,800,2,80,300,700);
        MyThread t1 = new MyThread(list);
        MyThread t2 = new MyThread(list);
        t1.setName("抽奖箱1");
        t2.setName("抽奖箱2");
        t1.start();
        t2.start();
    }
}
public class MyThread extends Thread {
    ArrayList<Integer> list;
    public MyThread(ArrayList<Integer> list){
        this.list=list;
    }
    ArrayList<Integer> list1 = new ArrayList<>();
    ArrayList<Integer> list2 = new ArrayList<>();
    @Override
    public void run() {
        while (true){
            synchronized (MyThread.class){
                if (list.size() == 0){
                    if ("抽奖箱1".equals(getName())){
                        System.out.println("抽奖箱1"+list1);
                    }else {
                        System.out.println("抽奖箱2"+list2);
                    }
                    break;
                }else {
                    Collections.shuffle(list);
                    int prize = list.remove(0);
                    if ("抽奖箱1".equals(getName())){
                        list1.add(prize);
                    }else {
                        list2.add(prize);
                    }
                }
            }
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

第二种方式

public class Text {
    public static void main(String[] args) throws IOException {
        ArrayList<Integer> list = new ArrayList<>();
        Collections.addAll(list,10,5,20,50,100,200,500,800,2,80,300,700);
        MyThread t1 = new MyThread(list);
        MyThread t2 = new MyThread(list);
        t1.setName("抽奖箱1");
        t2.setName("抽奖箱2");
        t1.start();
        t2.start();
    }
}
public class MyThread extends Thread {
    ArrayList<Integer> list;
    public MyThread(ArrayList<Integer> list){
        this.list=list;
    }

    @Override
    public void run() {
        ArrayList<Integer> boxlist =new ArrayList<>();
        while (true){
            synchronized (MyThread.class){
                if (list.size() == 0){
                    System.out.println(getName()+boxlist);
                    break;
                }else {
                    Collections.shuffle(list);
                    int prize = list.remove(0);
                    boxlist.add(prize);
                }
            }
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

多线程之间的比较

在此次抽奖过程中,抽奖箱1总共产生了六个奖项
分别为10,20,100,500,2,300
最高奖项为300元,总计额为932元
在此次抽奖过程中,抽奖箱2总共产生了六个奖项
分别为5,5,200,800,80,700
最高奖项为800元,总计额为1835元
在此次抽奖过程中,抽奖箱2中产生了最大奖项,该奖项金额为800元

public class Text {
    public static void main(String[] args) throws IOException, ExecutionException, InterruptedException {
        ArrayList<Integer> list = new ArrayList<>();
        Collections.addAll(list,10,5,20,50,100,200,500,800,2,80,300,700);
        MyCallable mc = new MyCallable(list);
        FutureTask<Integer> ft1 =new FutureTask<>(mc);
        FutureTask<Integer> ft2 =new FutureTask<>(mc);
        Thread t1 = new Thread(ft1);
        Thread t2 = new Thread(ft2);
        t1.setName("抽奖箱1");
        t2.setName("抽奖箱2");
        t1.start();
        t2.start();
        Integer max1 = ft1.get();
        Integer max2 = ft2.get();
        System.out.println(max1);
        System.out.println(max2);
    }
}

public class MyCallable implements Callable<Integer> {
    ArrayList<Integer> list;
    public MyCallable(ArrayList<Integer> list){
        this.list=list;
    }

    @Override
    public Integer call() throws Exception {
        ArrayList<Integer> boxlist =new ArrayList<>();
        while (true){
            synchronized (MyCallable.class){
                if (list.size() == 0){
                    System.out.println(Thread.currentThread().getName()+boxlist);
                    break;
                }else {
                    Collections.shuffle(list);
                    int prize = list.remove(0);
                    boxlist.add(prize);
                }
            }
            Thread.sleep(10);
        }
        if(boxlist.size() == 0){
            return null;
        }else {
            return Collections.max(boxlist);
        }
    }
}

线程池

以前写多线程的弊端
弊端1:用到线程的时候就创建
弊端2:用完之后线程消失
浪费了系统的资源

线程池主要核心原理
1、创建一个池子,池子中是空的。
2、提交任务时,池子会创建新的线程对象,任务执行完毕,线程归还给池子,下回再次提交任务时,不需要创建新的线程,直接复用已有的线程即可
3、但是如果提交任务时,池子中没有空闲线程,也无法创建新的线程,任务就会排队等待。

线程池的代码实现
1、创建线程池
2、提交任务
3、所有的任务全部执行完毕,关闭线程池

Executors:线程池的工具类通过调用方法返回不同类型的线程池对象。

public static ExecutorService newCachedThreadPool()//创建一个没有上限的线程池
public static ExecutorService new FixedThreadPool(int nThreads)//创建有上限的线程池
//1、获取线程池对象
ExecutorService pool1 = Executors.newCachedThreadPool();
//2、提交任务
pool1.submit(new MyRunnable());
//3、销毁线程池
pool1.shutdown();
public class MyRunnable implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName()+"---"+i);
        }
    }
}

线程池一般不会销毁

自定义线程池解析

饭店里招员工
来一个顾客临时找一个服务员,进行一对一服务
顾客走了,服务员也就辞退了
但是这个时候引进了正式员工
就算没有顾客,正式员工也不会被辞退
但是顾客过多时还是会引入临时员工进行帮忙

核心元素一:正式员工数量
核心元素二:餐厅最大员工数
核心元素三:临时员工空闲多长时间被辞退(值)
核心元素四:临时员工空闲多长时间被辞退(单位)
核心元素五:排队的客户
核心元素六:从哪里招人
核心元素七:当排队人数过多,超出顾客请下次再来(拒绝服务)

细节:
什么时候才会去创建临时线程
核心线程都在忙,而且队伍已经排满了
任务在执行的时候,一定是按照提交的顺序来执行的吗

自定义线程的任务拒绝策略

ThreadPoolExecutor.AbortPolicy//默认策略:丢弃任务并抛出RejectedExecutionException异常
ThreadPoolExecutor.DiscardPolicy//丢弃任务,但是不是抛出异常,这不是推荐的做法
ThreadPoolExecutor.DiscardOldestPolicy//抛弃队列中等待最久的任务,然后把当前任务加入队列中
ThreadPoolExecutor.CallerRunPolicy//调用人物的run()方法绕过线程池直接执行
 ThreadPoolExecutor pool = new ThreadPoolExecutor(
        3,//核心线程数量,能小于0
        6,//最大线程数,不能小于0,最大数量>=核心线程数量
        60,//空闲线程最大存活时间
        TimeUnit.SECONDS,//时间单位,用TimeUnit指定
        new ArrayBlockingQueue<>(3),//任务队列,不能为null
        Executors.defaultThreadFactory(),//创建线程工厂,不能为null
        new ThreadPoolExecutor.AbortPolicy()//任务的拒绝策略,不能为null
);

关于拒绝策略是内部类
因为拒绝策略单独存在没有意义
只有在线程池中才有意义
就像心脏单独存在没有意义,只有在人体中才有意义

线程池小结

1、创建一个空的池子
2、有任务提交时,线程池会创建线程去执行任务,执行完毕归还线程

不断地提交任务,会有以下三个临界点:
1、当线程池满时,再提交任务就会排队
2、当核心线程满,队伍满时,会创建临时线程
3、当核心线程满,队伍满,临时线程满时,会触发任务拒绝策略

线程池的最大并行数

最大并行数和cpu有关
以4核8线程的为例

4核就相当于cpu有4个大脑,在超线程技术的支持下,就能把原本的四个大脑虚拟成8个,这8个就是线程,最大并行数是8

//向Java虚拟机返回可用处理器的数目
int count = Runtime.getRuntime().availableProcessors();
System.out.println(count);

线程池多大合适

项目可以分成两种
CPU密集型运算(计算多,读取录入少):最大并行数+1(万一前面有个线程出问题了,保证CPU的时钟周期不被浪费)

I/O密集型运算9(读取录入多):最大并行数期望CPU利用率总共时间(CPU计算时间+等待时间)/CPU计算时间

网络编程

什么是网络编程?
在网络通信协议下,不同计算机上运行的程序,进行的数据传输
应用场景:即时通信、网络对战、金融证券、国际贸易、邮件、等等
不管是什么场景,都是计算机跟计算机之间通过网络进行数据传输
Java中可以使用java.net包下的技术秦颂开发出常见的网络应用程序。
常见的软件架构:bs和cs

cs:client server(客户端加服务器)
在用户本地需要下载并安装客户端程序,在远程有一个服务器程序。
bs:(浏览器加服务器)
只需要一个浏览器,用户通过不同的网址,客户访问不同的浏览器

bs架构的优缺点:
不需要开发客户端,只需要页面+服务端,开发部署维护都蛮简单
用户不需要下载,打开浏览器就能使用
如果应用过大,用户体验受到影响

cs架构的优缺点
画面可以做的非常精美,用户体验好
需要开发客户端,也需要开发服务端
用户需要下载和更新的时候太麻烦

小总结

1、什么是网络编程?
计算机跟计算机之间通过网络进行数据传输
2、常见软件架构有哪些?
CS/BS
3、通信的软件架构CS/BS的各有什么区别和优缺点、
CS:客户端服务端模式需要开发客户端
BS:浏览器服务端模式不需要开发客户端。
CS:适合定制专业化的办公类软件,如:IDEA、网游
BS:适合移动互联网应用,可以在任何地方随时访问的系统。

网络编程的三要素

1、确定对方电脑在互联网上的地址(IP)
2、确定接收数据的软件(端口号)
3、确定网络传输的规则(协议)

IP:设备在网络中的地址,是唯一的标识
端口号:应用程序在设备中唯一的标识
协议:数据在网络中传输的规则,常见的协议有UDP、TCP、http、ftp

IP

全称Internet Protocol,是互联网协议地址,也称IP地址
是分配给上网设备的数字标签
通俗理解:上网设备在网络中的地址,是唯一的
常见的IP分类为IPv4,IPv6

IPv4
全称:Internet Protocol version 4 ,互联网通信协议第四版(第一版就是第四版,对外发布的第一版)
采用32位地址长度,分成4组
这玩意不够用,才42亿多
2019年11月26日全部分配完毕

IPv6
全称Internet Protocol version 6,互联网通信协议第六版
由于互联网的蓬勃发展,IP地址的需求量越来越大,而IPv4的模式下IP的总数是有限的
采用128位地址长度,分成8组
数量大到能够地球上每一粒沙子一个IP

特殊情况:如果计算出的16进制表示形式中间有多个连续的0
会使用0位压缩表示法

IP小总结

1、IP的作用
设备在网络中的地址,是唯一的标识
2、IPV4有什么忒但
目前的主流方案,最多只有2^32次方个ip,目前已经用完了
3、IPv6有什么特点
为了解决IPv4不够用而出现的
最多有2^128次方个ip
可以为地球上的每一粒沙子都设定ip

IPv4小细节

IPv4的地址分类形式
公网地址(万维网使用)和私有地址(局域网使用)
192.168.开头的就是私有地址,范围即为192.168.0.0–192.168.255.255,专门为组织机构内部使用,以此节省IP

127.0.0.1,也就是localhost:是回送地址也称本地回环地址,也称本机IP,永远只会寻找当前所在本机

常见的CMD命令
ipconfig:查看本机IP地址
ping:检查网络是否连通

InetAddress的使用

InetAddress address = InetAddress.getByName("LAPTOP-2NHBIG5V");
System.out.println(address);
String name = address.getHostName();
System.out.println(name);
String hostAddress = address.getHostAddress();
System.out.println(hostAddress);

端口号

端口号是应用程序在设备中唯一的标识
端口号:有两个字节表示的整数,取值范围:0~65535
其中0~1023之间的端口号用于一些知名的网络服务或者应用(这些端口号是不能用的)
一个端口号只能被一个应用程序使用

协议

计算机网络中,连接和通信的规则被称为网络通信协议
OSI参考模型:世界互联协议标准,全球通信规范,单模型过于理想化,为能在因特网上进行官方推广(太理想化了这玩意)
TCP/IP参考模型(TCP/IP协议):事实上的国际标准
请添加图片描述
OSI参考模型把整个数据的传输分为了七层
应用层,表示层,会话层,传输层,网络层,数据链路层,物理层
我们的代码运用在最上面的应用层,一层层的往下,最终在物理层转成二进制再传给对方的电脑
而对方的电脑会从物理层开始一层一层解析,最后展示再应用层

而TIC/IP将上面的应用层,表示层,会话层合并为一层叫应用层,物理层和物理链路层合并为一层叫做物理链路层,减少了资源的消耗。
每一层都有自己的协议

UDP协议

用户数据报协议
UDP是面向无连接通信协议(正常传送数据,要先检查两台电脑之间是否畅通,链接上了,而这玩意直接发送数据,管你有没有链接,能收到就收到,收不到拉倒)
速度快,有大小限制一次最多发送64K,数据不安全,易丢失数据

应用场景:网络会议,丢一点数据无所大谓,不产生任何的影响
语音通话,看视频

UDP通信程序(发送数据)

小故事:
一个在天涯的人给一个在海角的人寄礼物
1、找快递公司————创建发送端的DatagramSocket对象
2、打包礼物—————数据打包(DatagramPacket)
3、快递公司发送包裹—发送数据
4、付钱走人—————释放资源

//1、创建DatagramSocket对象(快递公司)
//细节:
//绑定端口:以后我们就是通过这个端口往外发送
//空参:所有可用的端口中随机一个进行使用
//有参:指定端口号进行绑定
DatagramSocket ds = new DatagramSocket();
//2、打包数据
String str = "我爱卡卡!!!";
byte[] bytes = str.getBytes();
InetAddress address = InetAddress.getByName("10.200.28.55");
int port = 10086;
DatagramPacket dp = new DatagramPacket(bytes,bytes.length,address,port);
//3、发送数据
ds.send(dp);
//4、释放资源
ds.close();
UDP通信程序(发送数据)

小故事
海角的人要收到天涯的人给她寄的包裹

1、找快递公司——————创建接收端的DatagramSocket对象
2、接受箱子———————接收打包好的数据
3、从箱子里面获取礼物——解析数据包
4、签收走人———————释放资源

//1、创建DatagramSocket对象(快递公司)
//细节:
//在接受的时候,一定要绑定端口号
//而且绑定的端口一定要跟发送的端口保持一致
DatagramSocket ds = new DatagramSocket(10086);

//2、接受数据包
byte[] bytes = new byte[1024];
DatagramPacket dp = new DatagramPacket(bytes,bytes.length);

//该方法是阻塞的
//程序执行到这一步的时候,会在这里四等
//等发送端发送信息
ds.receive(dp);

//3、解析数据包
byte[] data = dp.getData();
int len = dp.getLength();
InetAddress address = dp.getAddress();
int port = dp.getPort();
System.out.println("接收到数据"+new String(data,0,len));
System.out.println("该数据是从"+address+"这台电脑中的"+port+"这个端口发出的");

//4、释放资源
ds.close();

请添加图片描述

小练习——聊天室

按照下面的要求实现程序
UDP发送数据:数据来自于键盘录入,直到输入的数据是886,发送数据结束
UDP接受数据:因为接收端不知道发送端什么时候停止发送,故采用死循环接受

//接收端
//1、创建DatagramSocket对象(快递公司)
DatagramSocket ds = new DatagramSocket(10086);

//2、接受数据包
byte[] bytes = new byte[1024];
DatagramPacket dp = new DatagramPacket(bytes,bytes.length);

while (true) {
    ds.receive(dp);

    byte[] data = dp.getData();
    int length = dp.getLength();
    String ip = dp.getAddress().getHostAddress();
    String name = dp.getAddress().getHostName();

    System.out.println("ip为"+ip+",主机为:"+name+"的人,发送数据:"+new String(data,0,length));
}
//1、创建对象DatagramSocket的对象
DatagramSocket ds= new DatagramSocket();
//2、打包数据
Scanner sc = new Scanner(System.in);
while (true) {
    System.out.println("请输入您想要说的话:");
    String str = sc.nextLine();
    if ("886".equals(str)){
        break;
    }
    byte[] bytes = str.getBytes();

    InetAddress address = InetAddress.getByName("10.200.28.55");
    int port = 10086;
    DatagramPacket dp = new DatagramPacket(bytes,bytes.length,address,port);

    //3、发送数据
    ds.send(dp);
}

//4、释放资源
ds.close();
UDP的三种通信方式

1、单播
一对一
2、组播
一对多(一组)、
组播地址:224.0.0.0~239.255.255.255
其中224.0.0.0~224.0.0.255为预留的组播地址
3、广播
一对全(全部)
广播地址:255.255.255.255

//接收端
//1、创建MulticastSocket对象
MulticastSocket ms = new MulticastSocket(10000);
InetAddress address = InetAddress.getByName("224.0.0.1");
//3、创建DatagramPacket数据包对象
byte[] bytes = new byte[1024];
DatagramPacket dp = new DatagramPacket(bytes,bytes.length);
//4、接受数据
ms.receive(dp);
//5、解析数据
byte[] data = dp.getData();
int length = dp.getLength();
String ip = dp.getAddress().getHostAddress();
String name = dp.getAddress().getHostName();
System.out.println("ip为"+ip+",主机为:"+name+"的人,发送数据:"+new String(data,0,length));
//6、释放资源
ms.close();
//发送端
//创建MulticastSocket对象
MulticastSocket ms = new MulticastSocket();
//创建DatagramSocket对象
String str = "你好你好";
byte[] bytes = str.getBytes();
InetAddress address = InetAddress.getByName("224.0.0.1");
int port = 10000;
DatagramPacket datagramPacket = new DatagramPacket(bytes,bytes.length,address,port);

//调用MulticastSocket发送数据方法发送数据
ms.send(datagramPacket);
//释放资源
ms.close();
TCP协议

传输控制协议
TCP协议是面向链接的通信协议
速度慢,没有大小限制,数据安全
应用场景:文件包下载,文字聊天,发送邮件

TCP通信协议

TCP通信协议是一种可靠的网络协议,它在通信的两端各建立了一个Socket对象
通信之前要保证连接已经建立
痛过Socket产生IO流来进行网络通信

TCP通信
客户端(输出流)
1、创建客户端对象Socket与指定服务端连接(Socket (String host, int port)
2、获取输出流,写数据(OutputStream getOutputStream())
3、释放资源(void close())
服务器(输入流)
1、创建服务器端的Socket对象(ServerSocket)
ServerSocket(int port)
2、监听客户端连接,返回一个Socket对象
Socket accept()
3、获取输入流,读数据,并把数据显示在控制台
InputStream getInputStream()
4、释放资源
void close()

//1、创建Socket对象
//细节:在创建对象的同时会连接服务端
//如果连接不上,代码会报错
Socket socket = new Socket("10.200.28.55",10000);
//2、可以从连接通道中获取输出流
OutputStream os = socket.getOutputStream();
//写出数据
os.write("你好你好".getBytes());
//3、释放资源
os.close();
socket.close();
//1、创建对象ServerSocket
ServerSocket ss = new ServerSocket(10000);
//2、监听客户端的连接
Socket socket = ss.accept();
//3、从连接通道中获取输入流读取数据
InputStream is = socket.getInputStream();
int b;
while((b = is.read()) != -1){
    System.out.print((char) b );
}
//4、释放资源
socket.close();
ss.close();

上述代码在传输中文时会出现乱码,只能传英文
原因是输出流将中文的编码一对一对的输出,但是读入的时候是一个一个的读入输出。所以需要将输入流里面进行更改,如下所示

//1、创建Socket对象
//细节:在创建对象的同时会连接服务端
//如果连接不上,代码会报错
Socket socket = new Socket("10.200.28.55",10000);
//2、可以从连接通道中获取输出流
OutputStream os = socket.getOutputStream();
//写出数据
os.write("你好你好".getBytes());
//3、释放资源
os.close();
socket.close();
//1、创建对象ServerSocket
ServerSocket ss = new ServerSocket(10000);
//2、监听客户端的连接
Socket socket = ss.accept();
//3、从连接通道中获取输入流读取数据
InputStream is = socket.getInputStream();
InputStreamReader isr = new InputStreamReader(is);
int b;
while((b = isr.read()) != -1){
    System.out.print((char) b );
}
//4、释放资源
socket.close();
ss.close();

下面是提高效率版

//1、创建Socket对象
//细节:在创建对象的同时会连接服务端
//如果连接不上,代码会报错
Socket socket = new Socket("10.200.28.55",10000);
//2、可以从连接通道中获取输出流
OutputStream os = socket.getOutputStream();
//写出数据
os.write("你好你好".getBytes());
//3、释放资源
os.close();
socket.close();
//1、创建对象ServerSocket
ServerSocket ss = new ServerSocket(10000);
//2、监听客户端的连接
Socket socket = ss.accept();
//3、从连接通道中获取输入流读取数据
InputStream is = socket.getInputStream();
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);
int b;
while((b = br.read()) != -1){
    System.out.print((char) b );
}
//4、释放资源
socket.close();
ss.close();
代码的小细节

运行的时候要先运行服务端
客户端若是先运行的话,TCP通信开始连接,连接不到对象,就会直接报错
服务端若是先运行的话,到accept开始死等有人来找他这个对象,那么开始启动客户端,客户端连接上了以后就开始了两端的连接读取操作(三次握手协议)

关于结束
那个流是通道里面的,所以我们在释放资源的时候,这个输入输出流是不需要关的,只要把连接通道关了,它里面的流也就断开了(这个流可关可不关)
关于读取结束
如果说服务端还没读取结束,客户端就把这个通道给关了,读取的内容读取到一半肯定是不行的,所以有个四次挥手协议,利用这个协议断开连接,而且保证连接通道里面的数据已经处理完毕了

三次握手协议和四次挥手
三次握手协议

三次握手是为了确保连接建立
一开始客户端会向服务器去发出连接的请求,等待服务端的确认
而服务端向客户端返回了一个相应,告诉客户端收到了请求
客户端向服务器再次发出确认信息,连接建立。

第一次握手,服务端收到了客户端的请求,说明服务端的接受能力和客户端的发送能力正常
第二次握手,客户端收到了服务端的请求,说明服务端在第一次握手收到消息,说明客户端已经知道客户端和服务端的接受和发送能力是ok滴,但是服务端还不知道客户端的接受能力怎么样
所以有第三次握手,客户端再次向服务端发出确认信息,告诉服务端,我的接受能力也是ok滴
三次握手以后,双方就都知道了自己和对方接受和发送的能力都是ok滴,也就是连接成功,自此可以开始传输数据。

四次挥手协议

是为了确保连接断开,且数据处理完毕

第一次挥手:
客户端向服务器发出取消请求
第二次挥手:
服务器向客户端返回一个相应
标识收到客户端取消请求(服务器将最后的数据处理完毕)
第三次挥手
当服务端处理完输出,服务器向客户端发出确认取消信息
第四次挥手
客户端再次发送确认信息,连接取消

小练习

多发多收

客户端:多次发送数据
服务器:接受多次接受数据,并打印

//客户端
//1、创建Socket对象并连接服务端
Socket socket = new Socket("10.200.28.55",10000);
//2、写出数据
Scanner sc = new Scanner(System.in);
OutputStream os = socket.getOutputStream();
while (true) {
    System.out.println("请输入您想要发送的信息");
    String str = sc.nextLine();
    if("886".equals(str)){
        break;
    }
    os.write(str.getBytes());
}
//3、释放资源
socket.close();
//服务端
//1、创建对象绑定10000端口
ServerSocket ss = new ServerSocket(10000);
//2、等待客户端连接
Socket socket = ss.accept();
//3、读取数据
InputStreamReader isr = new InputStreamReader(socket.getInputStream());
int b;
while ((b = isr.read()) != -1){
    System.out.print((char) b);
}
//4、释放资源
socket.close();
ss.close();

接受和反馈

客户端:发送一条数据,接受服务端反馈的消息并打印
服务器:接收数据并打印,再给客户端反馈消息

//1、创建Socket对象并连接服务端
Socket socket = new Socket("10.200.28.55",10000);
//2、写出数据

String str = "你好";
OutputStream os = socket.getOutputStream();
os.write(str.getBytes());
//接受服务端回写的数据
InputStreamReader isr = new InputStreamReader(socket.getInputStream());
int b;
while((b = isr.read()) != -1){
    System.out.println((char)b);
}
//释放资源
socket.close();
//1、创建对象绑定10000端口
ServerSocket ss = new ServerSocket(10000);
//2、等待客户端连接
Socket socket = ss.accept();
//3、读取数据
InputStreamReader isr = new InputStreamReader(socket.getInputStream());
int b;
while ((b = isr.read()) != -1){
    System.out.print((char) b);
}
//回写数据
String str2 = "到底有多开心";
OutputStream os = socket.getOutputStream();
os.write(str2.getBytes());
//释放资源
socket.close();
ss.close();

上述代码有问题,在服务端运行读取数据时会卡死。
read方法会从连接通道中读取数据,但是需要有一个结束标记,此处的循环才会停止,下面的代码才会继续运行
否则,程序就会一直停在read方法这里,读取下面的数据
下面是正确的代码

//1、创建Socket对象并连接服务端
Socket socket = new Socket("10.200.28.55",10000);
//2、写出数据

String str = "你好";
OutputStream os = socket.getOutputStream();
os.write(str.getBytes());
//写出一个结束标记
socket.shutdownOutput();
//接受服务端回写的数据
InputStreamReader isr = new InputStreamReader(socket.getInputStream());
int b;
while((b = isr.read()) != -1){
    System.out.println((char)b);
}
//释放资源
socket.close();
//1、创建对象绑定10000端口
ServerSocket ss = new ServerSocket(10000);
//2、等待客户端连接
Socket socket = ss.accept();
//3、读取数据
InputStreamReader isr = new InputStreamReader(socket.getInputStream());
int b;
while ((b = isr.read()) != -1){
    System.out.print((char) b);
}
//回写数据
String str2 = "到底有多开心";
OutputStream os = socket.getOutputStream();
os.write(str2.getBytes());
//释放资源
socket.close();
ss.close();

上传文件

客户端:将本地文件上传到服务器。接收服务器的反馈
服务器:接受客户端上传的文件,上传完毕之后给出反馈

 //1、创建对象并绑定
ServerSocket ss = new ServerSocket(10000);
//2、等待客户端来连接
Socket socket = ss.accept();
//3、读取数据并保存到本地文件中
BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("C:\\Users\\xiaoyou\\Desktop\\javafile\\receive\\c.jpg"));
int len;
byte[] bytes = new byte[1024];
while((len = bis.read(bytes)) != -1){
    bos.write(bytes,0,len);
}
//4、回写数据
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
bw.write("上传成功");
bw.newLine();
bw.flush();

//5、释放资源
socket.close();
ss.close();
Socket socket = new Socket("10.200.28.55",10000);
//2、读取本地文件中的数据,并写到服务器当中
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("C:\\Users\\xiaoyou\\Desktop\\javafile\\CAE083634634F6F3831376E45F62D0DE.png"));
BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());
byte[] bytes = new byte[1024];
int len;
while ((len = bis.read(bytes)) != -1){
    bos.write(bytes,0,len);
}
//往服务器写出结束标记
socket.shutdownOutput();
//3、接受服务器的回写数据
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String line = br.readLine();;
System.out.println(line);

//4、释放资源
socket.close();

上传文件(文件名重复问题)

解决上一题文件名重复问题

UUID能帮助我们解决这个疑难杂症,这玩意可以生成一个随机的文件名

//1、创建对象并绑定
ServerSocket ss = new ServerSocket(10000);
//2、等待客户端来连接
Socket socket = ss.accept();
//3、读取数据并保存到本地文件中
BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());
String name = UUID.randomUUID().toString().replace("-", "");
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("C:\\Users\\xiaoyou\\Desktop\\javafile\\receive\\"+name+".jpg"));
int len;
byte[] bytes = new byte[1024];
while((len = bis.read(bytes)) != -1){
    bos.write(bytes,0,len);
}
//4、回写数据
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
bw.write("上传成功");
bw.newLine();
bw.flush();

//5、释放资源
socket.close();
ss.close();

上传文件(多线程版)

想要服务器不停止,能接受很多用户上传的图片。
可以用循环或者多线程

import jdk.internal.util.xml.impl.Input;

import java.io.*;
import java.net.*;
import java.util.Scanner;

public class demo2 {
    public static void main(String[] args) throws IOException {
        Socket socket = new Socket("10.200.28.55",10000);
        //2、读取本地文件中的数据,并写到服务器当中
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream("C:\\Users\\xiaoyou\\Desktop\\javafile\\CAE083634634F6F3831376E45F62D0DE.png"));
        BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());
        byte[] bytes = new byte[1024];
        int len;
        while ((len = bis.read(bytes)) != -1){
            bos.write(bytes,0,len);
        }
        //往服务器写出结束标记
        socket.shutdownOutput();
        //3、接受服务器的回写数据
        BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        String line = br.readLine();;
        System.out.println(line);

        //4、释放资源
        socket.close();
    }
}

import java.io.*;
import java.net.*;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

public class demo {
    public static void main(String[] args) throws IOException {
         //1、创建对象并绑定
        ServerSocket ss = new ServerSocket(10000);
        while (true) {
            //2、等待客户端来连接
            Socket socket = ss.accept();
            new Thread (new MyRunnable(socket)).start();
        }
    }
}
import java.io.*;
import java.net.Socket;
import java.util.UUID;

public class MyRunnable implements Runnable{
    Socket socket;
    public MyRunnable(Socket socket){
        this.socket = socket;
    }
    @Override
    public void run() {
        try {
            //3、读取数据并保存到本地文件中
            BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());
            String name = UUID.randomUUID().toString().replace("-", "");
            BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("C:\\Users\\xiaoyou\\Desktop\\javafile\\receive\\"+name+".jpg"));
            int len;
            byte[] bytes = new byte[1024];
            while((len = bis.read(bytes)) != -1){
                bos.write(bytes,0,len);
            }
            //4、回写数据
            BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
            bw.write("上传成功");
            bw.newLine();
            bw.flush();

        } catch (IOException e) {
            throw new RuntimeException(e);
        }finally {
            //5、释放资源
            if (socket != null){
                try {
                    socket.close();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }
}

上传文件(线程池优化)

频繁创建线程并销毁非常的浪费系统资源,所以需要用线程池优化

public static void main(String[] args) throws IOException {
 //创建线程池对象
 ThreadPoolExecutor pool = new ThreadPoolExecutor(3,16,60, TimeUnit.SECONDS,new ArrayBlockingQueue<>(2),new ThreadPoolExecutor.AbortPolicy());


 //1、创建对象并绑定
 ServerSocket ss = new ServerSocket(10000);
 while (true) {
     //2、等待客户端来连接
     Socket socket = ss.accept();
     pool.submit(new MyRunnable(socket));
 }
}

BS(接受浏览器的消息并打印)

客户端:不需要写
服务器:接收数据并打印

public static void main(String[] args) throws IOException {
//1、创建对象绑定10000端口
ServerSocket ss = new ServerSocket(10000);
//2、等待客户端连接
Socket socket = ss.accept();
//3、读取数据
InputStreamReader isr = new InputStreamReader(socket.getInputStream());
int b;
while ((b = isr.read()) != -1){
    System.out.print((char) b);
}
//4、释放资源
socket.close();
ss.close();
}

润这个代码后,用浏览器打开自己本机ip地址加冒号再加上端口号,就能获得一些数据返回

反射

什么是反射?
反射允许对封装类的字段,方法和构造函数的信息进行编辑访问。
即:反射允许对成员变量,成员方法和构造方法的信息进行编程访问
能从类里面拿出能用的成员变量,成员方法,构造函数
能把这个类扒的干干净净,一点都不剩
IDEA里面的提示功能就是个反射

反射就可以分为两类
一个是获取,一个是解剖
获取的小细节,是从class文件里面去获取的

获取class对象的三种方式

1、Class.forName(“全类名”);
2、类名.class
3、对象.getClass();

java文件是我们编写的文件,运行前会先将java文件编译成class文件,这个过程是在硬盘里进行的操作。这个阶段被称为源代码阶段,而在这个阶段就用第一种方式去获取字节码文件。
运行代码时,要先将这个类的字节码文件加载到内存当中。这个阶段叫做加载阶段,这个阶段可以用第二种方式
在内存当中去创建对象,巴拉巴拉的就叫运行阶段,这个阶段就可以用第三种方式

what is 全类名?
全类名就是包名加类名
如何获取咧?
双击选中类名,右键,“复制粘贴特殊”---->“复制引用”

Class clazz = Class.forName("Student");
System.out.println(clazz);
Class studentClass = Student.class;
System.out.println(clazz == studentClass);
Student s = new Student();
Class clazz3 = s.getClass();
System.out.println(studentClass == clazz3);

第一种是最常用的
第二种更多是当作参数进行传递的,就像锁那边一样,把这玩意当成锁的参数
第三种是当我们已经有了这个类的对象时,才可以使用的

反射获取构造方法

Class类中用于获取构造方法的方法

Constructor<?>[] getConstructors()//返回所有公共构造方法对象的数组
Constructor<?>[] getDeclaredConstructors()//返回所有构造方法对象的数组
Constructor<T> getConstructor(Class<?>... parameterTypes)//返回单个公共构造方法对象
Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)//返回单个构造方法对象

Constructor类中用于创建对象的方法

T newInstance(Object... initargs)//根据指定的构造方法创建对象
setAccessible(boolean flag)//设置为true,标识取消访问检查
Class clazz = Class.forName("Student");
Constructor[] cons = clazz.getConstructors();
for (Constructor con : cons) {
    System.out.println(con);
}
System.out.println("------------------------");
Constructor[] con = clazz.getDeclaredConstructors();
for (Constructor constructor : con) {
    System.out.println(constructor);
}
System.out.println("------------------------");
Constructor con1 = clazz.getDeclaredConstructor();
System.out.println(con1);
System.out.println("------------------------");
Constructor con2 = clazz.getDeclaredConstructor(String.class);
System.out.println(con2);
System.out.println("------------------------");
Constructor con3 = clazz.getDeclaredConstructor(int.class);
System.out.println(con3);
System.out.println("------------------------");
Constructor con4 = clazz.getDeclaredConstructor(String.class, int.class);
System.out.println(con4);
int modifiers = con4.getModifiers();
System.out.println(modifiers);
System.out.println("------------------------");
Parameter[] parameters = con4.getParameters();
for (Parameter parameter : parameters) {
    System.out.println(parameter);
}
System.out.println("------------------------");
//暴力反射:临时取消权限校验
con4.setAccessible(true);
Student stu = (Student) con4.newInstance("张三", 23);
System.out.println(stu);

public————1
abstract———1024
final—————16
interface———512

反射获取成员变量

Class类中用于获取成员变量的方法

Field[] getFields()//返回所有公共成员变量对象的数组
Field[] getDeclaredFields()//返回所有成员变量对象的数组
Field getField(String name)//返回单个公共成员变量对象
Field getDeclaredField(String name)//返回单个成员变量对象

Field类中用于创建对象的方法

void set(Object obj, Object value)//赋值
Object get(Object obj)//获取值
//1、获取class字节码文件的对象
Class clazz = Class.forName("Student");
//2、获取成员变量
Field[] fields = clazz.getFields();
for (Field field : fields) {
    System.out.println(field);
}
//获取单个成员变量
Field name = clazz.getDeclaredField("name");
System.out.println(name);
//获取到权限修饰符
int modifiers = name.getModifiers();
System.out.println(modifiers);
//获取成员变量的名字
String n = name.getName();
System.out.println(n);
//获取成员变量的数据类型
Class<?> type = name.getType();
System.out.println(type);
//获取成员变量记录的值
Student s = new Student("张三",23);
name.setAccessible(true);
Object value = name.get(s);
System.out.println(value);
//修改对象里面记录的值
name.set(s,"lisi");
System.out.println(s);

获取成员方法

Class类中用于获取成员方法的方法

Method[] getMethods()//返回所有公共成员方法对象的数组,包括继承的
Method[] getDeclaredMethods()//返回所有成员方法对象的数组,不包括继承的
Method getMethod(String name,Class<?>...parameterTypes)//返回单个公共成员方法对象
Method getDeclaresMethod(String name,Class<?>...parameterTypes)//返回单个成员方法对象

Method类中用于创建对象的方法
Object invoke(Object obj,Object… args):运行方法
参数一:用obj对象调用该方法
参数二:调用方法的传递的参数(如果没有就不写)
返回值:方法的返回值(如果没有就不写)

//获取class字节码文件对象
Class<?> clazz = Class.forName("Student");
//2、获取里面所有的方法对象(包含父类中所有的公共方法)
Method[] methods = clazz.getMethods();
for (Method method : methods) {
    System.out.println(method);
}
Method[] declaredMethods = clazz.getDeclaredMethods();
for (Method declaredMethod : declaredMethods) {
    System.out.println(declaredMethod);
}
//获取指定的单一方法
Method m = clazz.getDeclaredMethod("eat", String.class);
System.out.println(m);
//获取方法的修饰符
int modifiers = m.getModifiers();
System.out.println(modifiers);
//获取方法的名字
String name = m.getName();
System.out.println(name);
//获取方法的形参
Parameter[] parameters = m.getParameters();
for (Parameter parameter : parameters) {
    System.out.println(parameter);
}
//获取方法的抛出异常
Class<?>[] exceptionTypes = m.getExceptionTypes();
for (Class<?> exceptionType : exceptionTypes) {
    System.out.println(exceptionType);
}
//方法运行
//Method类中用于创建对象的方法
//Object invoke(Object obj,Object... args):运行方法
//参数一:用obj对象调用该方法
//参数二:调用方法的传递的参数(如果没有就不写)
//返回值:方法的返回值(如果没有就不写)
Student s = new Student();
m.setAccessible(true);
//参数一s:表示方法的调用者
//参数二:汉堡包,表示在调用方法的时候传递的实际参数
String result = (String) m.invoke(s, "汉堡包");
System.out.println(result);

动态代理

什么是动态代理?
无侵入式的给代码增加额外的功能。
在定义吃这个方法时,吃需要先拿筷子,盛饭,如是将拿筷子,盛饭直接放到吃这个方法中,叫做侵入式修改。
而代理就是帮助我们在吃之前完成拿筷子和盛饭。

程序为什么需要代理?
对象如果嫌身上干的事太多的话,可以通过代理来转移部分职责。
就像sucker明星需要经纪人帮忙”给他包扎一会就恢复的伤口“
在这里插入图片描述

代理长什么样?
对象有什么方法想被代理,代理就一定要有对应的方法
就好比接单子,接单子的人不一定是做的人,但一定有处理单子的方法,可能他的方法是找别人去给他做。

终结如何知道要派有唱歌、跳舞方法的代理呢?
接口

Java通过什么来保证代理的样子?
通过接口保证,后面的对象和代理需要实现同一个接口
接口中就是被代理的所有方法

java.lang.reflect.Proxy类:提供了为对象产生代理对象的方法

public static Object newProxtInstance(ClassLoader loader, Class<?>[]   interfaces, InvocationHandler h)
//参数一:用于指定用哪个类加载器,去加载生成的代理类
//参数二:指定接口,这些接口用于指定生成的代理长什么,也就是有哪些方法
//参数三:用来指定生成的代理对象要干什么事情
public class BigStar implements Star{
    private String name;

    public BigStar() {
    }
    public BigStar(String name) {
        this.name = name;
    }
    @Override
    public String sing(String name){
        System.out.println(this.name + "正在唱" + name);
        return "谢谢";
    }
    @Override
    public void dance(String name){
        System.out.println(this.name + "正在跳舞" );
    }
    public String getName() {
        return name;
    }

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

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class ProxyUtil {
    //方法的作用:给定一个明星的对象,创建一个代理
    //形参:被代理的明星对象
    //返回值:给明星创建的代理

    //需求:外面的人想要大明星唱一首歌
    //1、获取代理的对象
    //代理对象 = ProxyUtil.createProxy(大明星的对象);
    //2、再调用代理的唱歌方法
    //代理对象.唱歌的方法()
    public static Star creatProxy(BigStar bigStar){
        Star star = (Star) Proxy.newProxyInstance(ProxyUtil.class.getClassLoader(), new Class[]{Star.class},
                new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                if ("sing".equals(method.getName())){
                    System.out.println("准备话筒,收钱");
                }else if ("dance".equals(method.getName())){
                    System.out.println("准备场地,收钱");
                }
                return method.invoke(bigStar,args);
            }
        });
        return star;
    }
}

public interface Star {
    //我们可以将所有想要被代理的方法定义在接口当中
    public String sing(String name);
    public void dance(String name);
}

import java.util.ArrayList;
import java.util.Map;

public class Test {
    public static void main(String[] args) {
        //1、获取代理的对象
        BigStar bigStar = new BigStar("鸡哥");
        Star proxy = ProxyUtil.creatProxy(bigStar);

        //2、调用唱歌的方法
        String result = proxy.sing("只因你太美");
        System.out.println(result);

        //3、调用跳舞的方法
        proxy.dance("只因你太美");
    }

}

结语

JavaSE的课的笔记到此结束了,祝能看的列位早日进大厂,拿到想要的Offer
莫道桑榆晚,为霞尚满天

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

优降宁

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值