Java深入浅出之反射&集合&可见性&NIO

前言

java的一些特性、集合框架概要、线程安全的可用性说明、NIO网络编程

一、特性

1.抽象类和接口的区别

  1. 定义
  • 抽象类:用abstract修饰的类,一个类没有包含足够多的信息来描述一个具体的对象
    • 有构造器
    • 接口方法可以有public、protectd、default修饰
    • 单个继承
  • 接口:抽象类型,抽象方法的集合。
    • 无构造器
    • 接口方法只能由public修饰
    • 多重实现

抽象类可以有实例变量,而接口不能拥有实例变量,接口中的变量都是静态(static)的常量(final)

  1. 默认的方法实现
  • 抽象类:可以有方法实现
  • 接口: 没有(java8后可以使用default关键字添加非抽象方法)
  1. 实现
  • 抽象类:使用extends继承,并且如果不是抽象类,则需要实现所有方法
  • 接口:使用implements实现,并且需要实现所有方法
  1. 速度
  • 抽象方法比接口快

2.反射

反射的操作都是在编译之后,即运行时

动态的获取类的信息和调用其方法

  1. Class类的使用

    类是对象,是java.lang.Class类的实例对象
    任何一个类都是Class的实例对象

  • 三种类实例对象(类类型)的表示方式

    1
    2
    3
    4
    5
    6
    7
    // 1.任何一个类都有一个隐含的静态成员变量
    Class c1 = Person.class;
    // 2.类的对象
    Class c2 = person.getClass();
    // 3.通过类的全路径
    Class c3 = Class.forName("com.mango.Person");
    c1 == c2 == c3
  • 通过类的类类型创建实例对象

    1
    Person person = c1.newInstance();
  1. 动态加载类

Class.forName()代表动态加载类
编译时加载类是静态加载类,运行时是动态加载类

  1. 获取方法信息

基本的数据类型、void都存在类类型

  • Class

  • Method

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    Class c1 = int.class;Class c2 = String.class;
    // 1.类的名称:全称、简称c1.getName();
    c2.getSimpleName();
    // 2.Method类,方法对象:一个类方法就是一个Method对象
    Method[] methods = c1.getMethods();
    // 2.1方法的返回值类型的类类型
    Class returnType = methods[0].getReturnType();
    // 2.2方法的名称
    String methodName = methods[0].getName();
    // 2.3方法的参数列表类型的类类型
    Class[] paramTypes = methods[0].getParameterTypes();
    String paramTypeName = paramTypes[0].getName();
  1. 获取属性构造方法信息

属性也是对象

  • Field

  • Constructor

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    // cl.getFields() 获取的public的属性
    // 1.获取所有该类属性
    Field[] fields = c1.getDeclaredFields();
    // 1.1获取属性类型的类类型
    Class fieldType = fields[0].getType();
    // 1.2获取属性名称
    String fieldName = fields[0].getName();

    // cl.getConstructors()获取的public的构造方法
    // 2.获取类的构造方法
    Constructor[] constructors = c1.getDeclaredConstructors();
    String constructorName = constructors[0].getName();
    // 2.1获取构造方法参数列表的类类型
    Class[] paramTypes = constructors[0].getParameterTypes();
    String paramTypeName = paramTypes[0].getName();
  1. 方法反射的操作

方法的名称和方法的参数列表才能唯一决定某个方法
反射的操作:method.invoke(对象,参数列表)

  • 获取方法:c.getMethod(方法名,参数列表);

  • 方法调用:method.invoke(对象,参数列表)

    1
    2
    3
    4
    5
    6
    // 1.获取类类型
    Class c = person.getClass();
    // 2.获取具体方法
    Method method = c.getMethod("getAge",new Class[]{int.class});
    // 3.调用
    Object returnValue = method.invoke(person,23);
  1. 通过反射了解集合泛型的本质

编译之后集合的泛型是去泛型化
集合的泛型,是防止错误输入的,只在编译阶段有效

  • 反射的操作都是在编译之后

    1
    2
    3
    4
    5
    6
    // 1.创建两个集合
    ArrayList list = new ArrayList();ArrayList list2 = new ArrayList<String>();
    // 2.反射操作
    Class c1 = list.getClass();
    Class c2 = list2.getClass();
    c1 == c2

二、集合框架

单列集合根接口:Collection

双列集合:Map

1.List

  • 特性
    • 元素是有序的(存入和取出的顺序)、可重复的

1)ArrayList

  • 数据结构:数组
  • 线程不同步

2)LinkedList

  • 数据结构:链表(双向)
  • 线程不同步

3)Vector-效率低,废弃

  • 数据结构:数组
  • 线程同步

2.Set

  • 特性
    • 元素是无序的、不可重复的

1)HashSet

  • 数据结构:哈希表
  • 线程不同步

2)TreeSet

  • 数据结构:二叉树
  • 线程不同步

3)LinkedHashSet

  • HashSet的子类
  • 元素是有序的(插入和输出顺序一致)

3.Map集合

  • 特性
    • 存储形式是键值对,键是唯一的
  • 优缺点
    • 优点
      • 灵活性强,易扩展,耦合度低
      • 代码简单
    • 缺点
      • 存储的类型和内容不可知

1)Hashtable-废弃

  • 数据结构:哈希表
  • 线程同步

2)HashMap

  • 数据结构:哈希表
  • 线程不同步

3)TreeMap

  • 数据结构:二叉树
  • 线程不同步

声明集合线程安全,可以使用Collections工具类的方法Collections.synchronizedList、Collections.synchronizedSet、Collections.synchronizedMap

三、线程安全

1.可见性

  • 概述

    • 可见性:一个线程对共享变量值的修改,能够及时地被其他线程看到
    • 共享变量:如果一个变量在多个线程的工作内存中都存在副本,那么这个变量就是这几个线程的共享变量
    • 不可见的原因
      • 线程交叉执行
      • 重排序结合线程交叉执行
      • 共享变量更新后没有在工作内存与主内存同时刷新
  • Java内存模型:描述了java程序中各种变量(线程共享变量)的访问规则,以及在JVM中将变量存储到内存和从内存中读取出变量这样的底层细节

    • 所有变量都存储在主内存中
    • 每个线程都有自己独立的工作内存,里面保存该线程使用到的变量的副本(主内存的拷贝)
    • 规定
      • 线程对共享内存的操作只能在自己的工作内存中
      • 不同线程之间无法直接访问其他线程工作内存中的变量,线程间变量值的传递需要通过主内存来完成
  • 实现原理

    一个线程修改数据后,让其他线程能够看到

    1. 把该线程中的变量刷新(同步)到主内存中
    2. 将主内存中的最新变量值更新到其他线程的工作内存中
    3. 实现方式:synchronized、volatile、final
  • synchronized实现原理

    • 规定
      • 线程解锁前,必须把共享变量的最新值刷新到主内存中
      • 线程加锁时,将清空工作内存中共享变量的值,从而使用需要从主内存中冲洗读取最新的值
    • 线程执行互斥代码的过程

      1. 获得互斥锁
      2. 清空工作内存
      3. 从主内存拷贝变量的最新副本到工作内存
      4. 执行代码
      5. 将更改后的共享变量刷新到主内存
      6. 释放互斥锁

      重排序:代码书写顺序与实际执行的顺序不同,指令重排序是编译器或处理器为了提高程序性能而做的优化

      1. 编译器优化的重排序(编译器优化)
      2. 指令级并行重排序(处理器优化)
      3. 内存系统的重排序(处理器优化)

      as-if-serial:无论如何重排序,程序执行的结果都应该与代码顺序执行的结果一致(java编译器、运行时和处理器都会保证Java在单线程下遵循as-if-serial语义)

    • 作用

      • 原子性==>线程不会交叉执行
      • 原子性==>重排序是在单个线程内
      • 可见性==>规定加锁解锁数据的同步机制
  • volatile实现原理

    • 作用

      • 保证volatile变量的可见性
      • 不能保证volatile变量复合操作的原子性
    • 原理:加入内存屏障和禁止重排序优化

      • 对volatile变量执行写操作时,会在写操作后加入一条store屏障指令
      • 对volatile变量执行读操作时,会在读操作前加入一条load屏障指令
    • 过程

      • 写操作
        1. 改变线程工作内存中volatile变量副本的值
        2. 变量值刷新到主内存
      • 读操作
        1. 从主内存中读取volatile变量值
        2. 变量值同步到工作内存中
    • 解决非原子性问题

      • 使用synchronized

      • 使用ReentrantLock

        1
        2
        3
        4
        5
        6
        7
        private Lock lock = new ReentrantLock();
        // 加锁开始
        lock.lock()
        // 操作(try)

        // 加锁结束(只能在finally)
        lock.unlock();
      • 使用AtomicInteger

    • 场景(同时满足)

      • 对变量的写入操作不依赖当前值
        • 不满足:number++;count*=5;
        • 满足:Boolean变量
      • 该变量没有包含在具有其他变量的不变式中
        • 不满足:不变式 low<up
  • synchronized和volatile比较

    • volatile不需要加锁,更轻量级,非阻塞
      • 从内存可见性角度,volatile读相当于加锁,写相当于解锁
    • synchronized可以保证可见性和原子性,volatile只能保证可见性
  • 其他

    • 对64位变量(long、double)的读写可能不是原子操作(java内存模型允许jvm将没有被volatile修饰的64位数据类型的读写操作划分为两次32位的读写操作来进行)

2.ThreadLocal

  • 基础信息

    • 定义:提供线程局部变量;一个线程局部变量在多个线程中,分别有独立的值
    • 特点:简单(开箱即用)、快速(无额外开销)、安全(线程安全)
    • 场景:多线程场景(资源持有、线程一致性、并发计算、线程安全等)
    • 实现原理:java 使用哈希表
    • 应用范围:几乎所有提供多线程特征的语言
  • API

    • 构造:ThreadLocal<T>()
    • 初始化:initialVal()
    • 获取/设值:get/set
    • 回收:remove

四、java网络编程之NIO

简介:Non-Blockiing I/O 或New I/O

起始于JDK1.4,主要用于高并发网络服务

1.编程模型

  • 模型:对事物共性的抽象

  • 编程模型:对编程共性的抽象

  • 阻塞I/O:读取时线程阻塞

  • BIO网络模型

    • 流程

    1)服务器一直监听建立连接请求

    2)当客户端发起建立连接请求

    3)服务端启动新线程

    4)该线程与于客户端建立连接,响应客户端

    5)该线程一直等待客户端的请求,阻塞等待

    BIO网络模型

    • 弊端

      • 阻塞式I/O模型
      • 弹性伸缩能力差
      • 多线程耗资源
      • 高并发时,服务器可能崩溃
    • 基本使用

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      /**服务端 **/
      // 1.监听端口
      ServerSocket ss = new ServerSocket(8000);
      while(true){
      // 2.接收请求,建立连接
      Socket socket = ss.accept();
      // 3.数据交换
      new Thread(new BIOServerHandler(socket)).start();
      }
      ss.close();
      /**客户端 **/
      // 1.建立连接
      Socket s = new Socket("127.0.0.1",8000);
      // 2.获取输入输出流
      InputStream is = s.getInputStream();
      OutputStream os = s.getOutputStream();
  • NIO网络模型

    • 流程
      1. 服务端注册建立连接事件
        • 服务端有Selector组件,用于循环检测注册事件就绪情况
      2. 当客户端发起建立连接请求
      3. 服务端会启动建立连接事件处理器
        • Acceptor Handler处理器
      4. 处理器会创建与客户端连接
      5. 处理器响应客户端建立连接请求
      6. 处理器向Selector注册连续可读事件
        • 可多个
      7. 客户端发送请求到Selector
      8. Selector启动连接读写处理器
        • Read&Write Handler读写处理器
      9. 读写处理器处理客户端读写业务
      10. 读写处理器响应客户端请求
      11. 读写处理器向Selector注册连续可读事件

NIO网络模型

  • 改进(相比BIO)
    • 非阻塞I/O模型
    • 弹性伸缩能力强
    • 单线程节省资源
  • 弊端
    • 麻烦:NIO类库和API繁杂
    • 心累:可靠性能力补齐,工作量和难度大
    • 有坑:Selector空轮询,导致CPU 100%

2.NIO核心

  • Channel:通道

    1. 简介:双向性、非阻塞性、操作唯一性

    2. 实现

      • 文件类:FileChannel
      • UDP类:DatagramChannel
      • TCP类:ServerSocketChannel/SocketChannel
    3. 使用

      1
      2
      3
      4
      5
      6
      7
      8
      // 服务器通过服务端socket创建channel
      ServerSocketChannel ssc = ServerSocketChannel.open();
      // 服务器绑定端口
      ssc.bind(new InetSocketAddress(8000));
      // 服务器监听客户端连接,建立socketChannel连接
      SocketChannel sc = ssc.accept();
      // 客户端连接远程主机及端口
      SocketChannel sc = SocketChannel.open(new InetSocketAddress("127.0.0.1",8000));
  • Buffer:缓存区

    1. 简介
      • 作用:读写Channel中数据
      • 本质:一块内存区域
    2. 属性
      • Capacity:容量
      • Position:位置
      • Limit:上限
      • Mark:标记
  • Selector:选择器/多路复用器

    1. 简介

      • 作用:I/O就绪选择
      • 地位:基础
    2. 使用

      1
      2
      3
      4
      5
      6
      7
      8
      // 创建Selector
      Selector selector = Selector.open();
      // 将channel注册到selector上,监听读就绪事件
      SectionKey sk = channel.register(selector,SectionKey.OP_READ);
      // 阻塞等待channel有就绪事件发生
      int selectNum = selector.select();
      // 获取发生就绪事件的channel集合
      Set<SelectionKey> selectionKeySet = selector.selectedKeys();
    3. SelectionKey简介

      • 四种就绪状态常量
      • 有价值的属性

3.NIO实现步骤

  1. 创建Selector
  2. 创建ServerSocketChannel,并绑定监听端口
  3. 将Channel设置为非阻塞模式
  4. 将Channel注册到Selector上,监听连接事件
  5. 循环调用Selector的select,检测就绪情况
  6. 调用selectedKeys方法获取就绪channel集合
  7. 判断就绪状态种类,调用业务处理方法
  8. 根据业务需要决定是否再次注册监听事件,重复执行第三步操作
------ 本文结束感谢您的阅读 ------

本文标题:Java深入浅出之反射&集合&可见性&NIO

文章作者:MangoCheng

发布时间:2020年06月27日 - 10:05:54

最后更新:2020年06月27日 - 10:19:07

原始链接:http://mangocheng.com/posts/aaef87ba.html

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。