多线程
定义一个子类MyThread继承线程类java.lang.Thread 重写run()方法
调用线程对象的start()方法启动线程(启动后还是执行run方法的)
注意 为什么不直接调用run方法,而是调用start启动线程
直接调用run方法会当成普通方法执行 此时相当于还是单线程执行 只有调用start方法才是启动一个新的线程执行
不能把主线任务放在子线程之前 如上图就是把main函数里面的那个 主线程执行输出 放在 t.start() 之前
缺点 线程类已经继承了Thread,无法继承其他类,不利于扩展
定义一个线程任务类MyRunnable实现Runnable接口,重写run()方法
方式二优缺点
优点 线程任务类只是实现接口,可以继续继承类和实现接口,扩展性强
缺点 编程多一层对象包装类 如果线程有执行结果是不可以直接返回的(第一种也是一样)
多线程的实现方案二(另一种写法而已):实现Runnable接口(匿名内部类形式)
在上面的基础之上继续简化
多线程的实现方案三:利用Callable、FutureTask接口实现
1 得到任务对象 定义类实现Callable接口,重写call方法 ,封装要做的事情
用FutureTask把Callable对象封装成线程任务对象
4 线程执行完毕后,通过FutureTask的 get 方法获取任务执行的结果
优点 线程任务类只是实现接口 可以继续继承类和实现接口 扩展性强
继承Thread类 代码简单,可以直接使用Thread的方法 由于继承只能单继承所以扩展性差 不能返回线程执
实现Runnable接口 扩展性好,可以实现多个接口,而且可以继承一个类 代码复杂,不能返回线程执行结果
利用Callable、FutureTask接口实现 扩展性好,还可以得到线程执行的结果 代码复杂
此时需要使用Thread的常用方法:getName() setName() currentThread()
String getName() 获取当前线程的名称 默认线程名称是Thread-索引
void setName(String name)将此线程的名称更改为指定的名称 通过构造器也可以设置线程名称
结果
结果
多个线程同时操作同一个共享资源的时候可能会出现业务安全问题,称为线程安全问题
比如 夫妻有同一个银行账户 余额10w 然后夫妻双方同时取钱10w,当他们同时判断余额时,可能双方余额都满足,所以同时取会取出20w
加锁 , 把共享资源进行上锁,每次只能有一个线程进入访问完毕以后解锁,然后其他线程才能进来
原理:每次只能一个线程进入,执行完毕后自动解锁,其他线程才可以进来执行
理论上 锁对象只要对于当前同时执行的线程来说是同一个对象即可
锁对象使用任意唯一的对象好不好 [就是synchronized("chen") 这里,括号里面可以随便写]
不好,会影响其他无关线程的执行 【 例如银行账户那个,如果是任意唯一对象,当另一个账户也来取钱,导致其需要等待上一个用户】
对于静态方法建议使用字节码(类名.class)对象作为锁对象
2 同步方法(锁的范围小于同步代码块)
原理 每次只能一个线程进入,执行完毕之后自动解锁,其他线程才可以进来执行
修饰符 synchronized 返回值类型 方法名称 (形参列表) {
对于实例方法默认使用this作为锁对象 对于静态方法默认使用类名.class对象作为锁对象
为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock,更加灵活,方便
Lock实现提供比使用synchronized方法和语句可以获得更广泛的锁定操作
Lock是接口 不能直接实例化,这里采用了它的实现类ReentrantLock来构建Lock锁对象
线程通信(先了解思想)
线程池
如果用户每发起一个请求,后台就创建一个新线程处理,下次新任务来了又要创建新线程,而创建新线程的开销是很大的,这样会严重影响系统的性能
使用ExecutorService的实现类ThreadPoolExecutor自动创建一个线程池对象
用KTV形象表示 参数一相当于长久工假设3个 参数二好比仅招10个员工,所以还可以找7个临时工,参数5相当于3个正式工都在忙,再来人时需要等待,假设就可以等待5个人,参数六相当于人力部招人的,参数七表示等待已满,再来人时选择 一脚踢出去还是咋样
使用 Executors(线程池的工具类) 调用方法返回不同特点的线程池对象
新任务提交时发现核心线程都在忙,任务队列也满了,并且还可以创建临时线程,此时才会创建临时线程
核心线程和临时线程都在忙,任务队列也满了,新的任务过来是才会开始任务拒绝
方式1(使用ExecutorService的实现类ThreadPoolExecutor自动创建一个线程池对象)
线程池处理Callable任务
此处用第二个submit 返回未来任务对象,用Future<V>接收
方式2(使用 Executors(线程池的工具类) 调用方法返回不同特点的线程池对象)
Executors线程池的工具类通过调用方法返回不同类型的线程池对象
Executors的底层其实也是基于线程池的实现类ThreadPoolExecutor创建线程池对象的
Executors使用过程中存在的问题导致其在大型系统中无法使用
Timer是单线程,处理多个任务按照顺序执行,如果有一个任务有睡眠,导致存在延时与设置定时器的时间有出入
可能因为其中的某个任务的异常使Timer线程死掉,从而影响后续任务执行
3000表示的意思是 点开执行任务时,需要等3s才会开始执行
2000表示的意思是 开始执行任务时,需要每2s才会执行一次
并发与并行(并发 CPU分时轮询的执行进程 并行 同一个时刻在同时执行)
正在运行的程序就是一个独立的进程 线程是属于进程的 多个线程其实是并发与并行同时进行的
CPU同时处理线程的数量有限 CPU会轮换位系统的每个线程服务,由于CPU切换速度很快,给我们的感觉这些线程在同时进行,这就是并发
比如 我给全班人发糖 ,由于我跑的快,感觉全班人都在被我发糖 这就是并发
当我再叫上几个人,一起发糖,就是并行 CPU的线程数就是并行数
线程的状态 也就是线程从生到死的过程,以及中间经历的各种状态及状态转换
Client-Server(CS) 客户端与服务端 需要下载客户端软件
Browser/Server(BS)浏览器与服务端 直接用浏览器即可
协议 数据在网络中传输的规则,常见的协议有UDP协议和TCP协议
192.168.开头就是常见的局域网地址 范围为192.168.0.0--192.168.255.255专门为组织机构内部使用
本机IP永远是 127.0.0.1 或者 localhost:称为回送地址也可称本地回环地址,只会寻找当前所在本机
标识正在计算机设备上运行的进程(程序),被规定为一个16位的二进制,范围是0-65535
周知端口 0-1023 被预先定义的知名应用占用(如 HTTP占用80 FTP占用21)
注册端口 1024-49151 分配给用户进程或某些应用程序(如 Tomcat 占用8080 MySQL占用3306)
动态端口 49152-65535 之所以称为动态端口,是因为它一般不固定分配某种进程 而是动态分配
注意:我们自己开发的程序选择注册端口 且一个设备中不能出现两个程序的端口号一样 否则出错
传输前,采用“三次握手”方式建立连接,点对点的通信,所以可靠
速度快,有大小限制一次最多发送64k,数据不安全,易丢失数据
UDP三种通信方式
本机所在网段的其他主机的程序只要匹配端口成功就可以接受消息了
UDP实现组播
使用组播地址:224.0.0.0~239.255.255.255
接收端必须绑定该组播IP 端口还要对应发送的目的地端口 这样既可接受该组播消息
DatagramSocket的子类MulticastSocket可以在接收端绑定组播IP
TCP通信
public Socket(String host , int port) 创建发送端的Socket对象与服务端连写,参数为服务端程序的ip和端口
Socket类成员方法
OutputStream getOutputStream() 获得字节输出流对象
InputStream getInputStream() 获得字节输入流对象
2 使用Socket对象调用getOutputStream()方法得到字节输出流
服务端(接收端)实现
public ServerSocket(int port) 注册服务端端口
public Socket accept() 等待接收客户端Socket通信连接,连接成功返回Socket对象与客户端建立端到端通信
2 调用ServerSocket对象的accept()方法,等待客户端的连接,并得到Socket管道对象
3 通过Socket对象调用用getIputStream()方法得到字节输入流,完成数据的接收
注意 需要把客户端对象第四步发送消息的那里变为println 或者 在内容加个 \n 因为接收端是读取的一行,当没遇到换行它是不会停止的,但是发送端发完消息后,就结束了,相应的接收端也会挂掉,见下面
使用TCP通信实现 多发多收消息(基于上面的 只展示修改代码的部分)
现在服务端是单线程的,每次只能处理一个客户端消息(解决见 下面)
每接收到一个Socket通信管道后分配一个独立的线程负责处理它
定义一个线程
上面这个存在问题,如果线程创建过多会导致服务器崩溃,所以用线程池优化
用来包装成任务的
单元测试就是针对最小的功能单元编写测试代码,Java程序最小的功能单元是方法,因此,
只有一个main方法,如果一个方法的测试失败了,其他方法测试会受到影响
JUnit是使用Java语言实现的单元测试框架,在IDE工具都集成了JUnit,这样就可以直接在IDE中编写并运行JUnit测试
JUnit可以灵活的选择执行哪些测试方法,可以一键执行全部的测试方法
1 将JUnit的jar包导入到项目中(一般IDEA都会给你整合好,但是如果没有整合好的话,需要自己动手)
2 编写测试方法:该测试方法必须是公共的无参数无返回值的非静态方法
3在测试方法上使用@Test注解:标注该方法是一个测试方法(如果@Test是红色的,表示还没有导入jar包,此时alt+Enter,导入jar包,这就是所谓的自动导入,但我的不行!!!!!!!!!!!!)
也可以在整个类的类名上运行,运行的是整个类里面的所有测试方法
反射概述
反射是指对于任何一个Class类,在“运行的时候”都可以直接得到这个类全部成分
在运行时,可以直接得到这个类的构造器对象:Constructor
这种运行时动态获取信息以及动态调用类中成分的能力称为Java语言的反射机制
反射的第一步都是先得到编译后的Class类对象,然后就可以得到Class的全部成分
方式一:Class c1 = Class.forName("包名+类名");
2 使用反射技术获取构造器对象并使用
如果是非public的构造器,需要打开权限(暴力反射),然后再创建对象
如果是非public的构造器,需要打开权限(暴力反射),然后再取值、赋值
反射的作用
反射是作用在运行时的技术,此时集合的泛型将不能产生约束了,此时可以为集合存入其他任意类型的元素
泛型只是在编译阶段可以约束集合只能操作某种类型,在编译阶成Class文件进入运行阶段的时候,其真实类型都是ArrayList了,泛型相当于被擦除了
还有一种更简洁的方法让其支持其他类型
需求:给你任意一个对象,在不清楚对象字段的情况下,可以把对象的字段名称和对应值存储到文件中去
内容如下
注解
例如:JUnit框架中,标记了注解@Test的方法就可以被当成测试方法执行,而没有标记的就不能当成测试方法执行
value属性,如果只有一个value属性的情况下,使用value属性的时候可以省略value名称不写
但是如果有多个属性,且多个属性没有默认值,那么value名称是不能省的
注解的操作中经常需要解析,注解的解析就是判断是否存在注解,存在注解就解析出内容
注解的应用
代理指某些场景下会找一个代理对象,来辅助自己完成一些工作,如歌星(经纪人),代理主要是对对象的行为额外做一些辅助操作
Java中代理的代表类是:java.lang.reflect.Proxy
Proxy提供了一个静态方法,用于为对象产生一个代理对象返回