JAVA (多线程、网络编程、反射、注解、代理)

一、介绍

Java学习之A4:多线程、网络编程、反射、注解、代理。

二、多线程

(一)、线程的创建

多线程指:多个线程同时执行,但每个线程都有自己的执行路径,互不影响(由CPU负责调度执行)。

  1. 多线程的创建方式一:继承Thread类,重写run()方法,然后分配和启动子类的实例(p.start();)。
  2. 多线程的创建方式二:声明一个实现Runnable接口,实现run()方法,然后创建Thread实例并作为参数传入实现Runnable接口的实例(new Thread(new RunnableImpl()).start();)。
  3. 多线程的创建方式三:使用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单元测试框架

单元测试:针对最小功能单元:方法,编写测试代码对其进行正确性测试。

优点:可以针对某个方法执行测试,可以针对整个方法执行测试。各自独立,会自动生成测试报告。

步骤:

  1. 导入Junit的jar包到项目中(IDEA集成了)
  2. 为需要测试的业务写对应的测试类,为每个业务方法写测试方法(公共,无参,无返回值)
  3. 测试方法上必须声明@Test注解,然后在测试方法中编写代码调用被测试的业务方法测试。
  4. 开始测试:选中测试方法,测试运行,测试通过为绿色,失败为红色。
  5. 测试的正确性取决于测试用例的完整性

四、反射

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();
}

(二)、元注解

元注解:注解注解的注解,即放在注解上面的注解。

常用元注解:

  1. @Retention(RetentionPolicy.RUNTIME):保留注解到编译器运行时,即运行时也能获取注解信息。
    SOURCE:只保留在源文件中,编译时丢弃;

    CLASS:保留在class文件中,运行时也保留;

    RUNTIME:保留在class文件中,运行时也保留,可以通过反射获取注解信息。

  2. @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);
   }
}

七、学习链接

  1. 黑马程序员Java课程
  2. A4部分代码仓库

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());
    }
}
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

友情链接更多精彩内容