一、介绍
Java学习之A4:多线程、网络编程、反射、注解、代理。
二、多线程
(一)、线程的创建
多线程指:多个线程同时执行,但每个线程都有自己的执行路径,互不影响(由CPU负责调度执行)。
- 多线程的创建方式一:继承Thread类,重写run()方法,然后分配和启动子类的实例(
p.start();)。 - 多线程的创建方式二:声明一个实现Runnable接口,实现run()方法,然后创建Thread实例并作为参数传入实现Runnable接口的实例(
new Thread(new RunnableImpl()).start();)。 - 多线程的创建方式三:使用Callable接口,实现call()方法,然后创建FutureTask实例并作为参数传入实现Callable接口的实例(
new FutureTask(new CallableImpl()).start();)。
1、继承Thread类
定义一个子类继承Thread类,重写run()方法,然后分配和启动子类的实例(p.start();)。
public class threadstest {
//main方法本身是由一条主线程负责执行的
public static void main(String[] args) {
MyThread t=new MyThread();//4、创建一个线程对象,才能代表线程
t.start();//5、启动线程,向CPU注册请求开启一个线程
//t线程执行,在主线程外执行,必须在t.start()后面才能开启t线程
for (int i=0;i<5;i++) System.out.println("main is running: "+i);
/**输出:(每次情况都不一样,主线程和MyThread线程并行)
* main is running: 0
* main is running: 1
* MyThread is running: 0
* main is running: 2
* main is running: 3
* main is running: 4
* MyThread is running: 1
* MyThread is running: 2
* MyThread is running: 3
* MyThread is running: 4
*/
}
}
class MyThread extends Thread{//1、定义定义一个子类继承Thread类,成为线程类
//2、重写Threads类的run方法
@Override
public void run(){
for (int i=0;i<5;i++)System.out.println("MyThread is running: "+i);
//3、在run方法中编写线程的任务代码(线程的任务)
}
}
优点:编码简单
缺点:无法控制线程的优先级、调度顺序、运行状态、等待和唤醒,无法继承其他类,只能继承Threads类。
注意:启动线程必须是调用start()方法,若调用run()方法,会当成普通的方法执行,此时相当于还是单线程执行。
2、实现Runnable接口
实现Runnable接口,实现run()方法,然后创建Thread实例并作为参数传入实现Runnable接口的实例(new Thread(new RunnableImpl()).start();)。
public class runnable {
public static void main(String[] args) {
//3、创建线程任务对象代表一个线程任务
Runnable r=new MyRunnable();
//4、把线程任务对象交给一个线程对象来处理
Thread t=new Thread(r);
//5、启动线程
t.start();
for (int i=0;i<5;i++) System.out.println("main is running: "+i);
//匿名内部类实现:
new Thread(new Runnable() {
@Override
public void run() {for (int i=0;i<5;i++) System.out.println("MyThread is running: "+i);}
}).start();//直接调用start()启动
//函数式接口匿名内部类
new Thread(()->{for (int i=0;i<5;i++) System.out.println("MyThread is running: "+i);}).start();
}
}
//1、定义一个线程任务类实现Runnable接口
class MyRunnable implements Runnable {
//2、重写run方法,设置线程任务
@Override
public void run(){
for (int i=0;i<5;i++)System.out.println("MyThread is running: "+i);
}
}
优点:任务类只是实现接口,可以继承其他类,扩展性强。
缺点:需要多一个Runnable对象,并且线程执行结果不能直接返回。
3、使用Callable接口(需要返回值)
前两种线程执行完后重写的run()方法不能直接返回结果,因为run的返回值为void。
使用Callable接口,实现call()方法,然后创建FutureTask(线程任务对象)实例并作为参数传入实现Callable接口的实例(new FutureTask(new CallableImpl()).start();)。
public class callable {
public static void main(String[] args) {
//3、创建一个callable接口的实现类对象
MyCallable c=new MyCallable(100);
//4、把callable对象封装成一个线程任务对象FutureTask对象
/**
* FutureTask类实现了Runnable接口,所以可以作为线程任务对象
* 线程任务对象可以作为线程对象来启动,可以获取线程执行完毕后的结果
*/
Runnable t=new FutureTask<Integer>(c);
//5、把FutureTask对象交给一个线程对象来处理
Thread thread=new Thread(t);
thread.start();
//6、调用FutureTask对象的get方法,获取线程执行完毕后的结果
try {
System.out.println(thread.getName()+" is running: "+((FutureTask<Integer>) t).get());
} catch (Exception e) {
e.printStackTrace();
}
}
}
//1、定义实现类,实现callable接口
class MyCallable implements Callable<Integer> {
private int sum;
public MyCallable(int sum) {this.sum=sum;}
//2、重写call方法,返回计算结果
public Integer call() throws Exception {
int n = 0;
for (int i = 1; i <= sum; i++) {
n += i;
}
return n;
}
}
优点:线程任务类只是实现接口,可以继续继承类和实现接口,可以在线程执行完毕后去获取线程执行的结果
缺点:编码复杂。
(二)、线程的常用方法
| Thread提供的常用方法 | 说明 |
|---|---|
| void run() | 线程任务,线程执行体,线程被创建后,必须重写run方法 |
| void start() | 启动线程,向CPU注册请求开启一个线程 |
| void String getName() | 获取线程名称,默认是Thread-索引,注意要放在启动线程之前 |
| void setName(String name) | 设置线程名称,默认是Thread-索引 |
| static Thread currentThread() | 获取当前线程对象,当前线程对象是静态方法,所以可以直接通过类名调用 |
| void sleep(long millis) | 线程休眠,单位为毫秒,线程暂停millis毫秒,在millis毫秒内,线程处于阻塞状态,无法执行其他任务 |
| void join() | 让调用当前的这个方法的线程先执行完 |
| Thread提供的常见构造器 | 说明 |
| Thread(Runnable target) | 创建一个线程对象,并指定线程任务对象,线程名称默认是Thread-索引 |
| Thread(Runnable target, String name) | 创建一个线程对象,并指定线程任务对象和线程名称 |
| Thread(String name) | 创建一个线程对象,并指定线程名称,线程任务对象默认为null |
(三)、线程安全问题与线程同步
线程安全问题:多个线程,同时操作同一个共享资源时,可能会出现业务安全问题。
线程同步:让多个线程先后依次访问共享资源,避免多线程操作共享资源时出现数据安全问题。
[!线程同步常见方案——加锁]
说明:每次只允许一个线程加锁,加锁后才能访问,访问完毕后自动解锁,然后其他线程才能再加锁进来。
1、同步代码块:把访问共享资源的核心代码给上锁。
synchronized(同步锁){访问共享资源的核心代码}2、同步方法:把访问共享资源的核心方法给上锁。
修饰符 synchronized 返回值类型 方法名(形参列表){访问共享资源的核心代码}3、lock锁对象
public class threadsafe {
public static void main(String[] args) {
//创建一个账户类,用于创建A,B共同账户对象,存入100。
Account A=new Account(1,100);
//模拟两个线程,A和B,A和B共同操作同一个账户,A和B同时取钱,每次取10。
new ABThread("A",A).start();
new ABThread("B",A).start();
}
}
class Account {
private Integer id;
private double balance;
public void getMoney(double balance){
String name=Thread.currentThread().getName();
if(this.balance>=balance){
System.out.println(name+"取钱成功,余额为:"+this.balance);
this.balance-=balance;
System.out.println(name+"取钱成功,余额应该为:"+this.balance);
}else{
System.out.println(name+"取钱失败,余额不足,余额为:"+this.balance);
}
}
//省略getter和setter方法,有参无参构造器,toString方法
}
class ABThread extends Thread {
private Account account;
public ABThread(String id,Account account){
super(id);
this.account=account;
}
@Override
public void run() {
account.getMoney(100);
}
}
1、同步代码块(性能好)
作用:把访问共享资源的核心代码给上锁。synchronized(this\类名.class/*同步锁*/){访问共享资源的核心代码}
原理:每次只允许一个线程加锁后进入,执行完毕后自动解锁,其他线程才能再加锁进来。
注意:对当前同时执行的线程来说,同步锁必须是同一把(同一个对象),否则会出bug。
锁对象的使用:使用共享资源作为锁对象,对于实例方法用this作为锁,对于静态方法用类名.class作为锁。
public class Synchronized {
public static void main(String[] args) {
Account B = new Accounts(1, 100);
new ABThread("A", B).start();
new ABThread("B", B).start();
Account C = new Accounts(2, 100);
new ABThread("C", C).start();
new ABThread("D", C).start();
}
}
class Accounts extends Account{
public Accounts(int i, int i1) {
super(i, i1);
}
private double p=super.getBalance();
@Override
public void getMoney(double balance){
String name=Thread.currentThread().getName();
// synchronized ("hei"){
// 同步锁只能为一个唯一对象,这里使用字符串,字符串是内存内只能加载一份,所以可以作为锁。
//同步锁只能为一个对象,这里使用this,this为当前对象,不用像字符串一样,每次锁的时候影响到其他对象
synchronized (this){
if(p >=balance){
System.out.println(name+"取钱成功,余额为:"+p);
p-=balance;
System.out.println(name+"取钱成功,余额应该为:"+p);
}else{
System.out.println(name+"取钱失败,余额不足,余额为:"+p);
}
}//锁对象:对于实例方法用this作为锁,对于静态方法用类名.class作为锁。
}
}
2、同步方法(性能稍欠)
作用:把访问共享资源的核心方法给上锁。修饰符 synchronized 返回值类型 方法名(形参列表){访问共享资源的核心代码}
给方法上锁,不如给代码块上锁好,有因为相较于方法,代码块的范围更小更加灵活,同步方法可读性好。
class Accounts2 extends Account{
public Accounts(int i, int i1) {
super(i, i1);
}
private double p=super.getBalance();
@Override
public synchronized void getMoney(double balance){//把核心方法给上锁
String name=Thread.currentThread().getName();
if(p >=balance){
System.out.println(name+"取钱成功,余额为:"+p);
p-=balance;
System.out.println(name+"取钱成功,余额应该为:"+p);
}else{
System.out.println(name+"取钱失败,余额不足,余额为:"+p);
}
}
}
3、lock锁(接口)
Lock是接口,不能直接实例化,可以采用它的实现类ReentrantLock来实现Lock锁对象。
对一个工具类创建一个final非静态变量,再在需要加锁的代码前加上变量名.lock(),再给需要加锁的代码抛出异常,最后在finally中解锁,以防出现死锁。
//省略main类
class Account {
private Integer id;
private double balance;
private final Lock lock=new ReentrantLock();//一个账户一把锁,加final表示不能修改
//创建一个锁对象,不能为静态,静态的话所有对象都用一把锁。
public void getMoney(double balance){
String name=Thread.currentThread().getName();
lock.lock();//加锁
try {
if(this.balance>=balance){
System.out.println(name+"取钱成功,余额为:"+this.balance);
this.balance-=balance;
System.out.println(name+"取钱成功,余额应该为:"+this.balance);
}else{
System.out.println(name+"取钱失败,余额不足,余额为:"+this.balance);
}
}finally {
lock.unlock();//解锁需要放在finally中,否则可能造成死锁。
}
}
}
(四)、线程池(线程复用)
若启用线程过多,创建的对象也会有很多,会导致内存消耗过大,CPU的负载过大,导致性能下降。
线程复用:工作线程(WorkThread)+任务队列(WorkQueue)+任务接口(Runnable/callable)
创建线程池:线程池接口——ExecutorService(执行服务)
[!ExecutorService接口]
方法一、使用实现类
ThreadPoolExecutor创建线程池对象。方法二、使用工具类
Executors调用静态方法返回不同特点的线程池对象。
1、ThreadPoolExecutor创建线程池对象(处理Runnable任务)
ThreadPoolExecutor类提供的构造方法:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectionExecutionHandler handler)
使用指定的参数创建线程池对象。
[!ThreadPoolExecutor构造方法]
参一.
corePoolSize:线程池中常驻的线程数量参二.
maximumPoolSize:线程池中允许的最大线程数量CPU密集型任务(计算密集型任务),线程池的常驻线程数量为:
int maxnumPoolSize = Runtime.getRuntime().availableProcessors() + 1;
IO密集型任务(网络IO密集型任务),线程池的常驻线程数量为:
int maxnumPoolSize = Runtime.getRuntime().availableProcessors() * 2;
线程池的最大线程数量:最大线程数=核心线程数*2参三.
keepAliveTime:线程池中线程空闲的时间,超过这个时间,线程池会自动回收线程参四.
unit:keepAliveTime的单位,默认为毫秒,指定线程存活时间的单位参五.
workQueue:任务队列,用于存放等待被执行的任务,默认为无界队列ArrayBlockingQueue,可以指定容量,可以基于数组或链表的任务队列。参六.
threadFactory:线程工厂,用于创建线程,默认为DefaultThreadFactory,可以指定线程工厂,如自定义线程名,自定义线程优先级等。参七.
handler:拒绝策略,当任务队列已满,且线程池中线程数量达到最大值,则执行拒绝策略,默认为AbortPolicy,直接抛出异常,可以指定拒绝策略,如忽略任务,执行自定义任务等。
[!线程池的注意事项]
开始创建临时线程:提交任务时核心线程在忙,任务队列满员,并且还可以创建临时线程时,才会创建临时线程。
开始拒绝新任务(handler):核心线程和临时线程都在忙,任务队列满,新任务来时才开始拒绝。
任务拒绝策略 说明 ThreadPoolExecutor.AbortPolicy() 直接抛出异常(默认)RejectedExecutionException ThreadPoolExecutor.DiscardPolicy() 不抛出异常,直接丢弃任务 ThreadPoolExecutor.DiscardOldestPolicy() 丢弃队列中最老的任务,再尝试放入新的任务 ThreadPoolExecutor.CallerRunsPolicy() 由主线程负责调用任务的run()方法从而绕过线程池直接执行
2、ThreadPoolExecutor创建线程池对象处理Callable任务
使用Future<T> submit(Callable<T> task)执行Callable任务,返回未来任务对象,用于获取线程返回的结果。
public class executthread {
public static void main(String[] args) {
//创建线程池对象,使用线程池的实现类
ExecutorService executorService=new ThreadPoolExecutor(2, 3,
10, TimeUnit.SECONDS,
new ArrayBlockingQueue<Runnable>(3), Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
//使用线程池执行Runnable任务
Runnable task1=new RunnableTask1();
executorService.execute(task1);//任务可以复用,提交的第一个任务
executorService.execute(task1);
executorService.execute(task1);//复用线程
executorService.execute(task1);
executorService.execute(task1);
//使用线程池执行Callable任务
Future<String> f1=executorService.submit(new CallableTask(100));
Future<String> f2=executorService.submit(new CallableTask(200));
Future<String> f3=executorService.submit(new CallableTask(300));
Future<String> f4=executorService.submit(new CallableTask(400));
try {
System.out.println(f1.get());
System.out.println(f2.get());
System.out.println(f3.get());
System.out.println(f4.get());
} catch (Exception e) {
e.printStackTrace();
}
//一般不关闭线程池,可以一直使用,但是需要调用shutdown方法
// executorService.shutdown();//等所有任务执行完毕后关闭线程池
// executorService.shutdownNow();//立即关闭线程池,并打断正在执行的任务
//当核心线程都在忙时,临时创建新线程
executorService.execute(task1);//临时线程,第六个为临时线程
//任务拒绝策略
executorService.execute(task1);//拒绝策略,直接抛出异常
}
}
class RunnableTask1 implements Runnable{
@Override
public void run() {
for (int i=0;i<5;i++) {
System.out.println(Thread.currentThread().getName()+" is running:"+i);
try {//模拟耗时操作
Thread.sleep(Integer.MAX_VALUE);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class CallableTask implements Callable<String>{
private int num;
public CallableTask(int num) {this.num = num;}
@Override
public String call() throws Exception {
int sum=0;
for (int i=0;i<num;i++) {
// System.out.println(Thread.currentThread().getName()+" is running:"+i);
sum+=i;
}
return Thread.currentThread().getId()+":"+sum;
}
}
3、通过Executors工具类创建线程池对象
Executors底层也是基于ThreadPoolExecutor实现类实现的,我们最好少用Executors工具类,因其没有控制允许的申请队列长度,会出现堆积大量的请求,没有控制允许的创建线程数量,导致OOM(内存溢出)
| Executors工具类方法 | 说明 |
|---|---|
ExecutorService newFixedThreadPool(int nThreads) |
创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。 |
ExecutorService newCachedThreadPool() |
创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。 |
ExecutorService newSingleThreadExecutor() |
创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。 |
ExecutorService newScheduledThreadPool(int corePoolSize) |
创建一个定长线程池,支持定时及周期性任务执行。 |
(五)、并发/并行
进程:正在运行的程序就是一个独立的进程,线程是属于进程的,一个进程中可以同时运行多个线程,进程中的多个线程是并发和并行执行的。
并发:进程中线程是由CPU负责调度执行的,但CPU能同时及处理线程的数量有限,CPU会分时轮流为系统每个线程服务,由于其速度快,这些线程彼此之间是并发的,即同时执行。
并行:同一时间刻度上,同时有多个线程被CPU调度执行
三、网络编程
(一)、通讯三要素
1、IP:设备在网络中的唯一标识,IPV4和IPV6, DNS域名解析器:把域名(如:www.bilibili.com)解析为IP地址。 物理IP:Mac地址
| InetAddress类常用方法 | 说明 |
|---|---|
InetAddress getLocalHost() |
获取本机IP,返回一个InetAddress对象 |
InetAddress getByName(String host) |
根据域名获取IP,返回一个InetAddress对象 |
boolean isReachable(int timeout) |
判断IP是否可达,参数为超时时间,单位为毫秒,返回boolean值 |
String getHostAddress() |
获取IP地址,返回String类型 |
String getHostName() |
获取域名,返回String类型 |
public class InetAddresstest {
public static void main(String[] args){
try{
//获取本地IP
System.out.println(InetAddress.getLocalHost().getHostAddress());//本地IP
System.out.println("主机名:"+InetAddress.getLocalHost().getHostName());//本地主机名
//获取对方IP
System.out.println(InetAddress.getByName("www.baidu.com").getHostAddress());
System.out.println("主机名:"+InetAddress.getByName("www.baidu.com").getHostName());
//判断本机与对方主机是否互通,判断是否联网
System.out.println(InetAddress.getByName("www.baidu.com").isReachable(5000));
}catch(Exception e){
e.printStackTrace();
}
}
}
2、端口:标记应用程序的唯一标识,
周知端口:0~1023被预先占用(HTTP占用80,FTP占用21)。
注册端口:1024~65535,分配给用户进程或者某些应用程序。
动态端口:49152~65535,由系统分配,不固定分配某进程。
3、协议:事先规定的连接规则,开放式网络互联标准:OSI网络参考模型(全球互联网标准),事实上国际标准:TCP/UDP网络模型。
TCP:三次握手——确保通信双方发消息没有问题(C-发出请求->S-返回一个响应->C-再次发出确认信息,建立联系->S)
四次挥手——确保通信双方断开连接没有问题(C-发出断开请求->S-返回一个响应->C,S-处理完毕确认断开->C-断开信息->S)
(二)、UDP通道(java.net.DatagramSocket)/CS
特点:无连接、不可靠通信。不事先建立连接,发送端每次把要发送的数据(<64KB)、接收端IP等信息封装成一个数据包,发出去就不管了。
| UDP通信的实现 | 说明 | |
|---|---|---|
| DatagramSocket构造器 | public DatagramSocket() |
创建客户端的Socket对象,系统随机分配一个端口号 |
public DatagramSocket(int port) |
创建服务器的Socket对象,指定端口号 | |
| 方法 | public void send(DatagramPacket p) throws IOException |
发送数据包,把数据包发送到指定的服务器 |
public DatagramPacket receive(DatagramPacket p) throws IOException |
接收数据包,把数据包封装到指定的DatagramPacket对象中,并返回接收到的数据包长度 | |
| DatagramPacket构造器 | public DatagramPacket(byte[] buf, int length) |
创建一个DatagramPacket对象,指定数据包长度,用于接收数据包 |
public DatagramPacket(byte[] buf, int length, InetAddress address, int port) |
创建一个DatagramPacket对象,指定数据包长度、目标IP和端口号,用于发送数据包 |
//____________单发单收____________
public class UDPC_solo {
//创建客户端
public static void main(String[] args) throws Exception{
System.out.println("客户端已启动");
//创建发送端对象,实现单发单收
DatagramSocket dgs=new DatagramSocket();
//创建数据包对象封装要发送的数据
byte[] b="Hello,here is a UDPCline massage!".getBytes();
/**
* 参数1:要发送的数据,字节数组
* 参数2:要发送的数据长度
* 参数3:要发送的目标主机的IP地址
* 参数4:要发送的目标主机的端口号
*/
DatagramPacket packet=new DatagramPacket(b, b.length, InetAddress.getByName("localhost"), 8888 );
//让发送端对象发送数据包
dgs.send(packet);
}
}
public class UDPS_solo {
//这里是服务端
public static void main(String[] args) throws Exception{
System.out.println("服务端已启动");
//创建接收端对象,注册端口
DatagramSocket ds=new DatagramSocket(8888);
//创建数据包对象负责接收数据
byte[] b=new byte[1024*64];
DatagramPacket dp=new DatagramPacket(b,b.length);
//接收数据将数据封装到数据包对象中
ds.receive(dp);
//输出数据
System.out.println("服务端收到:"+new String(dp.getData(),0,dp.getLength()));
//获取对方IP对象和端口号
System.out.println("对方IP:"+dp.getAddress().getHostAddress()+" 对象端口:"+dp.getPort());
}
}
//____________多发多收_______________
public class UDPCM {//客户端
public static void main(String[] args) throws Exception {
System.out.println("客户端启动");
DatagramSocket ds = new DatagramSocket();
Scanner sc = new Scanner(System.in);
while (true) {
System.out.print("请输入要发送的内容:");
String str = sc.nextLine();
if("exit".equals(str)){
System.out.println("客户端退出");
ds.close();//管道关闭,释放资源
break;
}
byte[] b = str.getBytes();
ds.send(new DatagramPacket(b, b.length, InetAddress.getLocalHost(), 8888));
}
}
}
public class UDPSM {//服务端
public static void main(String[] args) throws Exception {
System.out.println("服务器启动");
DatagramSocket ds = new DatagramSocket(8888);
byte[] b = new byte[1024*64];
DatagramPacket dp = new DatagramPacket(b, b.length);
while (true) {
System.out.println("等待客户端发送数据");
ds.receive(dp);
//查看是否收到
System.out.println("服务端收到:" + new String(b, 0, dp.getLength()));
//获取IP与端口
System.out.println("客户端IP:" + dp.getAddress().getHostAddress()+"端口:"+dp.getPort());
}
}
}
(三)、TCP通道(java.net.Socket)/CS
特点:面向连接、可靠通信、端到端通信。通信双方会实现次采取“三次握手”建立连接。
不同点:不同于UDP,TCP需要建立连接,想要同时处理多个客户端消息需要添加多线程代码,而UDP可以直接接收多个客户端消息,不需要用多线程处理。
| TCP通信 | 说明 | |
|---|---|---|
| 客户端 | public Socket(InetAddress address, int port) throws IOException | 创建客户端的Socket对象,指定服务器的IP和端口号,并建立连接 |
| public OutputStream getOutputStream() throws IOException | 获取输出流,发送数据 | |
| public InputStream getInputStream() throws IOException | 获取输入流,接收数据 | |
| 服务端 | public ServerSocket(int port) throws IOException | 创建服务器的ServerSocket对象,指定端口号,并等待客户端的连接 |
| public Socket accept() throws IOException | 等待客户端的连接,并返回一个Socket对象,表示客户端的连接对象 | |
| public void close() throws IOException | 关闭连接,释放资源 |
//____________单发单收____________
public class TCPC {
public static void main(String[] args) throws Exception {
//实现TCP通信一发一收
Socket s = new Socket("localhost", 8888);
System.out.println("客户端启动");
//从Socket通信管道中得到一个字节输出流,那么对面服务端也要是一个对应的字节输入流
OutputStream os = s.getOutputStream();
//特殊数据流
DataOutputStream dos = new DataOutputStream(os);
dos.writeInt(100);
dos.writeUTF("你好,我是客户端");
//关闭管道
dos.close();
}
}
public class TCPS {
public static void main(String[] args) throws Exception {
System.out.println("服务端启动");
ServerSocket ss = new ServerSocket(8888);//注册端口号,建立连接
//调用accept方法,等待客户端连接,一有客户端连接会返回一个Socket对象
Socket s = ss.accept();
//获取客户端发送的数据
InputStream is = s.getInputStream();
DataInputStream dis = new DataInputStream(is);
System.out.println(dis.readInt());
System.out.println(dis.readUTF());
//获取客户端端口与IP
System.out.println("客户端IP:"+s.getInetAddress().getHostAddress()+" 客户端端口:"+s.getPort());
}
}
//____________多发多收_______________
public class TCPCM {
public static void main(String[] args) throws Exception {
//实现TCP通信多发多收
Socket s = new Socket("localhost", 8888);
System.out.println("客户端启动");
OutputStream os = s.getOutputStream();
DataOutputStream dos = new DataOutputStream(os);
Scanner sc = new Scanner(System.in);
//循环发送
while (true) {
System.out.print("请输入要发送的内容:");
String msg = sc.nextLine();
if ("exit".equals(msg)) {
System.out.println("客户端退出");
dos.close();//管道关闭,释放资源
s.close();
break;
}
dos.writeUTF(msg);
dos.flush();//刷新数据
}
}
}
public class TCPSM {
public static void main(String[] args) throws Exception {
System.out.println("服务端启动");
ServerSocket ss = new ServerSocket(8888);
Socket s = ss.accept();
//获取客户端发送的数据
InputStream is = s.getInputStream();
DataInputStream dis = new DataInputStream(is);
//收消息
while (true) {
try {//使用try-catch,当客户端断开连接时,程序不会因为 EOFException 而崩溃
String msg = dis.readUTF();//等待读取客户端发送的数据
if ("exit".equals(msg)) {
System.out.println("服务端退出");
dis.close();//管道关闭,释放资源
s.close();
break;
}
System.out.println("客户端:"+msg);
System.out.println("客户端IP:"+s.getInetAddress().getHostAddress()+" 客户端端口:"+s.getPort());
}catch (Exception e)
{
System.out.println("客户端已经断开连接");
break;
}
}
}
}
//____________同时接收多个客户端消息_______________
//客户端不变、服务端多线程处理
public class TCPSM {
public static void main(String[] args) throws Exception {
System.out.println("服务端启动");
ServerSocket ss = new ServerSocket(8888);
//收消息
while (true) {
Socket s = ss.accept();
System.out.println("有客户端连接"+s.getInetAddress().getHostAddress());
//把客户端管道交给一个独立子线程处理
new ServerReader(s).start();
}
}
}
public class ServerReader extends Thread{
private Socket s;
public ServerReader(Socket s){
this.s = s;
}
@Override
public void run(){
try {
//获取客户端发送的数据
InputStream is = s.getInputStream();
DataInputStream dis = new DataInputStream(is);
//使用try-catch,当客户端断开连接时,程序不会因为 EOFException 而崩溃
while (true) {
try {
String msg = dis.readUTF();//等待读取客户端发送的数据
if ("exit".equals(msg)) {
System.out.println("服务端退出");
dis.close();//管道关闭,释放资源
s.close();
return;
}
System.out.println("客户端:"+msg);
System.out.println("客户端IP:"+s.getInetAddress().getHostAddress()+" 客户端端口:"+s.getPort());
}catch (Exception e)
{
System.out.println("客户端"+s.getInetAddress().getHostAddress()+"已经断开连接");
return;
}
}
}catch (Exception e) {
e.printStackTrace();
}
}
}
(三)、B/S架构原理
HTTP协议规定:响应给浏览器的数据格式必须满足一定的格式
协议版本 空格 状态码 空格 状态符 回车换行 HTTP/1.1 200 OK\t
头部字段名:值; 回车换行 Content-Type:text/html;charset=UTF-8\t
....... 回车换行 \t
头部字段名:值; 回车换行
必须单独换一行
响应正文(真正给浏览器展示的网页数据) <html>...</html>
浏览器不能每次请求都创建一个线程,可以使用线程池优化,把所有Socket管道包装成任务队列。
//使用线程池优化,ServerReader还是使用Thread线程(自包含Runnable线程)
public class TCPSM {
public static void main(String[] args) throws Exception {
System.out.println("服务端启动");
ServerSocket ss = new ServerSocket(8888);
//创建线程池
ExecutorService pool= new ThreadPoolExecutor(3, 5, 1000,
TimeUnit.SECONDS,new ArrayBlockingQueue<>(100),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
//收消息
while (true) {
Socket s = ss.accept();
System.out.println("有客户端连接"+s.getInetAddress().getHostAddress());
//把客户端管道包装成一个任务对象交给线程池处理
pool.execute(new ServerReader(s));
}
}
}
Junit单元测试框架
单元测试:针对最小功能单元:方法,编写测试代码对其进行正确性测试。
优点:可以针对某个方法执行测试,可以针对整个方法执行测试。各自独立,会自动生成测试报告。
步骤:
- 导入Junit的jar包到项目中(IDEA集成了)
- 为需要测试的业务写对应的测试类,为每个业务方法写测试方法(公共,无参,无返回值)
- 测试方法上必须声明
@Test注解,然后在测试方法中编写代码调用被测试的业务方法测试。 - 开始测试:选中测试方法,测试运行,测试通过为绿色,失败为红色。
- 测试的正确性取决于测试用例的完整性。
四、反射
1、反射:加载类,并允许以编程的方法解剖类中的各种成分(成员变量、方法、构造器等)
2、反射获取类的信息:加载类(Class对象),获取构造器(Constructor对象)、成员变量(Field对象)、方法(Method对象)。
反射获取类的信息:
法一:Class c=类名.class
法二:Class c=Class.forName("包名.ClassName")
法三:Class c=对象.getClass()
获取构造器对象:ClassName.getConstructors( )等方法获取成员变量对象:ClassName.getFields( )等方法
获取方法对象:ClassName.getMethods( )等方法
绕过私有:对象名.setAccessible(true)
获取构造器对象作用:反射可以动态创建对象,并且可以调用对象的方法。
类名 对象名=(类名) 构造器名.newInstance()获取成员变量对象作用:反射可以修改对象的属性。
变量对象名.setField(属性名,属性值)获取方法对象作用:反射可以调用对象的方法。
Object 对象名=方法对象名.setMethod(方法名,参数类型,参数值)
3、反射作用:可以得到一个类的全部成分然后操作,反射可以破坏封装性,可以绕过泛型的约束,适合做java的高级框架(如SpringBoot)
import java.lang.reflect.Method;
import java.util.ArrayList;
public class getClass {
public static void main(String[] args) {
// 获取Class本身
Class c1 = User.class;
System.out.println(c1);
//获取类本身:Class.forName("类的全类名")
try {
Class c2 = Class.forName("com.rasion.reflect.User");
System.out.println(c2);
System.out.println(c1 == c2);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
//获取类本身:对象.getClass()
User user = new User();
Class c3 = user.getClass();
System.out.println(c3);
System.out.println(c1 == c3);
//获取类的构造器对象:Class.getConstructors()
Constructor[] classes = c1.getDeclaredConstructors();
for (Constructor constructor : classes) {
System.out.println(constructor.getName() + ": " + constructor);
}
//无参构造器
try {
System.out.println(c1.getConstructor().getName() + ": " + c1.getConstructor());
} catch (Exception e) {
e.printStackTrace();
}
//获取一个参数的有参构造器
try {
Constructor constructor = c1.getConstructor(String.class);
System.out.println(constructor.getName() + ": " + constructor);
} catch (Exception e) {
e.printStackTrace();
}
//强转User的private无参构造器为公共的构造器
Constructor con = c1.getDeclaredConstructor();
con.setAccessible(true);//私有构造器使用权限
User user = (User) con.newInstance();
System.out.println(user);
//利用反射绕过泛型的约束
ArrayList<String> list = new ArrayList<>();
list.add("a");
Class clazz = list.getClass();
Method add = clazz.getMethod("add", Object.class);//改变泛型方法add的参数类型为Object类型
add.add(list,1234);
System.out.println(list);
}
}
public class reflectKuangJia {
//反射做简易框架
public static void main(String[] args) throws Exception{
P p = new P("rasion", 18);//自定义了一个P类
save(p);
User u = new User("TUP");
save(u);
}
public static void save(Object obj) throws Exception{
PrintStream out = new PrintStream(new FileOutputStream("resource/hello.txt", true));
//只有反射知道对象有多少字段
Class c = obj.getClass();
Field[] fields= c.getDeclaredFields();//成员变量
for(Field f:fields){
f.setAccessible(true);
out.println(f.getName()+"="+f.get(obj));
}
out.println("-------------------");
out.close();
}
}
五、注解
注解:java代码内的特殊标记,如:@Override,@Test,让其他程序根据注解信息来决定怎么执行程序。
应用场景:Junit框架的测试方法注解@Test。
@Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyTest {//模拟@Test注解
int count() default 1;// 注解运行次数默认一次
}
public class Myte {
public static void main(String[] args) throws Exception{
//获取MyTest注解对象
Myte mt = new Myte();
//获取类对象
Class clazz = Myte.class;
//遍历所有方法,判断方法上是否有MyTest注解,有则执行
for(Method m : clazz.getDeclaredMethods()){
//获取方法上的注解
Annotation[] ans = m.getAnnotations();
//遍历注解,判断注解类型是否为MyTest
for(Annotation an : ans){
if(an instanceof MyTest){
//获取注解对象,并调用方法
MyTest mt1 = (MyTest)an;
for(int i = 0; i < mt1.count(); i++){
m.invoke(mt);
}
}
}
}
}
@MyTest
public static void a1(){
System.out.println("方法一");
}
public static void a2(){//不加注解,不运行
System.out.println("方法二");
}
@MyTest(count = 3)//运行三次
public static void a3(){
System.out.println("方法三");
}
}
(一)、自定义注解
格式:public @interface 注解名{public 属性类型 属性名() default 默认值;}
特殊属性:value,如果注解中只有一个value属性,或其他属性都是默认值时,使用注解,value名可以不写。
@MyC(name="rasion",age=18)//不可以省略名
@My("rasion")
public class main {
public static void main(String[] args) {
// TODO Auto-generated method stub
}
}
public @interface MyC {
String name;
int age();
}
public @interface My {
String value();
int age() default 18;// 默认值为18
}
//原理
public interface my extends Annotation{
public abstract String value();
public abstract int age();
}
(二)、元注解
元注解:注解注解的注解,即放在注解上面的注解。
常用元注解:
-
@Retention(RetentionPolicy.RUNTIME):保留注解到编译器运行时,即运行时也能获取注解信息。
SOURCE:只保留在源文件中,编译时丢弃;CLASS:保留在class文件中,运行时也保留;RUNTIME:保留在class文件中,运行时也保留,可以通过反射获取注解信息。 -
@Target(ElementType.TYPE):声明被修饰的注解只能修饰在哪些位置使用TYPE:类,接口;FIELD:成员变量;METHOD:方法;PARAMETER:方法参数;LOCAL_VARIABLE:局部变量;CONSTRUCTOR:构造器;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,ElementType.METHOD})//注解限定范围为方法与类
//以上两个为元注解
public @interface MyAnnotation {}
(三)、注解的解析
即:判断类、方法、成员变量上是否存在注解,并把注解内部解析出来
public class mainTest {
@Test
public void parseClass(){//解析类注解
//获取类对象
Class clazz = main.class;
//判断类上是否有注解
if(clazz.isAnnotationPresent(My.class)){
//获取注解对象
Annotation my = (My) clazz.getDeclaredAnnotation(My.class);
// My my = (My) clazz.getAnnotation(My.class);//与上面的语句一样
//获取注解的值
System.out.println(((My) my).value());//强转
System.out.println(((My) my).age());
}
}
}
六、动态代理
动态代理:在运行时,根据接口创建代理对象,代理对象调用接口方法,实际调用的是代理对象的方法。
AOP切面:在代理代码中把目标代码类包围,在目标代码前后添加一些代码,从而实现代理,这就是切面编程思想。
| 代理 | 接口 | 目标对象类 |
|---|---|---|
| A方法(..){前置准备} | 接口(..){A方法;B方法;} | A方法(..){真正的方法)} |
| B方法(..){前置准备} | B方法(..){真正的方法)} |
代理创建过程:
目标对象类创建:工具类继承接口,重写方法
接口创建:创建目标对象方法的接口方法
代理创建:
修饰 接口名 getInstance(目标对象类 目标对象){//可以改成泛型方法
接口名 对象名=(接口名) Proxy.newProxyInstance(目标对象.getClass().getClassLoader(),
目标对象.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy,Method method, Object[] args)
{代理需要做的事情 return method.invoke(目标对象,args);}
});
return 对象名;}对象的创建:
接口名 对象名=代理名.getInstance(目标对象);
public class User implements UserMapper {//目标对象类
private String name;
private String password;
private String email;
//.......省略
@Override
public User add(User user) {//目标对象实现方法
System.out.println("添加用户成功");
return user;
}
@Override
public void delete(String email) {System.out.println("删除用户成功");}
@Override
public void update(User user) {System.out.println("修改用户成功");}
}
public interface UserMapper {//接口
User add(User user);
void delete(String email);
void update(User user);
}
public class UserProxy {//代理类
public static <T> T getInstance(T t) {
/**
* 参数一、用于执行用哪个类加载器加载生成的代理类
* 参数二、要代理的接口——>new Class[]{UserMapper.class}或者s.getClass().getInterfaces()
* 参数三、代理类要做的事
*/
T usm=(T) Proxy.newProxyInstance(t.getClass().getClassLoader(),
t.getClass().getInterfaces(),
new InvocationHandler() {
/**
* 参数一、proxy接收到代理对象本身
* 参数二、正在被调用的方法
* 参数三、被代理的方法参数
*/
@Override
public Object invoke(Object proxy,
Method method, Object[] args) throws Throwable {
//声明代理要做的事情
if("add".equals(method.getName()))
System.out.println("add");
else if("delete".equals(method.getName()))
System.out.println("delete");
else if("update".equals(method.getName()))
System.out.println("update");
//调用被代理的方法
return method.invoke(s,args);
}
});
return usm;
}
}
public class main {//main方法调用代理对象
public static void main(String[] args) {
User user = new User("rasion", "123456", "rasion@qq.com");
//创建代理对象
UserMapper proxy = UserProxy.getInstance(user);
System.out.println(proxy.add(user));
proxy.delete("rasion@qq.com");
proxy.update(user);
}
}
七、学习链接
StringBuilder
拼接字符串的时候,使用StringBuilder,性能更好。
public class StringBuilde {
public static void main(String[] args) {
//拼接字符串用"+",如果是大量拼接,效率极差
//String 的对象是不可变量,共享数据时性能可以,但是当修改数据时性能差
// String s="";
// for(int i=0;i<100000;i++){
// s=s+"a";
// }
// System.out.println(s);
//定义字符串可以使用String 类型,但是操作字符串使用StringBuilder(性能好)
StringBuilder sb=new StringBuilder();//默认长度为16
for(int i=0;i<100000;i++){
sb.append("a");
}
System.out.println(sb);
//StringBuilder只是提供拼接字符串的手段,结果还是要恢复成字符串的
//将对象内容反转:sb.reverse();
String s=sb.toString();
System.out.println(s);
//支持链式编程
StringBuilder sb2=new StringBuilder("hello").append("world").append("java");
System.out.println(sb2);
}
}
BigDecimal
用于解决浮点型运算,出现结果失真问题,把小数包装成BigDecimal对象解决
public class bigdecimal {
public static void main(String[] args) {
double a = 0.1;
double b = 0.3;
System.out.println(a + b);
//把小数包装成BigDecimal对象
//必须使用BigDecimal(String val)字符串构造器
// BigDecimal bd1 = new BigDecimal(Double.toString(a));
BigDecimal bd1 = BigDecimal.valueOf(a);
BigDecimal bd2 = BigDecimal.valueOf(b);
//目的是要把BigDecimal对象转成double
System.out.println(bd1.add(bd2).doubleValue());
//除法,要做精度运算(四舍五入),精确到小数后4位
System.out.println(bd1.divide(bd2, 4, BigDecimal.ROUND_HALF_UP).doubleValue());
}
}