拜托了,别再问我Java基础行不行?
写在前面
Java基础知识一直都是面试时的高频考点,很简单,但是对于很多程序员来说,如果长时间没有复习,也是很容易遗忘的。如果在面试时,一个很基础的问题,没有答上来,这就比较尴尬了。所以,这篇文档就快速总结下基础知识,防止遗忘。
字符串
创建方式:
1 构造方法:在堆中。
1 传入字符数组
2 传入字节数组
2 直接创建,使用双引号:在字符串常量池中。
比如:String a = "A";
特点:
1 字符串的内容永远不可改变
2 字符串是保存在一个char数组中
1 常量池内字符串的拼接还是在常量池中
2 如果拼接过程中出现了变量,则存放在堆空间
字符串常量池:
特点:
1 字符串常量池则存在于方法区
intern()方法: 将该字符串放入常量池中(若常量池中不存在该字符串)
集合
Collection:
ArrayList
特点:
1 基于数组方式实现,空构造器的情况下会创建一个大小为10的Object数组。
2 ArrayList在执行插入元素时可能要扩容,通常是1.5倍扩容。
3 在删除元素时并不会减小数组的容量,在查找元素时要遍历数组,对于非null的元素采取equals的方式寻找;
LinkedList
特点:
1 LinkedList基于双向链表机制实现;
2 LinkedList在插入元素时,须创建一个新的Entry对象,并切换相应元素的前后元素的引用;
在查找元素时,须遍历链表;
在删除元素时,要遍历链表,找到要删除的元素,然后从链表上将此元素删除即可,此时原有的前后元素改变引用连在一起;
HashSet
1 HashSet基于HashMap实现
2 不支持通过get(int)获取指定位置的元素,只能自行通过iterator方法来获取。
TreeSet
1 TreeSet支持排序,基于TreeMap实现
BlockingQueue
ArrayBlockingQueue :由数组组成的有界阻塞队列。
LinkedBlockingQueue:由链表组成的有界阻塞队列。但大小默认为Integer.MAX_VALUE。
SynchronousQueue:同步阻塞队列,单个元素的队列,不存储元素,必须有消费的需求才会往队列中添加一个元素,且,该元素会立刻被消费。
Stack
Map:
HashMap:
基本原理:
1 HashMap空构造,将loadFactor设为默认的0.75,并创建一个大小为16的Entry对象数组。
当其中的元素个数超过数组大小*loadFactor时,就会进行数组扩容一倍。
2 get取值也是根据key的hashCode确定在数组的位置,在根据key的equals确定在链表处的位置。
3 put的时候根据key hash后的hashcode和数组length-1按位与的结果值判断放在数组的哪个位置,如果该数组位置上若已经存放其他元素,则在这个位置上的元素以链表的形式存放。如果该位置上没有元素则直接存放。
扩容机制:
当map中包含的键值对数量大于等于阈值时,并且新加入的数据产生了hash冲突,那么就会触发扩容。将其扩容为2倍。
大致步骤:
1、新建数组
2、判断是否需要重新计算hash
3、遍历数组中每一个桶,将桶上的链表中的数据分到不同的桶去。
有效解决hash 冲突。
为什么是线程不安全?
1、多线程put操作,会出现数据不一致问题。
2、get操作可能因为resize而引起死循环
红黑树介绍:
1 它是一种特殊的二叉查找树,每个节点都有存储节点的颜色。时间复杂度是Olgn。
2 特性:
1、每个节点是红色或者黑色。
2、根节点是黑色。
3、红色节点的子节点必须是黑色。
4、一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。
5、每个空的叶子节点是黑色。
TreeMap
基于红黑树的实现,因此它要求一定要有key比较的方法,要么传入Comparator实现,要么key对象实现Comparable接口。
在put操作时,基于红黑树的方式遍历,基于comparator来比较key应放在树的左边还是右边,如找到相等的key,则直接替换掉value。
WeakHashMap
多线程
线程创建方法
继承Thread类
实现Runnable 接口
实现Callable 接口
线程的生命周期
新建状态:Thread t = new MyThread()
就绪状态:t.start();
运行状态:线程真正开始执行
阻塞状态:运行中的线程由于某种原因暂时放弃对CPU的使用权,停止执行
死亡状态
线程池:
内置5种线程池(均由Executors工厂类创建)
newFixedThreadPool:执行长期任务
1、定长线程池,超出的线程会在队列中等待。
2、它的核心线程数和最大线程数相等。
newSingleThreadExecutor:
一个任务一个任务顺序执行
特点:相当于特殊的FixedThreadPool
newCachedThreadPool:
执行很多短期异步的小程序或负载轻的服务器
newScheduleThreadPool:
创建一个定长线程池,支持定时及周期性任务执行
newSingleThreadScheduledExcutor:
创建一个单例线程池,定期或延时执行任务。
拒绝策略:当队列满了,线程池中线程数量已达最大线程,此时需要拒绝策略来处理。 1 直接抛异常 2 抛弃队列中等待最久的任务,将当前任务加入队列 3 直接丢弃任务,不抛出异常
如何关闭线程池?
原理:
都是通过遍历线程池中的工作线程,逐个调用 Thread.interrup() 来中断线程,所以一些无法响应中断的任务可能永远无法停止。
方法:
shutdown():将线程池的状态设置为 SHUTDOWN,然后中断所有没有正在执行的线程
shutdownNow() :将线程池设置为 STOP,然后尝试停止所有线程,并返回等待执行任务的列表
实际如何应用: 1 实际工作中一定要自定义线程池,不能使用JDK提供的。 2 线程数的配置需要看是CPU密集型还是IO密集型。 CPU密集型:即没有阻塞,配置尽可能少的线程数量,CPU核数+1 个线程。 IO密集型:1、由于不能一直在执行任务,则应配置尽可能多的线程,如CPU核数*2。
锁
锁机制:
所有的非静态同步方法用的都是同一把锁——实例对象本身
所有的静态同步方法用的也是同一把锁——类对象本身
锁分类:
公平锁:多个线程按照申请锁的顺序来获取锁,先来后到。
非公平锁:上来直接尝试占有锁,如果失败,就采用类似公平锁的方式
可重入锁:又叫递归锁,指的是线程可进入任何一个它已经拥有锁的同步代码块。
比如:ReentrantLock和Synchronized
读写锁:读写分离的锁,读多写少的情况下性能远高于重入锁,读锁共享,写锁互斥
比如:接口:ReadWriteLock
自旋锁:尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁
好处:减少线程上下文切换的消耗
缺点:是会消耗CPU。
独占锁:一次只能被一个线程持有
比如:ReentrantLock和Synchronized
共享锁:指该锁可被多个线程所持有。读锁。
比如:ReentrantReadWriteLock : 读写锁,读锁是共享锁,写锁是独占锁。
该锁可保证并发读是共享的,读写和写写都是互斥的。
锁优化建议: 1 减少锁的持有时间,只有在必要时才进行同步 2 减小锁粒度:缩小锁定对象的范围,例如ConcurrentHashMap。 3 应用在读多写少的场景下,因为读写锁是共享的 4 锁粗化:对同一个锁多次进行请求和释放的操作时,变成一次对锁的请求。
具体的锁详解:
Synchronized:
原理:
通过反编译可以看出,有两条指令,monitorenter和monitorexit。
每个对象都有一个监视器锁,monitor。
如果monitor的进入数为0,则该线程进入monitor,
然后将进入数设置为1,该线程即为monitor的所有者。
如果线程已经占有该monitor,只是重新进入,则进入数加1。
其他线程会直到monitor的进入数为0,再重新尝试获取monitor的所有权。
执行monitorexit指令,monitor的进入数减1,结果为0就退出monitor。
阻塞和唤醒:Object的wait() 和notify()
特点:
必须放在synchronized加锁的代码中
必须先wait,后notify才可以正常唤醒
ReentrantLock:可重入的锁,类似synchronized,但功能更加强大
Condition:
一个Lock对象可以产生多个Condition进行多线程间的交互,可精确唤醒
LockSupport:
是一个线程阻塞工具类,通过park() 和unpark(thread) 方法来实现阻塞和唤醒线程的操作
原理:
线程阻塞需要消耗凭证,这个凭证最多只有1个
当调用Park方法时:
如果有凭证,则会直接消耗这个凭证然后正常退出
如果没有凭证,就必须阻塞等待凭证可用
当调用Unpark方法时:
它会增加一个凭证,但凭证最多只有1个,累加无效
死锁如何定位?
1、jps 命令定位进程号
2、Jstack pid
异常
RuntimeException:由程序错误导致的异常 Exception:而程序本身没有没有问题,但由于像I/O错误这类异常导致的异常属于其他异常
JUC并发包
ConcurrentHashMap:
ConcurrentHashMap是线程安全的HashMap的实现, 其内部使用锁分段技术,维持16个Segment的数组,分别持有各自不同的锁Segment,内部hash算法将数据较均匀分布在不同锁中
put操作:
首先对key.hashcode进行hash操作。根据此hash值计算并获取其对应的数组中的Segment对象(继承自ReentrantLock),接着调用此Segment对象的put方法来完成当前操作。
get操作:
首先对key.hashCode进行hash操作,基于其值找到对应的Segment对象,调用其get方法完成当前操作。而Segment的get操作首先通过hash值和对象数组大小减1的值进行按位与操作来获取数组上对应位置的HashEntry。
CopyOnWriteArrayList:
是一个线程安全、并且在读操作时无锁的ArrayList。
CopyOnWriteArraySet:
基于CopyOnWriteArrayList实现,其唯一的不同是在add时调用的是CopyOnWriteArrayList的addIfAbsent方法。保证了无重复元素,但在add时每次都要进行数组的遍历,因此性能会略低于上个。
CountDownLatch:
让一些线程阻塞直到另外一些线程完成一系列操作后才被唤醒。
主要有两个方法(countDown和await),当一个或多个线程调用await方法时,这些线程会阻塞。
其它线程调用countDown方法会将计数器减1(调用countDown方法的线程不会阻塞),
当计数器的值变为0时,因await方法阻塞的线程会被唤醒,继续执行。
CyclicBarrier:当多个线程需要在某时阻塞,然后同时继续运行时, Countdownlatch是做减法,CyclicBarrier是做加法。
Semaphore:类似争车位,即多个线程争夺多个共享资源。模拟共享资源 在信号量上我们定义两种操作: acquire(获取): 当一个线程调用acquire操作时,它要么通过成功获取信号量(信号量减1),要么一直等下去,直到有线程释放信号量,或超时。 release(释放):实际上会将信号量的值加1,然后唤醒等待的线程。
AQS(AbstractQueuedSynchronizer):抽象的队列同步器
技术解释:
用来构建锁或者其他同步器组件的重量级基础框架及整个JUC体系的基石,
通过内置的队列来完成资源获取线程的排队工作,并通过一个int类型变量表示持有锁的状态。
原理:
AQS使用一个volatile 的int类型的成员变量来表示同步状态,通过内置的队列来完成资源获取的排队工作,将每条要去抢占资源的线程封装成一个Node节点来实现锁的分配,通过CAS完成对State值的修改.
内部结构:
int 类型 state 变量
CLH双端队列
未完待续。。。