你们的再哥面试复习-JAVA基础

1.多线程

使用场景:

我们经常使用的很多框架就有用到多线程,比如springMvc,他底层就是一个serverlet,然后serverlet在来一个请求的时候会开一个线程来处理,所以日常开发也要注意共享资源问题。

经常导出几万条数据的时候,for循环很慢,需要使用多线程来同步进行,但是数据库要支撑得住,难点在于多个线程执行完了怎么进行整合,使用feature

自己写逻辑比较复杂,这时候可以用Future来进行等待获取结果,大大提升效率,并且我们还可以设置jvm的线程大小来生成更多的线程,默认是1M

 

遇到的坑:

多线程下操作同一个共享数据,在高并发情况下有可能会出现数据不准确问题,两种解决办法:

使用锁,使用锁会保证数据准确性,但损失了性能。

使用threadloca来为每个线程复制一份共享数据,互不干涉,缺点是会占用内存空间,不过可以略忽不记,还有一个问题就是

threadloca一般还能用来做获取当前用户,正常做法是存到rediss或者map里,这就是一个共享资源,就会有并发问题,所以请求进来的时候根据token查询到用户信息,然后放到threadloca里,后续代码里每次get出来就是当前登录用户

死锁:

多线程的死锁就是两个线程在互相等待对方的锁释放,造成这种情况的原因一般是锁嵌套使用导致

解决办法有:

使用lock可以中断等待,设置超时时间,超出时间了就不等待了。

一般发生死锁,线程不释放,服务器cpu和load会升高,排查死锁位置,修复代码,jps 查询java线程的pid,jstack命令这个pid,就能看到现在的线程,处于什么状态,是等待,等谁,代码在哪个类第几行。

所以创建线程的时候最好取名字方便排查。

Volatile:

多线程使用中会有这总问题,我2个线程,线程里有成员变量,我线程2改了线程1的成员变量,咦没成功 什么情况?

保证不同线程对共享变量的可见性,因为我们各自线程的变量是私有的存在高速缓存区,而不是主内存,线程A拥有自己的变量a=1,这时候会把这个变量在主内存也存一份,然后执行代码的时候线程A把a改成2,这时候主内存的还是a=1,所以其他线程读的时候值不是最新的,所以需要用volatile来定义这个变量,这样在修改的时候也同步主内存,其他线程读的就是最新的。

 

 

 

2.锁/sychonize 、lock

sychonize可以加在方法上或者代码块锁住某一块,在jdk1.6之前只有重量级锁,就是直接锁住整个,1.6之后进行了很多优化

包括加入了偏向锁和轻量级锁,一开始没有其他线程竞争的时候,使用cas乐观锁锁住,这就是偏向锁,然后有人来竞争了升级成轻量级锁,如果乐观锁执行失败,代表竞争太激烈了,升级成重量级锁。

如果判断这个锁有没有在竞争,锁对象会有一个计数器,有一个线程在竞争就加一。

 sychonize可重入锁,在锁都是一样的时候,A方法和B方法都加了锁,都是this,A方法调B方法不需要再重新获取锁,直接进入。

sychonize是非公平锁,会有线程始终抢不到的问题,如果并发量高,多个线程一直在竞争,其中有一个就饿死了,就会出现我02分下的单都到了,01分下的单怎么还不见

 

而lock是为了更加灵活的手动释放锁,不想排队了退出,并且lock可以设置为公平锁,谁排队最久谁优先。

 

3.线程池

线程池能够更好的利用线程,减少生成和关闭线程的代价,也是一种预热,当有任务进来的时候如果线程数小于corePoolSize和新线程数,则创建一个线程去执行

如果当前线程大于corePoolSize就吧任务放到队列等待执行,如果超出maximumPoolSize则使用拒绝策略。

线程池主要参数:

corePoolSize :线程池的核心池大小,

maximumPoolSize线程池允许的最大线程数

任务缓存队列类型

在前面我们多次提到了任务缓存队列,即workQueue,它用来存放等待执行的任务。

workQueue的类型为BlockingQueue<Runnable>,通常可以取下面三种类型:

1)有界任务队列ArrayBlockingQueue:基于数组的先进先出队列,此队列创建时必须指定大小;

2)无界任务队列LinkedBlockingQueue:基于链表的先进先出队列,如果创建时没有指定此队列大小,则默认为Integer.MAX_VALUE;

3)直接提交队列synchronousQueue:这个队列比较特殊,它不会保存提交的任务,而是将直接新建一个线程来执行新来的任务。

拒绝策略

AbortPolicy:丢弃任务并抛出RejectedExecutionException

CallerRunsPolicy:只要线程池未关闭,该策略直接在调用者线程中,运行当前被丢弃的任务。显然这样做不会真的丢弃任务,但是,任务提交线程的性能极有可能会急剧下降。

DiscardOldestPolicy:丢弃队列中最老的一个请求,也就是即将被执行的一个任务,并尝试再次提交当前任务。

DiscardPolicy:丢弃任务,不做任何处理。

 

3.io模型

阻塞io,就是一直会等,就先我们socket 的read方法,没读到就会一直等。

非阻塞io 去获取资源的时候,如果没拿到返回error,不是一直等,好处是我们可以自己设定什么时候重试,也不会一直占用cpu。

多路复用io,现在用的最多的,像redis都是用这个才比较快,java的nio也是基于这个,其实就是一个线程管理多个io,轮询的去调用select方法查询每个socket有没有读写事件,有的话才让这个socket进行读取。

异步io,socket的read挂起的时候,当前线程可以去做别的事了,等真的有读写到来再回去进行处理,不会一直卡在那,不过这个是java1.7后才支持,也是我们称的aio

 

4.hashMap / concurrenthashmap

HashMap是一个键值对的数据结构,底层存储是先用hashCode确定到数组下标,然后数组里是链表,用eq方法查询key有没有重复,有就替换,没有就在末尾插入,在jdk1.8之后链表长度如果超过8就会转成红黑树结构,反之再转回链表,因为链表太长了全部遍历会比较费时间,而使用树结构有排序查找的复杂度会比较低,HashMap在插入数据的时候会如果容量达到%75会进行扩容,扩容到原来大小的两倍,而且扩容会重新计算hashCode比较费资源,所以能预估到的容量初始化的时候写上。

HashMAp的数组用transient进行修饰,arrayList这些也一样,这个是因为他需要自定义自己的序列化,比如现在hashMap的数组长度是16,但实际里面存储的只有1个值,那么序列化就会是 [值,null,null,null…..],不是我们想要的,所以自己实现了序列化的方法。

 

为什么线程不安全

多个线程访问的时候会数据不一致,线程A put要插入到链表某个位置了,然后线程切换,线程B也插入这个位置,就会进行覆盖了

 

线程安全的concurrentHashMap

如果使用HashTable,他使用syc来保证线程安全,这样同一时间只有一个线程能访问,效率极低,我们想想hashMap存储的结构是根据hashCode来的,是不是只需要锁住这个这个hashCode下的数据,所以concurrentHashMap引入了分段锁的概念,多加了一个Segment来对应的控制map数组的对应下标。

  1、线程A执行tryLock()方法成功获取锁,则把HashEntry对象插入到相应的位置;
  2、线程B获取锁失败,则执行scanAndLockForPut()方法,在scanAndLockForPut方法中,会通过重复执行tryLock()方法尝试获取锁,在多处理器环境下,重复次数为64,单处理器重复次数为1,当执行tryLock()方法的次数超过上限时,则执行lock()方法挂起线程         B;
   3、当线程A执行完插入操作时,会通过unlock()方法释放锁,接着唤醒线程B继续执行;

Jdk1.8之后实现方式变得简单了,直接在put的时候,如果有hash冲突就对这个数组下标使用syc进行加锁,

 

5.JVM

类加载过程:

加载: 根据这个类的全类名从class文件获取二进制流信息加载到内存中,生成这个类的class对象放到方法区(元空间)

验证:进行格式验证,元数据验证,比如这个类有没有继承object,重写重载这些有没有正确,引用验证,验证这个类引用的其他类,方法是否合法,有可能会抛出找不到某个类或者某个方法,按道理来说,能够编译成class的肯定都是编译通过的,但是class文件的字节码也是可以修改的,所以有必要再验证一下。

准备:这个阶段会初始化静态变量的值,赋予默认值 
解析:解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程
初始化: 会进行对象字段的初始化, 先父类后子类,先静态值,然后再非静态值

使用: 在创建对象的时候就会使用这个class对象来进行创建对象了
卸载:当这个class已经没有任何存活的实例以及没地方用到这class的时候,就可以进行回收

 

调优:

第一点,调整合适的gc频率和占用时间,因为fgc回收的时候会暂停其他线程,所以老年代不是设置得越大越好,设置大了,内存大,回收慢,卡住得久,设置小了  频繁gc。

第二点代码层面减少大对象和长期存活的对象,

设置堆内存 -Xmx, 默认是内存的64/1 最大4/1,最大最小设置不用偏差过大,因为加内存还要申请资源,我们服务器就只有java的话可以设置大一些

第三点设置线程占用的大小,默认1M,如果我们的业务调用链路没那么深可以调小一点

最后当然是通过压测进行参数调整的验证,可以使用jmeter对系统进行压测然后看gc.log 里的gc情况等信息来进行调整。Log可以放到网上进行图形化的分析和建议。

Jvm出现过的问题:

vim大日志,内存不够用 linux会杀掉第一占用的进程,就是java

死锁问题,资源不释放

导出时候list放对象,对象信息还比较多,几万个内存溢出,只能分批处理

 

6.并发包常用

  • ConcurrentHashMap
  • CopyOnWriteArrayList
  • CopyOnWriteArraySet
  • ArrayBlockingQueue
  • LinkedBlockingQueue

 

7.深拷贝 & 浅拷贝 & 0拷贝

浅拷贝:复制类的时候只是复制它的内容不复制他里面的对象,所以里面对象引用还是一样的

深拷贝:对象里的对象也一并拷贝出一份,不同引用

0拷贝,指的是linux操作系统层面的数据拷贝,如netty的快速也有0拷贝的一份功劳,就是我们的数据从磁盘到java内存会经过几个拷贝,如cpu空间,

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值