封装、继承、多态
单一职责原则、开放封闭原则、Liskov替换原则、依赖倒置原则、接口隔离原则、良性依赖原则
final 对变量——>常量 、对方法——>不可重写方法、对类——>不可继承类
finally 是 异常处理中的知识 try catch finally 每次try catch 都会执行finally
finalize() 是垃圾回收时将无用的对象从内存中清除
两者都是为了将公共化的部分抽取出来,形成复用与多样性。
Java 类只能继承一个类 ,但能实现多个接口
抽象类可以中的方法可以有默认实现的、而接口中的方法只能是声明(好像最新版的可以带实现了)
实现、构造器、与Java类型比、访问修饰符、多继承、速度、添加新方法
抽线类代表 is-a 接口代表 like-a
== 该操作符生成一个boolean结果,它计算的是操作数的值之间的关系。
equals:Object的实例,比较两个对象的content是否相同
hashCode:Object的native方法,获取对象的哈希值,用于确定该对象在哈希表中的索引位置,它实际上是一个int型整数
Java内部类能调用外部的所有方法和属性
Java静态内部类只能调用静态方法和属性
每个内部类都能独立地继承一个类,而无论外部类是否已经继承了某个类。因此,内部类使多重继承的解决方案变得更加完整
重载:在本类中对于有着相同名称的方法,进行多个参数的变化
重写:用在继承中重新实现父类的方法。调用子类的方法时调用的是子类的方法。
泛型操作类型被指定为一个参数,在传入的时候会检测传入的参数是否是其类型或子类
?类型通配符 是为了解决 List不是 List 子类的 使用的话是 ListList<? extends Number>
Integer 是int 的包装类 ,Java提供自动装包和拆包。
源码中对于 大于-128 小于 127 的数 如果是的话从IntegerCache 类中取, 因此比较时 需要注意在这之间的数
另外比较的时候 对于 Integer a = new Integer(1) Integer b = 1 这两个不相等
Java反射机制 和动态代理 , Java反射就是在运行的时候通过不用直接通过实例化的方法去得到这个类Class ,Metnhd,Fieds,并实现一定的操作。 这种是事先内存中已经加载这个Class字节码文件,找的时候找的是字节码文件。 动态代理是通过反射的原理实现的事先代理类和原始类直接的关系不确定,以后详细说。
Java finally 不执行情况: 1. 不执行try 就不执行finally 2. 在finally之间执行 System.exit(0)
Java finally 执行在return 之后 ,在return 返回之前。
finally块中的return语句会覆盖try块中的return返回。
如果finally语句中没有return语句覆盖返回值,那么原来的返回值可能因为finally里的修改而改变也可能不变。
try块里的return语句在异常的情况下不会被执行,这样具体返回哪个看情况。
当发生异常后,catch中的return执行情况与未发生异常时try中return的执行情况完全一样。
- 静态初始化 父类静态块和静态成员初始化、子类静态块和静态成员初始化
- 父类初始化 父类普通成员和静态块初始化、 父类构造函数初始化
- 子类初始化 子类普通成员和静态块初始化、 子类构造函数初始化
字节流:处理除字符以外的所有问题
字符流:专注处理字符的问题,字符涉及到编码问题。
引入 Buffer缓存 、Channel通道、Selector选择器的概念,做成了非阻塞的IO
Buffer :从原始文件进行读取发送到Channel
Channel:可以用来进行读操作,又可以用来进行写操作
Selector:运行单线程处理多个Channel, 就可以实现非阻塞。
15.Java线程基础Java线程基础
Runnable 接口,重写run方法、继承Thread 类,Callable(有返回)与Future、线程池结合使用
java线程状态:新建 相当于 new Thread()、准备执行了start()、 运行中得到cpu、暂停 请求资源阻塞、自己调用阻塞方法、结束 正常退出或异常退出
sleep(long millis)在指定的毫秒数内让当前正在执行的线程休眠(暂停执行) 当前线程抢不到再次执行的权力
join():指等待t线程终止
yield():暂停当前正在执行的线程对象,并执行其他线程。 当前线程也加入争抢
setPriority(): 更改线程的优先级。
interrupt():它只是线线程发送一个中断信号,让线程在无限等待时(如死锁时)能抛出抛出,从而结束线程,但是如果你吃掉了这个异常,那么这个线程还是不会中断的!
wait()
Obj.wait(),与Obj.notify()必须要与synchronized(Obj)一起使用,也就是wait,与notify是针对已经获取了Obj锁进行操作,从语法角度来说就是Obj.wait(),Obj.notify必须在synchronized(Obj){…}语句块内。从功能上来说wait就是说线程在获取对象锁后,主动释放对象锁,同时本线程休眠。直到有其它线程调用对象的notify()唤醒该线程,才能继续获取对象锁,并继续执行。相应的notify()就是对对象锁的唤醒操作。但有一点需要注意的是notify()调用后,并不是马上就释放对象锁的,而是在相应的synchronized(){}语句块执行结束,自动释放锁后,JVM会在wait()对象锁的线程中随机选取一线程,赋予其对象锁,唤醒线程,继续执行
sleep()方法 如果有锁的话不会释放锁
wait() 调用时会把锁释放掉,他必须在synchronized 中使用。
1.Deque接口是什么,定义了一个怎样的规范?
Deque定义了一个线性Collection,支持在两端插入和删除元素,Deque实际是“double ended queue(双端队列)”的简称
2.LinkedList是双向链表,其底层实现是怎样的,具体包含哪些操作?
双向链表
多线程下 java 7 的refresh() 方法每一个元素进行hash然后进行重排序,会出现死循环的情况
Java8 利用位操作 元素的hash值与原容量进行与操作,要么是原位,要么加个旧容量。扩容后,新数组中的链表顺序依然与旧数组中的链表顺序保持一致!
底层用的是hashMap, 对hashMap 进行了封装,只用HashMap 的 key,用了一个默认的Object作为值
ArrayList 底层是一个数组
hash 的原理:在Java 1.8的实现中,是通过hashCode()的高16位异或低16位实现的:(h = k.hashCode()) ^ (h »> 16),主要是从速度、功效、质量来考虑的,这么做可以在bucket的n比较小的时候,也能保证考虑到高低bit都参与到hash的计算中,同时不会有太大的开销。
悲观锁:当得不到所需资源时,就会挂起,等到资源(比如一个共享数据)可用了,那么就将线程唤醒,让他进入runnable状态等待cpu调度
乐观锁:他的核心思路就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。在上面的例子中,某个线程可以不让出cpu,而是一直while循环,如果失败就重试,直到成功为止。
CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。
在CAS操作中,会出现ABA问题。就是如果V的值先由A变成B,再由B变成A,那么仍然认为是发生了变化,并需要重新执行算法中的步骤。有简单的解决方案:不是更新某个引用的值,而是更新两个值,包括一个引用和一个版本号,即使这个值由A变为B,然后为变为A,版本号也是不同的
BlockingQueue 有四种插入、移除、获取队列元素的方式。如果获取不到:抛出异常、返回特殊值、阻塞、阻塞超时。 put take 是阻塞
原理:公平锁:新线程会执行如果有另一个线程持有锁或者有其他线程在等待队列中等待这个锁,那么新发出的请求的线程将被放入到队列中。
非公平锁:新进线程会查看锁是否被人持有,如果有就会放到队列里,没有就会竞争,会出现线程饥饿状态。
hasQueuedPredecessors() 是实现公平的条件原理解释
Java8 使用final 修饰 final int hash; final K key; volatile V val; volatile Node
next;
它是个线程安全且读操作无锁的ArrayList,写操作则通过创建底层数组的新副本来实现,是一种读写分离的并发策略,我们也可以称这种容器为”写时复制器”
缺点也很明显,一是内存占用问题,毕竟每次执行写操作都要将原容器拷贝一份,数据量大时,对内存压力较大,可能会引起频繁GC;二是无法保证实时性,Vector对于读写操作均加锁同步,可以保证读和写的强一致性。而CopyOnWriteArrayList由于其实现策略的原因,写和读分别作用在新老不同容器上,在写操作执行过程中,读不会阻塞但读取到的却是老容器的数据。
compare and swap 通常将 CAS 用于同步的方式是从地址 V 读取值 A,执行多步计算来获得新 值 B,然后使用 CAS 将 V 的值从 A 改为 B。如果 V 处的值尚未同时更改,则 CAS 操作成功。
多个用户对一项资源产生共用时,保持该资源的安全性。 如抢票软件、秒杀商品。
现在的用户系统中只是对自己数据进行操作,因此不涉及多线程。
ReentrantLock :重入锁,在获得锁的情况下,还可以继续加锁,然后对应的解几次锁
ReentrantLock独占锁:只能获得锁的线程运行
interrupt()打断 会将其打断,放弃锁。
ReentrantLock 可实现公平锁、非公平锁
配合Condition 可以实现 awit() 等待signal() 的唤醒。
CountDownLatch是一个异步辅助类,它能让一个和多个线程处于等待状态,直到其他线程完成了一些列操作。当count 减为0 是awit() 才会继续 await(long timeout, TimeUnit unit) 等待时间过后就会继续。
CyclicBarrier 回环栏栅这个类,让所有的现场都去相互等待,知道它们都到达了一个栏栅的点 在指定个数的线程掉awit() 方法才会向下执行
Semaphore翻译成字面意思为 信号量,Semaphore可以控同时访问的线程个数,通过 acquire() 获取一个许可,如果没有就等待,而 release() 释放一个许可
创建线程池的 7个参数 1. 核心线程数 2. 最大线程数 3. 空闲等待时间 4.时间单位 5. 等待队列 6. 线程工厂 7. 大于最大等待队列拒绝策略
拒绝策略 4 种: 超出后拒绝,并抛出异常、 超出后拒绝、丢弃队列最前面的任务,然后重新尝试执行任务、由调用线程处理该任务
偏向锁、轻量锁、重量锁
Synchronized 是为了线程间的数据共享、ThreadLocal 是线程间的数据隔离。
Spring使用ThreadLocal解决线程安全问题我们知道在一般情况下,只有无状态的Bean才可以在多线程环境下共享,在Spring中,绝大部分Bean都可以声明为singleton作用域。就是因为Spring对一些Bean(如RequestContextHolder、TransactionSynchronizationManager、LocaleContextHolder等)中非线程安全状态采用ThreadLocal进行处理,让它们也成为线程安全的状态,因为有状态的Bean就可以在多线程中共享了。
ThreadLocal是解决线程安全问题一个很好的思路,它通过为每个线程提供一个独立的变量副本解决了变量并发访问的冲突问题。在很多情况下,ThreadLocal比直接使用synchronized同步机制解决线程安全问题更简单,更方便,且结果程序拥有更高的并发性。
单例 双重检测 第一层不加锁、第二层加锁synchronized 并且变量使用volatile关键字修饰。
单例使用 枚举的方式创建。
分为线程私有区:虚拟栈(方法执行时都会创建栈帧)、程序计数器PC(记录正在执行的虚拟机字节码的地址)、本地方法栈(native方法)
共享区:方法区(包含常量区)、对象堆(对象分配内存的区域)
并发程序要正确地执行保证: 原子性(一个操作或多个操作要么全部执行且执行过程不被中断,要么不执行 synchronized)、可见性(多个线程修改同一个共享变量时,一个线程修改后,其他线程能马上获得修改后的值volatile)、有序性(程序执行的顺序按照代码的先后顺序执行,volatile和synchronized就是有序的)三大特性解释
volatile 作用1. 禁止指令重排 2. 在一个线程进行被volatile修改的变量时,根据数据一致性的协议,通过信号量,更改其他线程的高速缓存中volatile关键字修饰变量状态为无效状态,其他线程如果需要重写读取该变量会再次从主内存中读取,而不是读取自己的高速缓存中的。
volatile 不会保证原子性 10个线程 每个加1000 最后结果不为10000 , 就是通知其他线程去读。i++.它分为读、加加、写。一种情况,当线程1读取了inc的值,还没有修改,线程2也读取了,线程1修改完了,通知线程2将线程的缓存的 inc的值无效需要重读,可这时它不需要读取inc ,它仍执行写操作,然后赋值给主线程,这时数据就会出现问题。
那synchronized 保证只有一个线程读此参数,因此不会出错。
volatile 的好处就是保证可见性,防止指令重排用在:状态标记、防止指令重排。 对于多线程使用一个变量时 ,小心小心再小心
垃圾收集算法有:标记清除法、复制算法、标记整理算法、分代收集算法
垃圾收集算法有:Serial收集器:单线程”收集 (复制算法)
ParNew: 集器是Serial收集器的多线程版本 (复制算法)
Parallel Scavenge:一个新生代收集器(复制算法),是并行的多线程收集器。
Serial old收集器:对老年代进行Serial 收集
Parallel Old收集器: 对老年代的Parallel 方法, 是并行的多线程收集器。
CMS收集器(标记清除算法) 初始标记、并发标记、重新标记、并发清除 初始标记、重新标记这两个步骤仍然需要“stop the world”
G1收集器(标记整理) 初始标记(Initial Marking)并发标记(Concurrent Marking)最终标记(Final Marking)筛选回收(Live Data Counting and Evacuation)
并行:多线程时,垃圾收集时程序暂停。 并发: 多线程时,可以垃圾收集和程序运行同时进行。
内存分配与回收策略:对象优先在Eden分配、大对象直接进入老年代、长期存活的对象将进入老年代、动态对象年龄判断、空间分配担保
引用计数算法:给对象中添加一个引用计数器,每当有个地方引用它,计数器值就加1,引用失效,计数器减1,任何时刻计数器为0的对象就不能再应用了。 很难处理循环引用
可达性分析算法:通过一系列“GC roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径成为引用链(refenecre chain) ,当一个对象到GcRoot 没有任何的引用链,则证明此对象不可用。
生存还是死亡:如果一个对象被判定有必要执行,则将这个对象放在一个F-Queue的队列中,并由一个线程去执行它。finalize方法是对象逃脱死亡命运的最后机会。当在finalize方法中重新将之间赋值给了某个变量,那么第二次标记就会被移除。如果对象第二次还没有逃脱,那么基本就被回收了。
回收方法区:方法区在hotspot虚拟机称为永久代,永久代的垃圾收集主要回收两部分内容:废弃常量和无用的类。回收废弃常量与回收堆类似,当一个字符串“abc”,进入了常量池,且没有任何String对象叫“abc”,那么它将会被回收。
判断一个类为无用的类:
- 该类的所有实例都被回收
- 加载该类的classloader已经被回收
- 该类对应java.lang.class对象没有在任何地方引用,无法通过反射访问该类。
Java 的加载验证机制分为5个阶段: 加载、验证、准备、解析、初始化。
加载:加载是类加载过程中的一个阶段,这个阶段会在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的入口。注意这里不一定非得要从一个Class文件获取,这里既可以从ZIP包中读取(比如从jar包和war包中读取),也可以在运行时计算生成(动态代理),也可以由其它文件生成(比如将JSP文件转换成对应的Class类)。
验证:这一阶段的主要目的是为了确保Class文件的字节流中包含的信息是否符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
准备:阶段是正式为类变量分配内存并设置类变量的初始值阶段,即在方法区中分配这些变量所使用的内存空间。注意这里所说的初始值概念注意 public static int v = 8080// 准备时只赋值 v= 0 ; public static final int v = 8080 // 准备时赋值为8080
解析阶段是指虚拟机将常量池中的符号引用替换为直接引用的过程。符号引用就是class文件中的:CONSTANT_Class_info、CONSTANT_Field_info、CONSTANT_Method_info 等类型的常量。
符号引用和直接引用的概念: 符号引用与虚拟机实现的布局无关,引用的目标并不一定要已经加载到内存中。各种虚拟机实现的内存布局可以各不相同,但是它们能接受的符号引用必须是一致的,因为符号引用的字面量形式明确定义在Java虚拟机规范的Class文件格式中。 直接引用可以是指向目标的指针,相对偏移量或是一个能间接定位到目标的句柄。如果有了直接引用,那引用的目标必定已经在内存中存在。
初始化:到了初始阶段,才开始真正执行类中定义的Java程序代码。初始化阶段是执行类构造器方法的过程。方法是由编译器自动收集类中的类变量的赋值操作和静态语句块中的语句合并而成的。虚拟机会保证方法执行之前,父类的方法已经执行完毕。p.s: 如果一个类中没有对静态变量赋值也没有静态语句块,那么编译器可以不为这个类生成()方法。
注意以下几种情况不会执行类初始化:
通过子类引用父类的静态字段,只会触发父类的初始化,而不会触发子类的初始化。
定义对象数组,不会触发该类的初始化。
常量在编译期间会存入调用类的常量池中,本质上并没有直接引用定义常量的类,不会触发定义常量所在的类。
通过类名获取Class对象,不会触发类的初始化。
通过Class.forName加载指定类时,如果指定参数initialize为false时,也不会触发类初始化,其实这个参数是告诉虚拟机,是否要对类进行初始化。
通过ClassLoader默认的loadClass方法,也不会触发初始化动作。
类加载器
虚拟机设计团队把加载动作放到JVM外部实现,以便让应用程序决定如何获取所需的类,JVM提供了3种类加载器:
启动类加载器(Bootstrap ClassLoader):负责加载 JAVA_HOME\lib 目录中的,或通过-Xbootclasspath参数指定路径中的,且被虚拟机认可(按文件名识别,如rt.jar)的类。 扩展类加载器(Extension ClassLoader):负责加载 JAVA_HOME\lib\ext 目录中的,或通过java.ext.dirs系统变量指定路径中的类库。 应用程序类加载器(Application ClassLoader):负责加载用户路径(classpath)上的类库。 JVM通过双亲委派模型进行类的加载,当然我们也可以通过继承java.lang.ClassLoader实现自定义的类加载器。