文章目录
- 多线程
- 什么是多线程
- 多线程的应用场景
- 小总结
- 并发和并行
- 并发
- 并行
- 多线程的实现方式
- 关于Thread类
- 关于Runable接口
- 关于Callable接口和Future接口
- 多线程三种实现方式对比
- 常用方法
- 线程的优先级
- 守护线程(备胎线程)
- 礼让线程
- 插入线程
- 线程的生命周期
- 线程的安全问题&同步代码块
- 有关同步代码快的小细节
- 同步方法
- lock
- 死锁(这是一种错误)
- 生产者和消费者(等待唤醒机制)
- 消费者和生产者代码实现
- 阻塞队列方式实现
- 阻塞队列的继承结构
- 线程的六种状态
- 综合小练习
- 抢红包
- 抽奖箱抽奖
- 多线程统计并求出最大值
- 多线程之间的比较
- 线程池
- 自定义线程池解析
- 线程池小结
- 线程池的最大并行数
- 线程池多大合适
- 网络编程
- 小总结
- 网络编程的三要素
- IP
- IP小总结
- IPv4小细节
- 端口号
- 协议
- UDP协议
- UDP通信程序(发送数据)
- UDP通信程序(发送数据)
- 小练习——聊天室
- UDP的三种通信方式
- TCP协议
- TCP通信协议
- 代码的小细节
- 三次握手协议和四次挥手
- 三次握手协议
- 四次挥手协议
- 小练习
- 多发多收
- 接受和反馈
- 上传文件
- 上传文件(文件名重复问题)
- 上传文件(多线程版)
- 上传文件(线程池优化)
- BS(接受浏览器的消息并打印)
- 反射
- 获取class对象的三种方式
- 反射获取构造方法
- 反射获取成员变量
- 获取成员方法
- 动态代理
- 结语
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{@Overridepublic 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{@Overridepublic 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> {@Overridepublic 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;@Overridepublic 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();@Overridepublic 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();@Overridepublic 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{@Overridepublic 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{//把吃的总数-1Desk.count--;//如果有,就开吃System.out.println("吃货在吃面条,还能再炫"+Desk.count+"碗!!!");//吃完之后,唤醒厨师继续做Desk.lock.notifyAll();//修改桌子的状态Desk.foodFlag = 0;}}}}}
}
生产者
public class Cook extends Thread{@Overridepublic 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;}@Overridepublic 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;}@Overridepublic 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;@Overridepublic 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;}@Overridepublic 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<>();@Overridepublic 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;}@Overridepublic 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;}@Overridepublic 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{@Overridepublic 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,//核心线程数量,能小于06,//最大线程数,不能小于0,最大数量>=核心线程数量60,//空闲线程最大存活时间TimeUnit.SECONDS,//时间单位,用TimeUnit指定new ArrayBlockingQueue<>(3),//任务队列,不能为nullExecutors.defaultThreadFactory(),//创建线程工厂,不能为nullnew 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;}@Overridepublic 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;}@Overridepublic String sing(String name){System.out.println(this.name + "正在唱" + name);return "谢谢";}@Overridepublic 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() {@Overridepublic 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
莫道桑榆晚,为霞尚满天