1.线程池提交任务submit和execute的区别
AbstractExecutorService中submit的实现如下:
/**
* @throws RejectedExecutionException {@inheritDoc}
* @throws NullPointerException {@inheritDoc}
*/
public Future<?> submit(Runnable task) {
if (task == null) throw new NullPointerException();
RunnableFuture<Void> ftask = newTaskFor(task, null);
execute(ftask);
return ftask;
}
虽然最后还是以execute的形式运行,但是用FutureTask做了封装,最后的run函数如下:
public void run() {
if (state != NEW ||
!UNSAFE.compareAndSwapObject(this, runnerOffset,
null, Thread.currentThread()))
return;
try {
Callable<V> c = callable;
if (c != null && state == NEW) {
V result;
boolean ran;
try {
result = c.call();
ran = true;
} catch (Throwable ex) {
result = null;
ran = false;
setException(ex);
}
if (ran)
set(result);
}
} finally {
// runner must be non-null until state is settled to
// prevent concurrent calls to run()
runner = null;
// state must be re-read after nulling runner to prevent
// leaked interrupts
int s = state;
if (s >= INTERRUPTING)
handlePossibleCancellationInterrupt(s);
}
}
Throwable 是Error和Exception的基类,因此在运行时遇见的所有的Error和Exception都会被捕获,并通过setException设置到outcome中,最后最get时会将异常抛出:
private V report(int s) throws ExecutionException {
Object x = outcome;
if (s == NORMAL)
return (V)x;
if (s >= CANCELLED)
throw new CancellationException();
throw new ExecutionException((Throwable)x);
}
如果直接调用execute,线程出现异常时如果未设置异常handler会直接挂掉。
2.线程的异常处理
刚才提到的异常handler实际上是通过setUncaughtExceptionHandler设置的:
public void setUncaughtExceptionHandler(UncaughtExceptionHandler eh) {
checkAccess();
uncaughtExceptionHandler = eh;
}
最后由JVM调用该handler:
/**
* Dispatch an uncaught exception to the handler. This method is
* intended to be called only by the JVM.
*/
private void dispatchUncaughtException(Throwable e) {
getUncaughtExceptionHandler().uncaughtException(this, e);
}
3.如果避免死锁
破坏互斥条件:这个条件我们没有办法破坏,因为我们用锁本来就是想让他们互斥的(临界资源需要互斥访问)。
破坏请求与保持条件:一次性申请所有的资源。
破坏不剥夺条件:占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放它占有的资源。
破坏循环等待条件:靠按序申请资源来预防。按某一顺序申请资源,释放资源则反序释放。破坏循环等待条件。
4.重写equals为什么要重写hashcode
假如只重写equals而不重写hashcode,那么类的hashcode方法就是Object默认的hashcode方法,由于默认的hashcode方法是根据对象的内存地址经哈希算法得来的,有可能会出现因为重写了equals,且o1.equals(o2)返回true,但是因为o1、o2的地址不同,两者的hashcode不同。
根据hashcode的规则,两个对象相等其哈希值一定相等,所以矛盾就产生了,因此重写equals一定要重写hashcode。
5.java类加载机制
类加载分为三个步骤:加载、连接、初始化
双亲委派:当一个类加载器收到了类加载请求,它会把这个请求委派给父(parent)类加载器去完成,依次递归,因此所有的加载请求最终都被传送到顶层的启动类加载器中。只有在父类加载器无法加载该类时子类才尝试从自己类的路径中加载该类。(注意:类加载器中的父子关系并不是类继承上的父子关系,而是类加载器实例之间的关系。)
详情见博客:
https://blog.csdn.net/cnahyz/article/details/82219210
6.JVM调优
java -Xmx3550m -Xms3550m -Xmn2g -Xss128k
-Xmx3550m :设置JVM最大可用内存为3550M。
-Xms3550m :设置JVM促使内存为3550m。此值可以设置与-Xmx相同,以避免每次垃圾回收完成后JVM重新分配内存。
-Xmn2g :设置年轻代大小为2G。整个堆大小=年轻代大小 + 年老代大小 + 持久代大小 。持久代一般固定大小为64m,所以增大年轻代后,将会减小年老代大小。此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8。
-Xss128k :设置每个线程的堆栈大小。JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K。更具应用的线程所需内存大小进行调整。在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右
7.信号量
Semaphore是一个计数信号量,它的本质是一个"共享锁"。
信号量维护了一个信号量许可集。线程可以通过调用acquire()来获取信号量的许可;当信号量中有可用的许可时,线程能获取该许可;否则线程必须等待,直到有可用的许可为止。 线程可以通过release()来释放它所持有的信号量许可。
Semaphore本质上是一个非公平锁:
public Semaphore(int permits) {
sync = new NonfairSync(permits);
}
acquire最后调用的是非公平锁的nonfairTryAcquireShared:
final int nonfairTryAcquireShared(int acquires) {
for (;;) {
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
即:acquire将可用值-1,相反release将可用值+1。
8.正则表达式
下图引用自:https://blog.csdn.net/weixin_39190897/article/details/81976632
9.volatile关键字
volatile关键字禁止指令重排序有两层意思:
1)当程序执行到volatile变量的读操作或者写操作时,在其前面的操作的更改肯定全部已经进行,且结果已经对后面的操作可见;在其后面的操作肯定还没有进行;
2)在进行指令优化时,不能将在对volatile变量访问的语句放在其后面执行,也不能把volatile变量后面的语句放到其前面执行。
java多线程的内存模型如下:
线程A访问主内存的值后,会将该值存储在本地。线程B更改了主内存的值之后,由于线程A使用的还是本地缓存的值,就是出现值不同步的现象。
使用volatile关键字会强制将修改的值立即写入主存,会导致其他线程的工作内存中缓存变量的缓存行无效。,由于缓存行无效,其他线程访问内存时会从主内存读取最新的值。
10.ThreadLocal
ThreadLocal 主要解决2类问题:
并发问题:使用 ThreadLocal 代替 Synchronized 来保证线程安全,同步机制采用空间换时间 -> 仅仅先提供一份变量,各个线程轮流访问,后者每个线程都持有一份变量,访问时互不影响。
数据存储问题: ThreadLocal 为变量在每个线程中创建了一个副本,所以每个线程可以访问自己内部的副本变量。
每个线程维护一个ThreadLocalMap,该Map的key是ThreadLocal本身,对应的主要操作使get和set,详细的代码解释已写在注释中:
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
// 如果之前未进行set,直接使用默认初始值set
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
// 如果当前线程之前未创建过ThreadLocalMap,需要初始化
return setInitialValue();
}
/**
* Variant of set() to establish initialValue. Used instead
* of set() in case user has overridden the set() method.
*
* @return the initial value
*/
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
// 对应着未set
map.set(this, value);
else
// 对应着未创建map
createMap(t, value);
return value;
}
void createMap(Thread t, T firstValue) {
// ThreadLocalMap的key是弱引用的ThreadLocal
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
// 如果当前key,也就是当前ThreadLocal存储过值,覆盖之前的值即可
if (k == key) {
e.value = value;
return;
}
// 如果遍历的key值是null,说明这个位置的ThreadLocal已经被回收
// 直接复用这部分内存即可
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
11.for-each时删除元素
编码规范并且说了for-each不允许删除元素,会导致异常,但其实不是绝对的。
在删除倒数第二个元素时,是不会异常的,测试代码如下:
List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
for (String s : list) {
if (s.equals("b")) {
list.remove("b");
}
}
System.out.println(list.size());
原因是,删除其他结点时出现的异常是因为如下代码:
private class Itr implements Iterator<E> {
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
// 遍历之前会将置为expectedModCount
int expectedModCount = modCount;
public boolean hasNext() {
return cursor != size;
}
@SuppressWarnings("unchecked")
public E next() {
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
// cursor的值会被+1,起始值为0
cursor = i + 1;
return (E) elementData[lastRet = i];
}
}
public E next() {
// 遍历元素之前首先会进行mod的校验
checkForComodification();
try {
int i = cursor;
E next = get(i);
lastRet = i;
cursor = i + 1;
return next;
} catch (IndexOutOfBoundsException e) {
checkForComodification();
throw new NoSuchElementException();
}
}
final void checkForComodification() {
// 如果modCount和expectedModCount不同,就会抛出ConcurrentModificationException
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
/*
* Private remove method that skips bounds checking and does not
* return the value removed.
*/
private void fastRemove(int index) {
// 删除元素时,会改变modCount的值
modCount++;
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
// 此时会将size的值-1
elementData[--size] = null; // clear to let GC do its work
}
而删除倒数第二个元素不会出现异常是因为,删除前size = 1,删除之后size变为2,而cursor刚好也是2
public boolean hasNext() {
return cursor != size;
}
hasNext判断返回false,不会继续进行迭代器遍历,也就不会出现ConcurrentModificationException异常。