Java 进阶

Java

Java 的一些进阶知识,以及一些经常使用示例。java

Jvm

Jvm组成

Jvm 是个虚拟机,主要由:类装载系统、执行引擎、垃圾回收器组成。c++

执行引擎,是Java跨平台的核心,负责虚拟机与OS的指令交互工做。算法

类装载系统

  • 加载器工做过程

类加载的过程主要包括:加载、验证、准备、解析、初始化等阶段。sql

  1. 加载:将字节码文件读入虚拟机,并堆中生成一个class引用。
  2. 验证:利用加载完成的class验证文件正确性,以及父类子类是否冲突等。
  3. 准备:给类变量,以及存在默认值的成员分配空间。
  4. 解析:生成全部成员的引用,就是把指针连起来。
  5. 初始化:初始化全部静态变量,或存在默认值的变量,过一次静态代码块。

1~4 都是 c++ 程序在执行,5为java执行程序。编程

  • 加载器分类
  1. 启动类加载器:引导类装载器进入虚拟机,相似Linux内核文件,c++。
  2. 扩展类加载器:加载指定Jre目录文件,造成 java基本运行环境,java。
  3. 系统类加载器: 加载指定路径的字节码文件,虚拟机开机自启程序。
  4. 自定义加载器:这里须要加载的是未定义的,动态class文件。
  • 双亲委派原则

Java 除顶层加载器以外,类加载器是一种子->父的单向循环过程。
主要是为了保证类不会被加载第二次,相似于一种层级的环境变量。数组

类加载器的主要过程就是重复以下操做:
先到当前class查询缓存是否存在此类,存在则返回,不存在将加载请求抛给父类。缓存

当顶层类依然不存在此类的时候:安全

// 自定义类加载器--> 遵循双亲委派原则 通常都用这个
public class ExpLoder extends ClassLoader{
    // 顶层找不到类的时候,会来这里
    @Override
    protected Class<?> findClass(String name){
        return null;
    }
    // 将字节流转化为,jvm可识别的文件
    @Override
    protected final Class<?> defineClass(byte[] b,int off,int len){
        return defineClass(null, b, off, len, null);
    }
}

// 自定义类加载器 --> 不遵循双亲委派原则
public class ExpLoder extends ClassLoder{

    // 重写这个方法会直接破坏双亲原则
    @Override
    protected Class<?> loadClass(String name,boolean resolve){
        //...
    }
}
  • 线程上下文类加载

自己是并发带来的问题,多个线程各自走双亲委派原则,致使类加载不一致。
因此线程上下文类加载,并不遵循双亲委派原则,而是直接给主线程去加载了。数据结构

垃圾回收器

Java 对象执行完毕以后,清除无用空间的操做,这里指堆空间。多线程

  • 通常性内存空间整理算法
  1. 标记清除:标记无用空间,统一清除,易产生碎片空间。
  2. 复制算法:将使用空间复制到一个新的区域,以后清除整片旧空间,太浪费了。
  3. 标记整理:标记无用空间,前移全部使用空间,覆盖无用空间,太耗时了。
  • 分代算法
  1. 新生代:每一个周期都有大量空间失效,因此用复制算法。

    Eden 区:存储 new 的对象,不少对象会当场去世。
    from 区:存储从 Eden 区存活下的对象。
    to 区:存储上 from 区活虾的对象。

    • 当 Eden区满了,则开启 GC,执行一次周期。
    • 再将 to 区存活对象,移动至老年代。
    • 再将 from 区存活对象,移动至 to 区域。
    • 先将 Eden 区存对象,移动至 from 区,再清除 Eden 区。
  2. 老年代:相对而言,每一个周期少许空间失效,通常用 标记-整理,或标记-清楚。

    空间满了,则开启GC,根据算法不一样选择不一样的策略。

  3. 元空间,之前叫永久代,受限于本地内存。

    string 常量区就在这里,相似于方法区这个东西。

  • 垃圾收集器
  1. 串行收集器 serial

    串行收集器会中止全部线程,简单且高效,适用于单线程、C/S的client端。
    新生代-复制,老年代-标记整理。

  2. ParNew

    自己就是串行收集器的多线程版本,没啥差异,中止全部线程,简单高效。

  3. CMS

    CMS 是一种基本的以空间换时间的收集器,追求最短响应时间。

    • 初始标记:中止全部线程,标记失效空间。
    • 并发标记:不会中止任何线程,而是开新线程去获取失效空间地址。
    • 从新标记:中止全部线程,将并发标记结果写入标记集合。
    • 并发清除:不会中止任何线程,开新线程清除失效的空间。

    CMS 会致使吞吐量降低,部分浮动垃圾没法清除,且碎片空间太多了。

  4. G1

    G1 是对 CMS 的一种改进版本。

    • 初始标记:短期中止全部线程,标记失效空间。
    • 并发标记:不会中止任何线程,开新线程获取失效空间地址。
    • 最终标记:停不停线程都行,将并发标记结果写入标记集合。
    • 筛选回收:停不停线程均可以,开新线程标记-总体失效空间。

    G1 相对于 CMS主要是,线程停顿时间可控,且采用标记-整理。
    G1 垃圾回收的性能由,线程停顿时间控制。

Jvm 内存

Jvm 内存结构,内存模型。

内存结构

Jvm 内存结构主要分为两大部分。

  • 进程区

进程区,亦称为线程共享区,全部线程均可访问。

  1. 堆区:存储对象用的,被垃圾收集器所管理。
  2. 方法区:主要存储类一级别的变量,包括类信息,静态变量,方法等,class对象引用在堆区,其指向的就是这里。
  3. 元空间:存储一些常量,String常量就是在这一个区域。
  • 线程区

线程区,亦称线程私有区,仅线程自身能够访问。

  1. 程序计数器:切换线程上下文所用的标识位,记录行号。
  2. 本地方法栈:一些OS方法,或其它语言的方法。
  3. 虚拟机栈:线程执行时用的栈,变量,对象引用,出口,函数连接等等。

内存模型

自己就是一个缓存模型,由主内存,工做内存,栈内存构成。

  • 变量类型
  1. 可被内存模型处理的变量仅包括了实例字段、静态字段和构成数组对象的元素。
  2. 不包括局部变量与方法参数,这些是线程私有的。
  • 线程通讯
  1. 任何一个线程都有本身的缓存,里面存放着经常使用变量,其余变量在主内存里。
  2. 线程之间的通讯,是其中一个线程将变量写入主内存里,另外一个去主内存拿。
  3. 线程之间的通讯必须在主内存里完成,线程之间是没法直接执行通讯的。

JMM通讯模型:files/jmm.png。

  • 操做说明
  1. lock:将一个变量标识为一条线程独占状态。
  2. unlock:将一个处于锁定状态的变量释放出来。
  3. read:将一个变量从主内存传输到线程的工做内存中,以便随后的 load 使用。
  4. load:将 read 操做从主内存中获得的变量值放入工做内存的变量副本中。
  5. use:将工做内存中的一个变量值传递给执行引擎,每当虚拟机遇到一个须要使用变量的值的字节码指令时将会执行这个操做。
  6. assign:把一个从执行引擎接收到的值赋值给工做内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操做。
  7. store:把工做内存中的一个变量的值传送到主内存中,以便随后的write的操做。
  8. write:把 store 操做从工做内存中一个变量的值传送到主内存的变量中。
  • 模型性质

被 volatile 的变量的全部读写都是在主存内执行的,且会全部操做会加锁。

  1. 原子性:全部内存模型操做都是原子性的,即不会干到一半不干了。
  2. 可见性:被 volatile修饰的变量,任何更改对多有线程都是可见的。
  3. 顺序性:被 volatile修饰的变量,禁止做内存优化,全部操做都是顺序的。

内存屏障

  • 问题所在

    1. 当下 cpu 通常都存在多级缓存,其查找变量的时候,先在一级缓存中寻找,继续在二级缓存之中寻找,如此类推。虽然缓存有不少,但内存终究就只有一个。多级缓存会带来一个数据不一致的问题。

    2. 在不一样 cpu 执行的不一样线程对同一个变量的缓存值不一样,为了解决这个问题。

  • 硬件协议

    通常为解决,多级缓存,以及多线程缓存不一致的问题,从 cpu 自己出发的 mesi 协议,可解决缓存不一致的问题。

    读屏障:在指令前插入读屏障,可让高速缓存中的数据失效,强制从主内存取。

  • Jvm内存屏障

Jvm是在代码指令中添加汇编指令,使用cpu的内存屏障协议保证数据的一致性。

1.阻止屏障两侧指令重排序,(Jvm有内存优化的)。
2.强制把写缓冲区/高速缓存中的脏数据等写回主内存,让缓存中相应的数据失效。

并发

并发的主要依靠多线程来解决,在这个过程当中须要锁来维持数据一致性。

锁主要的做用是保证线程安全,但会影响性能。

分类

  • 独占锁与共享锁

独占锁:即为写锁,亦称互斥锁,最多只能有一个线程获取此类锁。
共享锁:即为读锁,存在写操做时,只能有一个线程可获取锁;仅存在读操做时,可存在多个线程同时获取锁。

  • 悲观锁与乐观锁

悲观锁:默认会发生冲突,因此任何操做都加锁。
乐观锁:默认发生错误较少,因此仅在写操做时加锁。

  • 公平锁与非公平锁

公平锁:内部顺序获取锁,不会产生饥饿,但效率较低。
非公平锁:内部会根据规则抢锁,会产生饥饿,但效率高。

  • 重入锁

亦称递归锁,锁是以现称为单位获取获释放的。
当线程中某一部分获取锁后,至关于整个线程获取到了锁。
主要是保证了同一个锁,不会由于相同线程的争抢而致使发生死锁。

  • 自选锁

当线程获取锁被阻塞时,轮询获取锁。
优势是避免程序切换上下文,缺点是消耗cpu。

  • 引用锁,非引用锁

全部被 synchronized 锁住的都是引用类型。
全部被 lock 锁住的都是代码块。

synchronized & ReentrantLock

  • synchronized

悲观锁,非公平锁,重入锁。
只有类锁,对象锁,成员锁这三种引用锁,且是虚拟机级别的。

任何一个类,对象,成员都有一个内置锁,亦称监听器 monitor。
是一个互斥锁,即同时刻只能有一个线程得到某一级别的锁,其余线程阻塞。
但不一样线程或相同线程可得到不一样级别的锁,即为重入锁。

被synchronized修饰的代码都会根据条件获取指定锁,其余线程只能等锁释放

public class Example{
    static int k = 10;
    public int m = 10;
    public void show(){
        //...
    }
    synchronized static public void sh(){
        //...
    }
    synchronized public void show(){
        //...
    }
}
// 测试对象
Example exp = new Example();

// 同步 类,静态变量 或调用静态同步方法时,会加类锁。
synchronized(Example.class){};
synchronized(exp.k){};
sh();

// 同步对象,则加对象锁。
synchronized(exp){}

// 同步成员变量,或调用同步成员函数时,加成员锁。
synchronized(exp.m){}
show();
  • ReentrantLock

悲观锁,默认非公平锁,可设定为公平锁,可重入锁,。
核心为 AbstractQueueSynchronzer,AQS基于FIFO双向队列实现。

// 此时为,非公平锁
ReentrantLock lock = new ReentrantLock();
// 此时为,公平锁
ReentrantLock lock = new ReentrantLock(true);
  • 二者异同

相同之处:都是悲观锁,且都是可重入锁,都是阻塞式锁。

类型 加锁范围 公平锁 级别 锁问题
synchronized 类,对象,成员 非公平锁 虚拟机级别 写锁
ReentrantLock 代码区 公平锁,非公平所均可以 线程级别 可设定读写锁
  • volatile

volatile 亦是一种线程同步的方式,主要是线程通讯。

  • 其余

synchronized 由一对 monitorenter/moniterexit 字节码指令实现。
java 锁竞争的基本原则,会根据金证程度来使用权限。
偏向锁->轻量级锁->重量级锁。

线程

多线程是提升并发量、吞吐量的主要手段。

线程同步

  • 线程同步

线程同步的方式,可根据消耗按照以下三种方式排序。

// 利用 volatile 同步线程
public class Example implements Runnable{

    // 被 volatile 修饰的变量,写操做后必须刷新变量的值
    static volatile int k = 20;
    @Override
    public void run(){
        while(true){
            System.out.println(Thread.currentThread().getName()+":"+k--);
            try{
                Thread.currentThread().sleep(1000);
            }catch(InterruptedException e){
                e.printStackTrace();
            }
        }
    }
}

// 利用 synchronized 同步数据
public class Example implements Runnable{

    static int k = 20;
    @Override
    public void run(){
        while(true){
            // 成员锁,仅限定这个成员的访问
            show();
            try{
                Thread.currentThread().sleep(1000);
            }catch(InterruptedException e){
                e.printStackTrace();
            }
        }
    }
    // 同步方法
    synchronized private void show(){
        System.out.println(Thread.currentThread().getName()+":"+k--);
    }

}

import java.util.concurrent.locks.ReentrantLock;

// 并发量很大的时候,用 ReentrantLock 锁同步代码区
public class Example implements Runnable{

    // 同步测试
    static int k = 20;
    // 同一时间,被 lock 的代码区,只能由一个线程来执行
    Lock lock = new ReentrantLock();

    @Override
    public void run(){
        while(true){
            try{
                // 同步锁开始
                lock.lock();
                // 输当前线程名称,k的值,以后再 k--
                System.out.println(Thread.currentThread().getName()+":"+k--);
            }catch(Exception e){
                e.printStackTrace();
            }finally{
                // 同步锁结尾
                // 放在这里是为了防止发生异常后直接锁死整个线程组
                lock.unlock();
                try{
                    // try catch 块 真他妈是个让人头疼的东西。
                    Thread.currentThread().sleep(1000);
                }catch(InterruptedException d){
                    d.printStackTrace();
                }
            }
        }
    }
}
Example exp = new Example();

// 并发执行多线程,同步数据。
new Thread(exp,"Thread-A").start();
new Thread(exp,"Thread-B").start();
new Thread(exp,"Thread-C").start();
new Thread(exp,"Thread-D").start();

线程池

  • 多线程弊端

多线程并发除了可能会影响数据一致性以外,亦会因线程自己而引起异常。

  1. 实时新建、销毁、切换线程,自己就很是消耗Cpu。
  2. 可能存在须要短期内new大量线程,易发生OO,引起宕机。
  3. 线程缺少管理,好比定时执行,按期执行,定时中断等等。
  • 概念

线程池就是首先建立一些线程,它们的集合称为线程池。
线程池能够很好地提升性能,线程池在系统启动时即建立大量空闲的线程。
程序将任务传给线程池,线程池就会启动一条线程来执行这个任务。
执行结束之后,该线程并不会死亡,而是再次返回线程池中成为空闲状态,等待执行下一个任务。

  • 工做机制

在线程池的编程模式下,任务是提交给整个线程池,而不是直接提交给某个线程。
线程池在拿到任务后,就在内部寻找是否有空闲的线程,若是有,则将任务交给某个空闲的线程。

一个线程同时只能执行一个任务,但能够同时向一个线程池提交多个任务。

线程池示例

线程池管理接口。

  • 缓存线程池

一般用于执行生存周期较短的异步任务。

当任务来的时候,会检测是否有被缓存的线程,有则用缓存的线程,无则新增线程。
全部正在执行的线程都是核心线程,全部被缓存的线程都是非核心线程。
全部被缓存的线程都有超时机制,超过 60秒没有被引用,线程会被回收。

// 初始化线程池,此时线程池内无线程,以后新增的线程都会被缓存,缓存后可复用
ExecutorService cache = Executors.newCachedThreadPool();

for(int i=0; i<10; i++){
    Thread.sleep(1000);
    // 新增线程-->至线程池中
    cache.execute(new Runnable(){
        @Override
        public void run(){
            System.out.println(Thread.currentThread.getName()+"-running");
            try{
                Thread.sleep(1000);
            }catch(InterruptedException e){
                e.printStackTrace();
            }
        }
    });
}
// 虽然循环 10 次,但实际只有 2 个线程。
  • 固定大小的线程池

内部拥有固定数量的可重用线程。

全部线程都是核心线程,不会被回收,任务超过线程上限则进入队列等待。

// 获取系统核数,利用这个建立固定大小的线程池
int corenum = Runtime.getRuntime().availableProcessors();
// 建立 固定数量线程的线程池
ExecutorService fix = Executors.newFixedThreadPool(corenum);

for(int i=0; i<100; i++){
    fix.execute(new Runnable(){
        @Override
        public void run(){
            System.out.println(Thread.currentThread.getName()+"-running");
            try{
                Thread.sleep(1000);
            }catch(TnterruptedException e){
                e.printStackTrace();
            }
        }
    });
}
  • 延迟的周期性线程池

通常为固定数量的可重用线程,外加额外线程,且线程都是周期性执行任务。

其核心线程数量是固定的,空闲亦不会被回收,而非核心线程执行完毕则当即回收。

// 建立固定数量的线程池
ScheduledExecutorService sched = Executors.newScheduledThreadPool(5);
for(int i = 0; i<5; i++) {
    sched.scheduleAtFixedRate(new Runnable() {
        @Override
        public void run() {
            System.out.println("延迟执行" + Thread.currentThread().getName());
        }
    }, 5, 1, TimeUnit.SECONDS);
}
// 此处含义为,建立任务后 5 秒以后开始周期性执行,每一个周期间隔 1 秒
  • 单线程式线程池

内部只有一个线程,且全部任务会按照 FIFO 顺序执行。

内部只有一个核心线程,空闲亦不会被回收。

// 内部只有一个线程
ExecutorService single = Executors.newSingleThreadExecutor();
for(int i = 0;i < 10;i++){
    // 任务执行顺序不会改变
    single.execute(new Runnable() {
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName()+"-running");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    });
}
自定义线程池

其上所展现的四种线程池都来自这个类,只不过参数被肯定了。

类型 建立 核心线程
缓存线程池 newCachedThreadPool() 核心线程与非核心线程动态变化
固定线程池 newFixedThreadPool(int n) 全部线程都是核心线程
单线程线程池 newSingleThreadExecutor() 只有一个核心线程
周期性线程池 newScheduledThreadPool(int n) 固定的核心线程,动态变化的非核心线程
  • 线程池核心类
// 线程池原始定义方法
public ThreadPoolExecutor(
    // 核心线程数量
    int corePoolSize,
    // 最大线程数量,当核心线程满了以后,会查询这个值来肯定是否新增线程
    int maximumPoolSize,
    // 线程总数大于核心线程数量时,多余空闲线程最多空闲时间,超时则释放资源
    long keepAliveTime,
    // keepAliveTime 时间单位
    TimeUnit unit,
    // 超出核心线程时,多余任务的存储位置
    BlockingQueue<Runnable> workQueue,
    // 建立新线程时使用的工厂
    ThreadFactory threadFactory,
    // 当线程池达到上限之时,所要执行的策略
    // 当任务队列达到最大值被阻塞,且总线程数量达到最大值时,即为上限
    RejectedExecutionHandler handler
    ) {
    //...
}
  • 线程池通常策略

线程总数 < 核心线程:新开核心线程,任务进入线程。

线程总数 = 核心线程:若存在空闲的核心线程,任务进入线程,不然进入队列。

队列到达上限,线程总数 < 线程最大值:新增非核心线程,任务进入队列。

队列到达上限,线程总数 = 线程最大值:执行饱和策略。

// 此处主要是来存储,超出执行的任务
BlockingQueue<Runnable> task = null;
// 内部是数组,数组必有界
task = new ArrayBlockingQueue<>(100);
// 内部是链表,链表内存是动态化的,但也可设置上限,即有界、无界皆可
task = new LinkedBlockingQueue<>();
// 无缓冲的队列,无界
task = new SynchronousQueue<>();

// 任务到达上限时所要执行的 饱和策略
RejectedExecutionHandler rejection = null;

// 忽略新增任务,新增则抛异常,亦为默认值
rejection = new ThreadPoolExecutor.AbortPolicy();
// 忽略新增任务,新增不会抛异常
rejection = new ThreadPoolExecutor.DiscardPolicy();
// FIFO,删除队列头部任务,以后继续新增
rejection = new ThreadPoolExecutor.DiscardOldestPolicy();
// 新增任务失败后,主线程本身去执行,不要用这玩意儿
// 由于新增任务都须要主线程,才可进入队列或直接进入线程
// 且队列内的任务,都须要主线程分配给各个线程才可执行
rejection = new ThreadPoolExecutor.CallerRunsPolicy();
  • 自定义示例
BlockingQueue<Runnable> task = new SynchronousQueue<Runnable>();
RejectedExecutionHandler rej = new ThreadPoolExecutor.AbortPolicy();
ThreadFactory th = Executors.defaultThreadFactory();
// 核心线程池
ExecutorService core = new ThreadPoolExecutor(6,18,60,TimeUnit.SECONDS,task,th,rej);

/*
 * 线程池
 * execute 实现 Runnable接口建立任务
 */
core.execute(new Runnable(){
    @Override
    public void run(){
        System.out.println("Runnable--execute");
    }
});


Future<?> ex = core.submit(new Runnable(){
    @Override
    public void run(){
        System.out.println("running");
        System.out.println(0/0);
    }
});


/*
 * 线程池
 * submit 实现 Callable<Object> 接口建立任务,存在返回值
 * Future 类能够获取,submit 任务的返回值,但会阻塞至任务结束。
 * Future submit Ruuuable 能获取到的只有异常,或null
 * Future submit Callable 能够获取到异常以及程序返回值
 */
Future<?> res = core.submit(new Callable<Integer>(){
    @Override
    public Integer call(){
        System.out.println("Runnable-submit");
        return 1;
    }
});

// 此处会阻塞至,res执行完毕,才会有结果
System.out.println(res.get());
  • 线程池异常

Java 全部线程池中的任务发生异常后,整个线程会被回收。

// 新建线程池
ExecutorService core = Executors.newFixedThreadPool(3);

// execute Runnable 系列没法捕获异常,线程会被回收
core.execute(new Runnable{
    @Override
    public void run(){
        System.out.println("runnable -- Exception");
        // 此处会发生异常
        System.out.println(10/0);
    }
});

// submit Runnnable 可捕获异常,线程依然会被回收
Future exp = core.submit(new Runnable(){
    @Override
    public void run(){
        System.out.println("runnable -- Exception");
        // 此处会发生异常
        System.out.println(10/0);
    }
});

// 此处能够捕获线程执行发生的异常
try{
    System.out.println("exception"+exp.get());
}catch(Exception e){
    e.printStackTrace();
}
特殊操做

Future 是面向任务的一种类,可控制当前任务。
submit 通常配合 Future 使用。
Callable<?>存在返回值,可抛出异常,仅在线程池中使用。

// 新建线程池
ExecutorService core = Executors.newFixedThreadPool(3);

/*
 * Future submit callable 系列可控制一个完成的线程生命
 */
Future res = core.submit(new Callable<Integer>{
    // callable 通常仅在线程池中使用
    @Override
    public Integer call(){
        System.out.println("submit future");
        return 1;
    }
})

// Future 可获取当前任务的全部信息
System.out.println(res);

/*
 * Future 类亦能够中途取消执行任务
 * true 可强行中断,false 不可强行中断
 * 返回值,取消结果
 */
Boolean ok = res.cancel(true);

/*
 * Future 可获取线程执行结果
 * Future 可捕获,任务线程执行时发生的异常
 */
try{
    // res.get() 便可获取线程执行结果,阻塞式获取
    System.out.println(res.get());
}catch(Exception e){
    e.printStackTrace();
}
线程变量
  • 局部变量

ThreadLocal,线程级别的HashMap,存储线程局部变量。

  1. 默认新开线程时,此线程即会被建立。
  2. 可用在多线程共享变量。
  • 数据结构

主要是一些线程安全的数据结构。

Synchronized:全部被修饰的变量都是线程安全的。
StringBuffer:线程安全的字符串结构。
BlockQueue:线程安全的队列。
Vector:线程安全的数据结构。
Collections.synchronizedList:将列表转化为线程安全的,但内部锁粒度过大。
ConcurrentHashMap:内部是分段锁,不会把整个表锁起来。
SynchronizedMap/Hashtable:线程安全,但锁粒度太大。

IO

主要介绍常见的IO知识点。

IO流

  • 通常IO

主要分为字节流、字符流。

字节流:即为二进制数据流。
字符流:即为char[]流,通常须要字符编码格式,默认utf-8。

  • 核心概念

同步:一种可靠的时序操做,执行操做后,后续任务须要等待。
异步:执行操做后,后续任务无需等待,通常经过事件、回调等方式实现。

阻塞:这是线程的状态,仅当条件容许时,线程会进入就绪状态等待执行。
非阻塞:无论 IO是否结束,该返回直接返回。

  • IO模型

IO,亦称BIO,是一种同步且阻塞的IO方式,默认IO都是这个模型。
NIO 是一种同步非阻塞的IO方式。
AIO 是一种异步非组赛的IO方式。

BIO:读写时,会阻塞线程,只有操做执行完毕时才会返回结果。
NIO:读写时,不会阻塞线程,只有操做执行完毕时才会返回结果。
AIO:读写时,不会阻塞线程,执行结果会分段返回。

经常使用类

主要是一些类的经常使用示例。

Java8 新特性

主要介绍下 Java-8的一些特色。

1.语法糖

// 语法糖,本质上源代码没变,只是减小了代码量
// lambda 系列
new Thread(()->{System.out.println("lambda");}).start();
// lambda 若一行能够写完,则无需;
new Thread(()->System.out.println("lambda")).start();

2.接口默认方法

// 接口内部能够存在默认方法,且能够被重写
public interface Exp{
    default void show(){
        System.out.println("interface-default-function");
    }
}

3.函数式接口

函数式接口,就是一个接口默认一个方法,能够即时定义使用。
调用形式有一种,面向切面编程的思想。

// 函数式接口是一种特殊的接口

// 必须被 FunctionalInterface 注解标注,且内部只能存在一个方法
@FunctionalInterface
public interface Exp{
    public void show();
}

// 定义 接口内的 show 方法
Exp exp = () -> System.out.println("Function interface");
exp.show();

@FunctionalInterface
public interface Example{
    public int sh(int k);
}

// 不一样的函数接口,就是不一样的切面,且此处 return 可省略
Example example = (x) -> x++ ;
Example exp = (x) -> x*10;

System.out.println(example.sh(10)+"-"+exp.sh(10));

// java8 自带一些函数式接口
Consumer<String> ex = (x) -> System.out.println(x);
ex.accept("hello");

Suppiler<String> ex = () -> "hello";
System.out.println(ex.get());

// 这里就是动态代理
public void proxyFun(Consumer<String> consumer,String msg){
    consumer.accept(msg);
}
// 函数式编程模式
proxyFun((x)->System.out.println(x),"123465");

4.时间API

// 新增一组时间API
Clock clock = Clock.systemDefaultZone();
System.out.println(clock.millis());

//...

Date date = Date.from(clock.instance());

5.Stream

增长了部分相似,sql语法的方法。

6.多重注解

容许对注解单位加多重注解,面向多切面编程。

常见运算

  • Number

此下的方法适用于Number的全部子类。

// 将 string 解析为 Number
Integer.parseInt("123");
// 将 Number 转化为 String
Integer.toString(123);
// 比较两个 Number 的大小,相等 0 ,小于 -1,大于 1
Integer.compare(1,2);
  • Math

此下主要包括一些经常使用的计算方法。

// Number 求绝对值
Math.abs(-1000);
// Number 四舍五入
Math.round();
// Number 四舍五入 保留一位小数
Math.ceil();

// Math.max(); Math.min();
// ...

String

String 是最为通用的类型。

  • Character

char 类型能够看做是 int 类型。

// char 类型能够看做特殊的类型
char a = 100;
// 将 char 转化为长度为一的string
Character.toString(a);
  • String

String 是一个特殊的类型,实际是个常量,只要改变至关于一次新建。

// 将 Number 类型转化为 String
String p = String.valueOf(123);

String p = "abc";
// p 转大写
p = p.toUpperCase();
String p = "ABC";
// p 转小写
p = p.toLowerCase();

// 相等为 0,adc-abd -1,abc-abb 1
p.compareTo("ABD");

// 比较 String 是否相同
"sd".equals("sd");

// 比较 String 是否相同,忽略大小写
"SD".equalsIgnoreCase("sd");

// ...
  • String 常量池

常量池中的变量都是 final,即不可更改的变量,自己是一个char数组。

  1. String p = "hellow"; 此种方式建立string的时候,先到常量池中搜索,存在相同的string则直接返回引用,不然在常量池之中建立相应的 string,并返回引用。

  2. String p = new String("hellow"); 此种方式建立string的时候,先在常量池中搜索,若存在则直接返回引用,不然在堆区建立相应的对象。

// 此种方式建立的string即是在常量池中间的变量
String p = "hellow";
// p q 都指向常量池中的一个变量
String q = "hellow";
  • StringBuilder & StringBuffer

不一样于 String 类型,其自己是对象,任何操做都是针对对象自己,不会出现疯狂召唤GC的状况。

  1. StringBuilder:线程不安全。
  2. StringBuffer:线程安全,其全部方法都被 synchronized 所修饰。

时间类型

时间类型是最为基本的经常使用类型。

// 获取时间对象
Date date = new Date();

// 这里获取的是一个完整的时间对象,通常用不到
Calendar calendar = Calendar.getInstance();

// 这里获取的就只是时间对象
Date cal = Calendar.getInstance().getTime();

// 时间戳 13 位
date.getTime();
Calendar.getInstance().getTimeInMillis();
System.currentTimeMillis();

// 时间格式化类型
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

// 格式化时间,Date对象便可
sdf.format(date);
sdf.format(cal);

流类型

Java 自己分为字符流、字节流,以下为基本的抽象类。

流类型 in out
字节流 InputStream OutputStream
字符流 Reader Writer
// 常见流
FileInputStream input;
FileOutputStream output;
FileReader reader;
FileWriter writer;

//序列化的流
FileOutputStream out = new FileOutputStream("./exp.txt");
ObjectOutputStream obj = new ObjectOutputStream(out);
obj.write(new Object());
obj.close();
out.close();

// 反序列化的流
FileInputStream in = new FileInputStream("./exp.txt");
ObjectInputStream obj = new ObjectInputStream(in);
Object exp = obj.readObject();
obj.close();
in.close();

// IOException

集合类型

集合类型是一种经常使用数据结构类型,动态存储空间,且面向对象设计。

Collection

Collection接口采用线性列表的方式存储数据。

  • List

List接口用于存储有序,可重复的数据。

/*
 * ArrayList<?> 实现类
 * 优势:实际结构是动态数组,查询快,增删慢。
 * 缺点:线程不安全。
 */
ArrayList<String> list = new ArrayList<String>();

/*
 * Vector<?> 实现类
 * 优势:实际结构是动态数组,查询快,增删慢。
 * 缺点:线程安全。
 */
Vector<String> vector = new Vector<String>();

/*
 * LinkedList<?> 实现类
 * 优势:实际结构是链表,查询慢,增删快。
 * 缺点:线程不安全。
 */
LinkedList<String> linklist = new LinkedList<String>();

/*
 * Collections.synchronizedList(),静态化方法
 * 此方法的做用是将,任何基于List接口实现的类转化为线程安全的类。
 */
List safelist = Collections.synchronizedList(list);

// 新增 移除 索引 ...
  • Set

Set接口用于不可重复的数据。

/*
 * HashSet<?> 实体类
 * 内部就是个 hash表,依赖 hashCode(),equals()方法保证惟一性。
 * 主要存储 无序,不重复的数据。
 */
HashSet<String> set = new HashSet<String>();

/*
 * LinkedHashSet<?> 实体类
 * 内部是一个由 hash表,和链表结构组成。
 * 主要存储 有序,不可重复的数据。
 */
LinkedHashSet<String> linkset = new LinkedHashSet<String>();

/*
 * TreeSet<?> 实体类
 * 内部就是大名鼎鼎的红黑树。
 * 主要存储 有序,不可重复的数据。
 */
TreeSet<String> tree = new TreeSet<String>();
  • Queue

Queue接口提供阻塞式,以及非阻塞式队列。

/*
 * BlockingQueue<?> 接口
 * 这个接口下的队列都是阻塞式的。
 */
BlockingQueue blockqueue;

/*
 * ConcurrentLinkedQueue<?> 实体类
 * 这个类提供非阻塞式的队列。
 */
ConcurrentLinkedQueue<String> q = new ConcurrentLinkedQueue<String>();

Map

Map 接口采用键值对映射的方式存储数据。

/*
 * HashMap<?,?> 实体类
 * 内部就是 hash表。
 */
HashMap<String,String> map = HashMap<String,String>();

/*
 * LinkedHashMap<?,?> 实体类
 * 内部键值映射为 hash表,外部键排序去重为链表。
 * 是一个键有序的,hash表。
 */
LinkedHashMap<String,String> m = new LinkedHashMap<String,String>();

Thread

java 线程,其优势在于并发,缺点在于相较于协程其更复杂且占用资源较大。

  • Runnable 接口

Runnable 接口核心在于,执行线程资源申请,并在新资源中间执行线程。
Runnable 接口内仅定义 run方法,线程管理须要手动定义。

// Runnable 接口实现过程
public class Example implements Runnable{
    // 线程核心方法
    @Override
    public void run(){
        while(true){
            //...
        }
    }
}

// 实例化对象,内部没有定义线程管理
Example exp = new Example();
// 没有线程管理策略,仅能直接打开线程,其余操做作不了
exp.run();

// Runnable 接口,管理
public class Demo implements Runnable{

    private boolean flag = false;

    public Demo(){
        //...
    }

    // 线程核心方法
    @Override
    public void run(){
        while(true){
            //...
            if(this.flag){
                break;
            }
        }
    }

    // 此时打开线程
    public void start(){
        this.run();
    }

    // 此时会直接退出线程而并不是挂起线程
    public void stop(){
        this.flag = true;
    }
}
// 实例化对象,Demo内部实现了部分线程管理
Demo demo = new Demo();
// 开线程
demo.start();
// 关闭线程,线程处于死亡状态
demo.stop();
  • Thread

Thread 类通常须要重写 run()方法来使用。

public class Example extends Thread{
    @Override
    public void run(){
        //...
    }
}

// Thread 类自带线程管理方法
new Example().start();

public class Demo extends Runnable{
    @Override
    public void run(){
        //...
        try{
            // Thread.currentThread(),静态方法获取当前线程引用
            Thread.currentThread().sleep(1000);
        }catch(InterruptedException e){
            e.printStackTrace();
        }
    }
}

Demo demo = new Demo();

// 利用 Thread类以及实现Runnable接口的类的对象建立线程
// 线程能够命名,也不命名
Thread th = new Thread(demo,"thread-1");
// 建立线程,命名线程,开启线程
new Thread(demo).start();

// 简写开线程,name字段能够不加
new Thread(
    new Runnable(){
        @Override
        public void run(){
            //...
        }
    }
).start();

// lambda 简写建立线程
new Thread(() -> { while(true){System.out.println(123);} }).start();

特殊

一些特殊的类。

特殊数据结构

Hash 亦称哈希,是一种将任意字符串转化为定长字符串,本质是一种压缩映射。
Hash 亦属于数字签名,或称信息摘要算法中的一个。

Object obj = new Object();

// java 中任何一个对象都有其相应的 hash 值。
obj.hashCode();
  • HashMap

Map 是一种 key:value 键值对形式的对象。

1.底层原理

默认是一个数组,且数组内部可嵌套其余数据结构。

// 1. 获取 hashCode()
int hash = obj.hashCode();

// 2. 用数组长度对 hash 取模运算,主要是获取数组下标
int i = hash%len;
//    或执行与运算,数组长度要减 1,现版是这个,但效果和取模同样
int i = hash&(len-1);

// 3. 根据 i 存入数组内部,此处是链表
//    当后续新的对象已被存到这里时,加入链表便可
//    当链表长度超 8,则变为红黑树,否则查询太慢
//    当红黑树实际元素少于 6,则退化为链表
// 4. 获取对象到这里时,利用  equals 方法便可

2.哈希碰撞与扩容

哈希碰撞即为两个对象哈希值相同而产生的异常。 HashMap 数组默认 16,键值对 64。 超过最大阈值 75%,则扩容,空间大一倍。

相关文章
相关标签/搜索