java基础与多线程总结
Java基础
- 面向对象编程的三大特性
- 封装 对外部内部细节是不可见的,暴露外界的只是他的方法接口
- 继承
- 多态 变量所指向的具体类型和通过该变量发出的方法调用在编程时是不确定的,而是在程序运行期间才能确定。
- 覆盖
- 只能比父方法抛出更少的异常
- 访问权限只能比父亲大
- 标识和父方法完全一致
- 父方法不能为private,final
- 重载
- 通过不同的参数样式,参数个数,参数顺序来重载
- 不能通过返回类型,访问权限,抛出的异常来重载
- 对于继承来说,不能重载访问权限为private的
- 抽象类和接口 一个类可以只能继承一个类,但可以多个方法,一个接口可以继承多个接口
- 接口
- 接口中的变量都为public static final 可以用来当常量类使用
- 不能有构造方法
- 方法都为abstract的,且只能为public,1.8中default 有默认实现
- 不能有静态方法,1.8也有静态方法了
- 抽象类
- 抽象方法不能是private的
- 可以含有非抽象方法
- 接口
- 内部类
- 成员内部类 一个类的内部
- 局部内部类 定义在一个方法里或者一个作用域里面,与成员内部类主要区别是作用域仅限于方法或作用域内部
- 匿名内部类 常用于事件监听代码里
- 静态内部类
- 面向对象的6大基本原则
- 单一职责 一个类最好只做一件事,高内聚低耦合的引申
- 依赖倒置 高层模块不依赖底层模块
- 接口隔离 接口实现专门的功能,而不是使用一个大接口
- 里式替换 子类能够替换基类,对继承的约束
- 开放封闭 面对修改封闭,对扩展开放
- 迪米特法则 一个对象应尽可能的对其他对象少的了解 ,门面模式
- 死锁
- 4个必要条件
- 互斥 资源只能被一个进程占用
- 请求保持 请求阻塞时不释放所拥有资源
- 不可抢占 占有资源在未使用完时不可被抢占
- 循环等待
- 破坏方法
- 破坏互斥,使资源可以共享
- 破坏请求保持
- 申请资源必须一次性申请到所有资源
- 申请资源前必须释放所占有资源
- 破坏不可抢占
- 占有资源的进程进一步申请资源时被拒,必须释放所占有资源
- 一个进程申请另一个进程的资源时可以抢占另一个的资源,进程需有优先级
- 破坏循坏等待
- 资源有序分配,申请资源需按顺
- 4个必要条件
- 银行家算法
- 单例模式7种实现方式
- 私有构造方法
- 静态实例
- 同步方法
- 双重校验锁
- 静态内部类
- 枚举单例 反射不可破坏
- 容器单例模式
- 垃圾回收算法
- 引用计数算法:给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;任何时刻计数器都为0的对象就是不再被使用的,垃圾收集器将回收该对象使用的内存。
- 根搜索算法:通过一系列的名为“GC Root”的对象作为起点,从这些节点向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Root没有任何引用链相连时,则该对象不可达,该对象是不可使用的,垃圾收集器将回收其所占的内存
- 垃圾收集算法
- 标记-清除算法
- 最基础的垃圾收集算法,算法分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成之后统一回收掉所有被标记的对象。
- 最基础的垃圾收集算法,算法分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成之后统一回收掉所有被标记的对象。
- 复制算法
- 将可用内存按容量分成大小相等的两块,每次只使用其中一块,当这块内存使用完了,就将还存活的对象复制到另一块内存上去,然后把使用过的内存空间一次清理掉。这样使得每次都是对其中一块内存进行回收,内存分配时不用考虑内存碎片等复杂情况,只需要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效。
- 复制算法的缺点显而易见,可使用的内存降为原来一半
- 标记-整理算法
- 标记-整理算法在标记-清除算法基础上做了改进,标记阶段是相同的标记出所有需要回收的对象,在标记完成之后不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,在移动过程中清理掉可回收的对象,这个过程叫做整理
- 标记-整理算法相比标记-清除算法的优点是内存被整理以后不会产生大量不连续内存碎片问题。
- 复制算法在对象存活率高的情况下就要执行较多的复制操作,效率将会变低,而在对象存活率高的情况下使用标记-整理算法效率会大大提高。
- 分代收集算法
- 根据内存中对象的存活周期不同,将内存划分为几块,java的虚拟机中一般把内存划分为新生代和年老代,当新创建对象时一般在新生代中分配内存空间,当新生代垃圾收集器回收几次之后仍然存活的对象会被移动到年老代内存中,当大对象在新生代中无法找到足够的连续内存时也直接在年老代中创建。
- 标记-清除算法
- 现在的商业虚拟机都采用复制收集算法来回收新生代,新生代中的对象98%都是“朝生夕死”的,所以并不需要按照1:1的比例来划分内存空间,而是将内存分为一块比较大的Eden空间和两块较小的Survivor空间,每次使用Eden和其中一块Survivor。当回收时,将Eden和Survivor中还存活着的对象一次性地复制到另外一块Survivor空间上,最后清理掉Eden和刚才用过的Survivor空间。HotSpot虚拟机默认Eden和Survivor的大小比例是8:1,也就是说,每次新生代中可用内存空间为整个新生代容量的90%(80%+10%),只有10%的空间会被浪费。当然,98%的对象可回收只是一般场景下的数据,我们没有办法保证每次回收都只有不多于10%的对象存活,当Survivor空间不够用时,需要依赖于老年代进行分配担保,所以大对象直接进入老年代。
- Minor GC:Minor GC是发生在新生代中的垃圾收集动作,采用的是复制算法。
- Full GC: Full GC是发生在老年代的垃圾收集动作,采用的是标记-清除/整理算法。
- java内存模型
- 程序计数器 保证线程切换后能恢复到原来的执行位置 线程私有
- 虚拟机栈 (栈内存)为虚拟机执行java方法服务:方法被调用时创建栈帧-->局部变量表->局部变量、对象引用 线程私有 StackOverFlowError和OutOfMemoryError。
- 本地方法栈 为虚拟机执使用到的Native方法服务 StackOverFlowError和OutOfMemoryError。
- 方法区 存储被虚拟机加载的类信息、常量、静态常量、静态方法等。运行时常量池(方法区的一部分)方法区叫做永久代 线程共享
- 堆内存 存放new出来的东西 ,即对象 java堆还可以初步细分为新生代和老年代。OutOfMemoryError 线程共享
- 类加载机制:双亲委派加载机制
- 双亲委派模型的工作流程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把请求委托给父加载器去完成,依次向上,因此,所有的类加载请求最终都应该被传递到顶层的启动类加载器中,只有当父加载器在它的搜索范围中没有找到所需的类时,即无法完成该加载,子加载器才会尝试自己去加载该类。
- 类加载过程 包括加载、链接(含验证、准备、解析)、初始化
- 加载:类加载指的是将类的class文件读入内存,并为之创建一个java.lang.Class对象,作为方法区这个类的数据访问的入口。
- 链接: 链接指的是将Java类的二进制文件合并到jvm的运行状态之中的过程。在链接之前,这个类必须被成功加载。
- 验证:验证是用来确保Java类的二进制表示在结构上是否完全正确(如文件格式、语法语义等)。如果验证过程出错的话,会抛出java.lang.VertifyError错误。主要验证以下内容:
- 文件格式验证
- 元数据验证:语义验证
- 字节码验证
- 准备:准备过程则是创建Java类中的静态域(static修饰的内容),并将这些域的值设置为默认值,同时在方法区中分配内存空间。准备过程并不会执行代码。
- 解析: 解析的过程就是确保这些被引用的类能被正确的找到(将符号引用替换为直接引用)。解析的过程可能会导致其它的Java类被加载。
- 验证:验证是用来确保Java类的二进制表示在结构上是否完全正确(如文件格式、语法语义等)。如果验证过程出错的话,会抛出java.lang.VertifyError错误。主要验证以下内容:
- 初始化:初始化阶段是类加载过程的最后一步。到了初始化阶段,才真正执行类中定义的Java程序代码,在以下几种情况中,会执行初始化过程:
- 创建类的实例
- 访问类或接口的静态变量(特例:如果是用static final修饰的常量,那就不会对类进行显式初始化。static final 修改的变量则会做显式初始化)
- 调用类的静态方法
- 反射(Class.forName(packagename.className))
- 初始化类的子类。注:子类初始化问题:满足主动调用,即父类访问子类中的静态变量、方法,子类才会初始化;否则仅父类初始化。
- java虚拟机启动时被标明为启动类的类
- Object的6方法
- clone()
- hashCode
- equals()
- toString()
- notify()
- notifyAll()
- 几个wait()方法
- finalize()
并发相关
- 一些概念
- 同步:在一个方法调用期间你不能做其他事,只是简单的等待
- 异步:在一个方法调用期间你可以做其他事,等待结果通知
- 并发:多个任务交替进行
- 并行:真正意义上的“同时进行”,一个CPU下无法实现
- 阻塞:一个线程占有了临界区资源,那么其他线程需要这个资源就必须进行等待该资源的释放,会导致等待的线程挂起
- 非阻塞:它强调没有一个线程可以阻塞其他线程,所有的线程都会尝试地往前运行
- 同步阻塞:等待方法执行完毕,线程挂起
- 同步非阻塞:在等待期间定时查看方法是否执行完毕,线程不挂起
- 异步阻塞:等待执行结果的通知,线程挂起
- 异步非阻塞:等待执行结果通知的同时,可以做其他事,线程不挂起
- 新建线程的3种方法
- 通过继承Thread类,重写run方法
- 通过实现runable接口
- 实现callable接口,利用future类,也可以利用futureTask类
- 线程中的几种操作
- 中断interrupt
- join()如果在一个线程A中执行了threadB.join(),其含义是:当前线程A会等待threadB线程终止后threadA才会继续执行
- sleep
- sleep与wait区别
- sleep()方法是Thread的静态方法,而wait是Object实例方法
- wait()方法必须要在同步方法或者同步块中调用,也就是必须已经获得对象锁。而sleep()方法没有这个限制可以在任何地方种使用。另外,wait()方法会释放占有的对象锁,使得该线程进入等待池中,等待下一次获取资源。而sleep()方法只是会让出CPU并不会释放掉对象锁;
- sleep()方法在休眠时间达到后如果再次获得CPU时间片就会继续执行,而wait()方法必须等待Object.notift/Object.notifyAll通知后,才会离开等待池,并且再次获得CPU时间片才会继续执行。
- sleep与wait区别
- wait如上,可以被中断
- notify 唤醒线程,使用前提是获得锁,notify/notifyAll() 唤醒线程并不会立即释放锁,而是继续执行
- yield: 一旦执行,它会是当前线程让出CPU,让出的时间片只会分配给当前线程相同优先级的线程。如果下次竞争中该线程又获得了cpu会继续执行,即yield后线程可能会继续执行
- 线程间通讯的方法
- synchronized 利用同一个同步变量
- wait,notify,join实现
- 管道通信就是使用java.io.PipedInputStreamjava.io.PipedOutputStream进行通信
- 中断
- 多线程API,如future,阻塞队列等
- 共享变量
- synchronized的理解
- 在JVM中,对象在内存中的布局分为三块区域:对象头、实例数据和填充数据,对象头是实现synchronized的基础
- 对象头结构
- Mark Word 存储着对象的HashCode、分代年龄、锁标记位
锁状态 |
25bit |
4bit |
1bit是否是偏向锁 |
2bit 锁标志位 |
无锁状态 |
对象HashCode |
对象分代年龄 |
0 |
0 |
-
-
- Mark Word 被设计成为一个非固定的数据结构,以便存储更多有效的数据,它会根据对象本身的状态复用自己的存储空间
- Mark Word 被设计成为一个非固定的数据结构,以便存储更多有效的数据,它会根据对象本身的状态复用自己的存储空间
-
-
-
- monitor monitor是由ObjectMonitor实现的,同一种锁对应一个monitor
- ObjectMonitor中有两个队列,_WaitSet 和 _EntryList,用来保存ObjectWaiter对象列表( 每个等待锁的线程都会被封装成ObjectWaiter对象),当多个线程同时访问一段同步代码时,首先会进入 _EntryList 集合,当线程获取到对象的monitor 后进入 _Owner 区域并把monitor中的owner变量设置为当前线程同时monitor中的计数器count加1,若线程调用 wait() 方法,将释放当前持有的monitor,owner变量恢复为null,count自减1,同时该线程进入 WaitSe t集合中等待被唤醒。若当前线程执行完毕也将释放monitor(锁)并复位变量的值,以便其他线程进入获取monitor(锁)。
- Java虚拟机对synchronized的优化 随着锁的竞争,锁可以从偏向锁升级到轻量级锁,再升级的重量级锁,但是锁的升级是单向的
- 偏向锁 :如果一个线程获得了锁,那么锁就进入偏向模式,此时Mark Word 的结构也变为偏向锁结构,当这个线程再次请求锁时,无需再做任何同步操作
- 轻量级锁 :倘若偏向锁失败,虚拟机并不会立即升级为重量级锁,它还会尝试使用一种称为轻量级锁的优化手段,轻量级锁所适应的场景是线程交替执行同步块的场合,如果存在同一时间访问同一锁的场合,就会导致轻量级锁膨胀为重量级锁。
- 自旋锁 轻量级锁失败后,虚拟机为了避免线程真实地在操作系统层面挂起,还会进行一项称为自旋锁的优化手段。线程持有锁的时间都不会太长,如果直接挂起操作系统层面的线程可能会得不偿失,虚拟机会让当前想要获取锁的线程做几个空循环(这也是称为自旋的原因),一般不会太久,可能是50个循环或100循环,在经过若干次循环后,如果得到锁,就顺利进入临界区。如果还不能获得锁,那就会将线程在操作系统层面挂起。
- 锁消除 Java虚拟机在JIT编译时,(可以简单理解为当某段代码即将第一次被执行时进行编译,又称即时编译),通过对运行上下文的扫描,去除不可能存在共享资源竞争的锁,通过这种方式消除没有必要的锁,如局部变量
- 一些特性
- 可重入的
- 不可中断
持续更新,,,
-