- 一、Java-基础
- 1、Java的8种基本数据类型
- 2、Java中 == 和 equels的区别
- 3、普通类和抽象类有哪些区别
- 4、接口和抽象类的区别
- 二、Java-集合
- 1、ArrayList
- 2、LinkedList
- 3、Vector
- 4、HashSet
- 5、LinkedHashSet
- 6、TreeSet
- 7、HashMap
- 8、LinkedHashMap
- 9、HashTable
- 10、TreeMap
- 11、ConcurrentHashMap
- 三、Java-线程
- 1、并行和并发的区别
- 2、线程和进程的区别
- 3、创建线程的几种方式
- 4、runnable 和 callable 有什么区别
- 5、线程有哪些状态
- 6、sleep() 和 wait() 有什么区别
- 7、notify()和 notifyAll()有什么区别
- 8、线程的 run() 和 start() 有什么区别
- 9、创建线程池有哪几种方式
- 10、线程池创建的几个参数
- 四、Java-锁
- 1、volatile关键字的作用
- 2、公平锁
- 3、非公平锁
- 4、非公平可重入锁(递归锁)
- 5、自旋锁
- 6、独占锁(写锁)
- 7、共享锁(读锁)
- 8、synchronized有什么作用
- 9、synchronized同步方法的原理
- 10、synchronized同步代码块的原理
- 11、ReentrantLock和synchronized有什么区别
- 12、什么是CAS
- 13、CAS有什么缺点
- 14、LockSupport的作用
- 15、阻塞线程和解除阻塞线程的方式
- 16、什么是AQS
- 五、Spring
- 1、IOC(控制反转)
- 2、AOP(面向切面编程)
- 3、SpringBean的生命周期
- 4、Spring的循环依赖
- 5、Spring的事务隔离级别
- 6、Spring的事务的失效场景
- 7、Spring的事务的回滚
- 字符类型:byte、char;
- 基本整型:short、int、long;
- 浮点型:float、double;
- 布尔型:boolean;
- ==:基本类型比较的是值是否相同;
- equels:引用类型比较的是引用是否相同;
String x = "string";
String y = "string";
String z = new String("string");
System.out.println(x==y); // true
System.out.println(x==z); // false
System.out.println(x.equals(y)); // true
System.out.println(x.equals(z)); // true
3、普通类和抽象类有哪些区别
- 普通类不能包含抽象方法,抽象类可以有抽象方法也可不有抽象方法;
- 抽象类不能被实例化,普通类可以直接实例化;
- 抽象类不能用final修饰,抽象类就是让其他类继承的,如果定义为final就不能被继承了,矛盾;
- 抽象类使用extend继承,接口使用implements实现接口;
- 抽象类可以有构造函数,接口不能有构造函数;
- 类可以实现很多个接口,但是只能继承一个抽象类;
- 接口默认修饰符是public,抽象类可使用任何的修饰符;
- ArrayList 是 java 集合框架中比较常用的数据结构,继承自 AbstractList,实现了 List 接口。底层基于数组实现容量大小动态变化。允许 null 的存在。同时还实现了 RandomAccess、Cloneable、Serializable 接口,所以ArrayList 是支持快速访问、复制、序列化的。
- LinkedList是java一种常用的数据容器,与ArrayList相比,LinkedList的增删操作效率更高,而查改操作效率较低。
- LinkedList实现了List接口 ,所以LinkedList是具备List的存储特征的(有序、元素有重复);
- LinkedList 实现了Deque 接口,即能将LinkedList当作双端队列使用;
- LinkedList 实现了Cloneable、Serializable 接口,所以LinkedList 是支持复制、序列化的。
- Vector用数组存储元素,容量能够动态的增长,它的很多实现方法都加入了同步语句,因此是线程相对安全的。
- 并行:多人多个事件同一时刻发生(一个人看电视,一个人洗衣服);
- 并发:多人同时抢着干一件事;
- 进程:是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。
- 线程:是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。
- extend继承Thread类重写run方法;
- implements实现runnable接口重写run方法;
- implements实现callable接口重写call方法;
runnable 没有返回值,callable 可以拿到有返回值,callable 可以看作是 runnable 的补充。
5、线程有哪些状态- 新建状态(NEW):在程序中用构造方法创建一个新线程时,如new Thread(),该线程就是创建状态,此时它已经有了相应的内存空间和其它资源,但是还没有开始执行。
- 就绪状态(READ):线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取cpu 的使用权 。
- 运行状态(RUNNING):可运行状态(runnable)的线程获得了cpu 时间片(timeslice) ,执行程序代码。
- 阻塞状态(BLOCKED):一个正在执行的线程在某些特殊情况下,如被人为挂起或需要执行耗时的输入输出操作时(调用sleep()、wait()方法),将让出CPU并暂时中止自己的执行,进入堵塞状态。
- 死亡状态(TERMINATED):线程调用stop(), destory()或run()执行结束后,线程即处于死亡状态。处于死亡状态的线程不具有继续运行的能力。
- 类不同:sleep() 来自 Thread,wait() 来自 Object。
- 释放锁:sleep() 不释放锁;wait() 释放锁。
- 用法不同:sleep() 时间到会自动恢复;wait() 可以使用 notify()/notifyAll()直接唤醒。
- notifyAll()会唤醒所有的线程,notify()之后唤醒一个线程。notifyAll() 调用后,会将全部线程由等待池移到锁池,然后参与锁的竞争,竞争成功则继续执行,如果不成功则留在锁池等待锁被释放后再次参与竞争。而 notify()只会唤醒一个线程,具体唤醒哪一个线程由虚拟机控制。
- start() 方法用于启动线程,run() 方法用于执行线程的运行时代码。run() 可以重复调用,而 start() 只能调用一次。
private static ExecutorService singleThreadPool = Executors.newSingleThreadExecutor();
private static ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);
private static ExecutorService cacheThreadPool = Executors.newCachedThreadPool();
private static ExecutorService scheduleThreadPool = Executors.newScheduledThreadPool(5);
10、线程池创建的几个参数
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler
}
- corePoolSize:核心线程数,正常工作的线程数量;
- maximumPoolSize:容纳同时执行的最大线程数量;
- keepAliveTime:空闲线程存活时间;
- unit:空闲线程存活时间单位;
- workQueue:存放任务的阻塞队列;
- threadFactory:线程工厂;
- handler:拒绝策略,有四种实现:
1.AbortPolicy:拒绝任务抛弃处理,并且抛出异常;
2.CallerRunsPolicy:拒绝任务直接抛弃;
3.DiscardOldestPolicy:重试添加当前的任务,直到成功;
4.DiscardPolicy:抛弃队列里面等待最久的一个线程,将当前任务加入队列;
- java提供的 轻量级同步机制;
- 保证可见性;
- 不保证原子性(java.util.concurrent.atomic包下的类可保证原子性);
- 禁止指令重排。下面代码:reader方法依赖flag的值,我们希望程序是顺序执行的正常单线程程序没问题,但是在多线程环境下,writer方法可能先执行flag=true,这样reader方法获取的值就不确定了,volatile的一个特点就是禁止指令重排,固定必须先执行a=1然后flag=true
public class OrderSortDemo {
private int a = 0;
private boolean flag = false;
public void writer(){
a = 1;
flag = true;
}
public void reader(){
if(flag){
int i = a + 1;
}
}
}
双重检索DCL单例实现使用volatile代码:
public class SingleInstance {
private static volatile SingleInstance instance = null;
private SingleInstance() {
}
public static SingleInstance getInstance() {
if (instance == null) {
synchronized (SingleInstance.class) {
if (instance == null) {
instance = new SingleInstance();
}
}
}
return instance;
}
}
2、公平锁
- 加锁前检查是否有排队等待的线程,优先排队等待的线程,先来先得;>>示例如下:
Lock lock = new ReentrantLock(true);3、非公平锁
- 直接获取锁,获取不到然后用公平锁的方式有序获取锁,非公平锁闭公平锁吞吐量大;ReentrantLock默认非公平锁、synchronized也是一种非公平锁;
- 同步方法调用另一个同步方法或者同步代码块调用另一个同步代码块,同一个线程在外层方法获取锁的时候,在进入内层方法会自动获取锁;>>示例代码:
public class ReEnterLockDemo {
private static Object obj = new Object();
private static Lock lock = new ReentrantLock();
public static void syncMethod() {
new Thread(() -> {
synchronized (obj) {
System.out.println(Thread.currentThread().getName() + "111");
synchronized (obj) {
System.out.println(Thread.currentThread().getName() + "222");
}
}
}, "threadA").start();
}
public static void lockMethod() {
new Thread(() -> {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "111");
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "222");
} finally {
lock.unlock();
}
} finally {
lock.unlock();
}
}, "threadB").start();
}
public static void main(String[] args) {
syncMethod();
lockMethod();
}
}
5、自旋锁
- 线程不会立即阻塞,而是挂起采用循环的方式去尝试获取锁,好处是减少上下文切换,缺点是循环消耗CPU;>>示例代码:
public final int getAndSetInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var4));
return var5;
}
6、独占锁(写锁)
- ReentrantReadWriteLock.WriteLock一次只能被一个线程所持有,ReentrantLock和synchronized都是独占锁;
- ReentrantReadWriteLock.ReadLock可被多个线程同时持有;
- 确保线程互斥的访问同步代码;
- 保证共享变量的修改能够及时可见;
- 有效解决重排序问题。
- 当方法调用时,调用指令将会检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置,如果设置了,执行线程将先获取monitor,获取成功之后才能执行方法体,方法执行完后再释放monitor。在方法执行期间,其他任何线程都无法再获得同一个monitor对象。
- 当执行monitorenter时,如果目标对象的计数器为0,那么说明它没有被任何线程所持有,java虚拟机会将该锁对象的持有线程设置为当前线程,并将计数器+1;在目标对象的计数器不为0时,如果锁对象的持有线程是当前线程,那么java虚拟机可以将其计数器+1,否则需要等待,直至持有线程释放该锁;当执行monitorexit时,java虚拟机将其计数器-1,计数器为0表示该锁已被释放。
- synchronized是java的关键字,ReentrantLock是API层面的;
- synchronized底层是通过minitor对象完成,wait和notify方法也依赖monitor对象所在的同步方法或者同步代码块,自动释放,不可中断,默认为非公平锁;
- ReentrantLock为显示锁必须使用lock加锁和unlock释放锁,可以中断,默认也是非公平锁,可以设置为公平锁,通过ReentrantLock(boolean fair)设置;
- synchronized的await和notify相当于ReentrantLock的Condition(条件)的await和signal,ReentrantLock可精确唤醒某个线程;
- 方法名为compareAndSet(比较并赋值),主要就是自旋锁与Unsafe类,内存值V,期望值A,待修改值B,如果V与A的值相同,将内存值V更新为B,否则一直比较下去,直到成功(do…while…)
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
public final int getAndSetInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var4));
return var5;
}
13、CAS有什么缺点
- 循环时间过长,开销大;
- 只能保证一个共享变量的原子操作,也就是compareAndSet方法this参数;
- ABA问题(解决:new AtomicReference
(V initialValue):参数为一个初始值、new AtomicStampedReference (V initialRef, int initialStamp):参数为一个初始值和一个时间戳版本号(类似于数据库的乐观锁));
- 线程等待唤醒机制,两个方法阻塞线程park()和解除阻塞线程unpark(),有一个permit许可证,只能有0和1两个值,park为0,unpark为1,有凭证消耗掉凭证然后正常退出,没有凭证则阻塞,凭证只能为一个累加无效,park和unpark成对出现;
- Object的wait和notify方法;
- LockSupport的park和unpark;
- Lock的Condition的await和singnal;
-
AbstractQueuedSynchronizer(抽象队列同步器):是构建锁和其他同步器组件的整个JUC体系的基石,主要由一个变量state状态值和变种的CLH双向队列(头节点Node head和尾节点Node tail)组成;
-
获取锁时通过CAS去更新state状态值操作,state初始值为0,说明没有被占用,当前线程可以获取锁,如果不为0说明被占用会尝试获取锁,仍获取不到会添加到等待队列,如队列不存在时初始一个队列(Node的thread值为null,waitStatus为0的哨兵节点作为头节点),如队列存在会在其后增加当前线程的一个Node节点,然后队列里的线程仍会自旋去尝试获取锁,如果仍获取不到,将当前线程的前节点的waitStatus改为-1,并通过LockSupport的park阻塞到等待队列,如果state的值等于0为空闲说明能够获取到锁然后出队,将该线程对应的Node节点设置为哨兵节点,以前的哨兵的节点将被回收掉;
- IOC不是一种技术,只是一种思想,一个重要的面向对象编程的法则,它能指导我们如何设计出松耦合、更优良的程序。传统应用程序都是由我们在类内部主动创建依赖对象,从而导致类与类之间高耦合,难于测试;有了IoC容器后,把创建和查找依赖对象的控制权交给了容器,由容器进行注入组合对象,所以对象与对象之间是 松散耦合,这样也方便测试,利于功能复用,更重要的是使得程序的整个体系结构变得非常灵活。
- AOP (Aspect Orient Programming),直译过来就是面向切面编程。AOP 是一种编程思想,是面向对象编程(OOP)的一种补充。面向对象编程将程序抽象成各个层次的对象,而面向切面编程是将程序抽象成各个切面。
说明链接:link.
5、Spring的事务隔离级别说明链接: link.
6、Spring的事务的失效场景说明链接: link.
7、Spring的事务的回滚


