java 基础


java 基础

class 文件内容

class 文件包含java 程序执行的字节码,数据严格按照格式紧凑排列在class文件中的二进制流,中间无任何分隔符; 文件开头有一个0xcafebabe(16进制) 特殊的一个标志。

jvm 运行时数据区

.java源代码->.class 字节码->线程共享部分(方法区,堆内存)线程独占部分(虚拟机栈,本地方法栈,程序计数器) ->执行引擎->本地库接口->本地方法库

线程独占:

每个线程都会有它独立的空间,随线程生命周期而创建和销毁

线程共享:

所有线程能访问这块内存数据,随虚拟机或者GC而创建和销毁

方法区:

jvm 用来存储加载类信息,常量,静态变量,编译后的代码等数据 虚拟机规范中这是一个逻辑区划,具体实现根据不同虚拟机来实现 如:oracle 的hotspot 在java7中方法区放在永久层,java8放在元数据空间,并通过GC机制来对这个区域管理

堆内存:

细分为老年代,新生代(Eden,From Survivor,To Survivor) jvm 启动时创建,存放对象的实例,垃圾回收期主要就是管理堆内存。 如果满了,就会出现OutOfMemroyError,

虚拟机栈

每个线程都在这个空间有一个私有的空间,线程栈由多个栈帧组成,一个线程会执行一个或多个方法,一个方法对应一个栈帧 栈帧包含:局部变量表,操作数栈,动态链接,方法返回地址,附加信息等 栈内存默认最大是1M,超出则抛出StackOverflowError

本地方法栈

和虚拟机栈功能类似,虚拟机栈为虚拟机执行java方法准备的,本地方法栈是为了虚拟机使用native本地方法而准备的 虚拟机规范没有规定具体的视线,由不同的虚拟机厂商去实现 HotSpot虚拟机中虚拟机栈和本地方法栈的实现是一样的额,同样,超出大小以后也会抛出stackoverflowerror

程序计数器

程序计数器记录当前线程执行字节码的位置,存储是字节码的指令地址,如果执行native方法,则计数器值为空。 每个线程都在这个空间有一个私有的空间,占有内存空间很少。 cpu同一时间,只会执行一条线程中的指令。jvm多线程会轮流切换并分配cpu执行时间的方式,为了线程切换后,需要 通过程序计数器来恢复正确的执行位置 | 标志名称 | 标志值 | 含义 | | ------------ | ------------ | ------------ | | ACC_PUBLIC | 0x0001| public? | | ACC_FINAL | 0x0010 |final? 只有類 | | ACC_SUPER | 0x0020 | invokespecial ? | | ACC_INTERFACE | 0x0200 |接口 | | ACC_ABSTRACT | 0x400 |abstract ? | | ACC_SYNTHETIC |0x1000 | 非由用戶產生 | | ACC_ANNOTATION | 0x2000 |這是一個註解 | |ACC_ENUM|0x4000|這是一個枚舉| 類信息包含的静态常量,编译之后就能确认

class 内容

没有构造函数,会自动生成构造函数

main 方法

访问控制,本地变量数量,参数数量,方法对应栈中操作数的深度。jvm 执行这源码编译后的指令码 javap 翻译是操作符, class文件存储是指令码,前面是数字,是偏移量(字节) jvm根据这个区分不同的指令。

线程状态 java.lang.thread.state

  1. new 尚未启动的线程的线程状态
  2. running 可以运行线程的线程状态,等待cpu 调度
  3. blocked 线程阻塞等待监视器锁定的线程状态 处于synchronized 同步代码快或者方法被阻塞
  4. waiting 等待线程的线程状态,下列不带超时的方式:Object.wait,Thread.join,LockSupport.park
  5. TimedWaiting 具有等待时间的等待线程的线程状态: Thread.sleep,Object.wait,Thread.join,LockSupport.parkNanos,LockSupport.parkUntil
  6. Terminated:线程终止状态,线程正常完成执行或者出现异常

注意点:如果一个线程终止之后,再进行调用,会抛出IllegalThreadStateException 异常

线程终止

  1. thread.interrupt 正确的线程终止 如果目标线程在调用Object class 的wait(),wait(long)或wait(long,int)方法,join(),join(long int)或者 sleep(long,int)方法时被阻塞,那么 interrupt会生效,该线程的终端状态被清除,抛出interruptedException异常。

    如果目标线程是被I/O,或者NIO的channel 所阻塞,同样I/O操作会被中断或者返回特殊异常值,达到终止线程的目的。 如果以上条件都不满足,则会设置此线程的中断状态 2. 标志位 通过状态判断是否执行

cpu 缓存和内存屏障

l1 cache ,l2 cpu 外部高速储存器 ,l3 内置的,文件系统缓存。共享一个L3

缓存同步协议

多cpu读取同样的数据进行缓存,进行不同运算之后,使用MESI协议,规定每条缓存有个状态,同时定义了下面4个状态:

  1. 修改态(Modified)此cache行已被修改过(脏读) 内容不同于主存,为此cache专有。
  2. 专有态(exclusive) 此cache行内容同于主存,但不出现于其他cache中。
  3. 共享态(shared)此cache行内容同于主存,但也出现于其他cache中
  4. 无效态(invalid) 此cache行内容无效(空行)

多处理器时,单个cpu对缓存中数据进行了改动,需要通知给其他cpu,也就是说 CPU处理要控制自己的读写操作,还要监听其他cpu发出的通知,从而保证最终一致。

CPU 性能优化的手段,运行时指令重排

指令重排的场景,当cpu写缓存发现缓存区块正被其他cpu占用,为了提供cpu处理性能,可能会将后面的读缓存命 令优先执行。 当然要遵守as-if-serial 语意,不管怎么重排序单线程程序的执行结果不能被改变,编译器和处理器不会对存在数据 依赖关系的操作做重排序。

cpu 告诉缓存下有一个问题

缓存中数据和主内存的数据并不是实时同步,各个cpu 间缓存的数据也不是实时同步,在同一个时间点,各个cpu所看到 统一内存地址的数据的值可能是不一致的。

cpu 指令重排序

单仅在单cpu 自己执行的时候保证

内存屏障

写内存屏障(store barrier)能让写入缓存中的最新数据更新写入主内存,让其他线程可见,强制写入主内存,这种显示调用,cpu就不会因为性能考虑去对指令重排

读内存屏障(loadmemory barrier) 可以让告诉缓存中的数据失效,强制从新从主内存加载数据,强制读取主内存内容,让cpu 缓存与主内存保持一致,避免了缓存导致的一致性问题

通信的方式

要实现多个线程之间的协同,如:线程执行先后顺序,获取某个线程执行的结果等等,设计到线程之间相互通信,分为4种: 1.文件共享,2.网络共享,3.共享变量 4.jdk 提供的线程协调API :wait/notify,park/unpark

suspend 和resume

这两个容易产生死锁,改为 wait/notify

wait/notify

  1. 这些方法只能由同一对象锁的持有者线程电泳,也就是写在同步块里面,否则就会抛出illegalMonitorStateException 异常
  2. wait 方法导致当前线程等待,加入该对象的等待集合中,并且放弃当前持有的对象锁,notify/notifyAll 方法唤醒一个或所有正在等待这个对象锁的线程。

注意: 虽然wait 会自动解锁,但是对顺序有要求,如果在notify被调用之后,才开始wait方法的调用,线程将会永远处于waiting 状态。

park/unpark 机制

线程调用Park 则等待许可,unpark 方法为指定线程提供“许可(permit)" 不要求park 和unpark 方法的调用顺序 多次调用unpack之后,再调用park,线程会直接运行。 但不会叠加,连续多次调用park方法,第一次会拿到许可直接运行,后续调用会进入等待

伪唤醒

建议应该在循环中检查等待条件,原因是出于等待状态的线程可能会收到错误警报和伪唤醒,如果不在循环中检查等待条件,程序就会在没有满足条件的情况下退出。 伪唤醒指的是线程并非因为notify,notifyall,unpark等API调用而唤醒,是更底层原因导致的。

//wait
synchronized(obj){
    while(<条件判断>)
        obj.wait();
}
//park
while(<条件判断>)
    LockSupport.park();

线程封闭概念

  1. 多线程访问共享可变数据时,涉及到线程间数据同步的问题,并不是所有时候,都要用到共享数据,所以线程封闭的概念提出来。
  2. 数据都被封闭在各自的线程之中,就不需要同步,这种通过将数据封闭在线程中而避免使用同步的技术成为线程封闭。

线程封闭具体的提现有:ThreadLocal,局部变量

ThreadLocal

ThreadLocal 是Java 里一种特殊的变量

它是一个线程级别变量,每个线程都有一个ThreadLocal就是每个线程都有了自己独立的一个变量,竞争条件被彻底消除了,在并发模式下是绝对安全的变量。 用法:ThreadLocal var = new ThreadLocal(); 会自动在每一个线程上创建一个T 的副本,副本之间彼此独立,互不影响。 可以用ThreadLocal存储一些参数,以便于线程中多个方法中使用,用来代替方法传参的做法。 可以理解为,JVM 维护了一个Map ,每个线程要用这个T的时候,用当前的线程去Map里面取。仅作为一个概念理解。

栈封闭

局部变量的固有属性之一就是封闭在线程中,他们位于执行线程的栈中,其他线程无法访问这个栈。

为什么要用线程池

线程是不是越多越好?

  1. 线程在java中是一个对象,更是操作系统的资源,线程创建,销毁需要时间,如果创建和销毁的时间大于执行时间就不合算
  2. java 对象占有栈内存,操作系统线程占用系统内存,根据jvm 规范,一个线程默认最大栈大小1M。这个栈空间是需要从系统内存中分配的,线程过多,会消耗更多的内存。
  3. 操作系统需要频繁切换线程上下文,影响性能。 线程池的退出,就是为了方便的控制线程数量。

线程池原理

  1. 线程池管理器:用于创建并管理线程池,包括创建线程池,销毁线程池,添加新任务。
  2. 工作线程:线程池中线程,在没有任务时处于等待状态,可以循环的执行任务;
  3. 任务接口: 每个任务必须实现的接口,以供工作线程调度任务的执行,它主要规定了任务的入口,任务执行完后的收尾工作,任务的执行状态等;
  4. 任务队列: 用于存放没有处理的任务,提供一种缓冲机制。

线程池API 接口定义和实现类

  1. 接口,executor 最上层的接口,定义了执行任务的方法execute
  2. 接口, executorService 继承了executor,拓展了callable future 关闭方法
  3. 接口,scheduledExecutorService 基础标准的线程池实现
  4. 实现类,ThreadPoolExecutor 基础标准的线程池实现
  5. 实现类,ScheduledThreadPoolExecutor 继承了ThreadPoolExecutor,实现了ScheduledExecutorService 相关定时任务方法。

线程池API

检测executorService 是否已经关闭,直到所有任务完成执行,或超时发生,或当前线程被中断,awaitTermination(long timeout,timeUnit unit)
执行给定的任务集合,执行完毕后,返回结果, invokeAll(Collection<? extends Callable<T>> tasks)
执行给定的任务集合,执行完毕或超时后,返回结果,其他任务终止  invokeAll(Collection<? extends Callable<T>> tasks,long timeout,TimeUnit unit)
执行给定的任务,任意一个执行成功则返回结果,其他任务终止  invokeAny(Collection<? extends Callable<T>> tasks)
执行给定的任务,任意一个执行成功或者超时后,则返回结果,其他任务终止,invokeAny(Collection<? extends Callable<T>> tasks,long timeout,Timeunit unit)
如果此线程已关闭,则返回true isShutdown()
如果关闭后所有任务都已完成,则返回true ,isTerminated()
优雅关闭线程池,之前提交的任务将被执行,但是不会接受新的任务 shutdown()
尝试停止所有正在执行的任务,停止等待任务的处理,并返回等待执行任务的列表 shutdownNow()
提交一个用于执行的callable返回任务,并返回一个Future,用于获取Callable执行结果 submit(Callable<T>Task)
提交可运行任务以执行,并发回一个Future对象,执行结果为null,submit(Runnable task)
提交可运行任务以执行,并返回Future,执行结果为传入的result, submit(Runnable task,T result)

ScheduleExcutorService

schedule(Callable<V> callable,long delay,Timeunit unit) 和 schedule(Runable command,long delay,Timeunit unit) 创建并执行一个一次性任务,过了延迟时间就会被执行
scheduleAtFixedRate(Runnable command,long initialDelay,long period,Timeunit unit) 
    创建并执行了一个周期性任务
    过来给定的初始延迟时间,会第一次被执行
    执行过程汇总发生了一场,那么任务被停止
    一次任务执行时长超过了周期时间,下一次任务会等到该次任务执行结束后,立刻执行,这也是它和scheduleWithFixedDelay的重要区别
scheduleWithFixedDelay(Runnable command,long initialDelay ,long delay,TimeUnit unit)
    跟上面的区别是在上一次任务执行结束的时间基础上,再计算执行延时,上面超过周期就会立刻执行。