- synchronized与ReentrantLock可重入锁的区别?
1、锁的实现:Synchronized是依赖于JVM实现的,而ReenTrantLock是JDK实现的
2、性能的区别:在Synchronized优化以前,synchronized的性能是比 ReenTrantLock差很多的,但是自从Synchronized引入了偏向锁,轻量级锁(自旋锁) 后,两者的性能就差不多了,在两种方法都可用的情况下,官方甚至建议使用synchronized
3、功能区别:Synchronized的使用比较方便简洁,并且由编译器去保证锁的加锁和释放,而ReenTrantLock需要手工声明来加锁和释放锁,为了避免忘记手工释放锁造成死锁,所以最好在finally中声明释放锁
4、ReenTrantLock独有的能力:
(1)ReenTrantLock可以指定是公平锁还是非公平锁。而synchronized只能是非公平锁。所谓的公平锁就是先等待的线程先获得锁。
(2)ReenTrantLock提供了一个Condition(条件)类,用来实现分组唤醒需要唤醒的线程们,而不是像synchronized要么随机唤醒一个线程要么唤醒全部线程。
(3)ReenTrantLock提供了一种能够中断等待锁的线程的机制,通过lock.lockInterruptibly()来实现这个机制。
- 乐观锁和悲观锁的区别?
悲观锁(Pessimistic Lock)
具有强烈的独占和排他特性。每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。
乐观锁(Optimistic Lock)
每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库如果提供类似于write_condition机制的其实都是提供的乐观锁。
- 如何实现一个乐观锁?
使用版本号机制和CAS算法实现
1. 版本号机制
一般是在数据表中加上一个数据版本号version字段,表示数据被修改的次数,当数据被修改时,version值会加一。当线程A要更新数据值时,在读取数据的同时也会读取version值,在提交更新时,若刚才读取到的version值为当前数据库中的version值相等时才更新,否则重试更新操作,直到更新成功。
2. CAS算法
即compare and swap(比较与交换),是一种有名的无锁算法。无锁编程,即不使用锁的情况下实现多线程之间的变量同步,也就是在没有线程被阻塞的情况下实现变量的同步,所以也叫非阻塞同步(Non-blocking Synchronization)。CAS算法涉及到三个操作数
需要读写的内存值 V
进行比较的值 A
拟写入的新值 B
当且仅当 V 的值等于 A时,CAS通过原子方式用新值B来更新V的值,否则不会执行任何操作(比较和替换是一个原子操作)。一般情况下是一个自旋操作,即不断的重试。
- AQS是如何唤醒下一个线程的?
用unpark函数唤醒等待队列中最前边的那个未放弃线程
- ReentrantLock公平和非公平锁是如何实现?
非公平:
1.调用lock()方法时,首先去通过CAS尝试设置锁资源的state变量,如果设置成功,则设置当前持有锁资源的线程为当前请求线程
2.调用tryAcquire方法时,首先获取当前锁资源的state变量,如果为0,则通过CAS去尝试设置state,如果设置成功,则设置当前持有锁资源的线程为当前请求线程
公平:
1.调用lock()方法时,不进行CAS尝试
2.调用tryAcuqire方法时,首先获取当前锁资源的state变量,如果为0,则判断该节点是否是头节点可以去获取锁资源,如果可以才通过CAS去尝试设置state
- CountDownLatch和CyclicBarrier的区别?各自适用于什么场景?
CountDownLatch : 一个线程(或者多个), 等待另外N个线程完成某个事情之后才能执行。 CountDownLatch 是计数器, 线程完成一个就记一个, 就像 报数一样, 只不过是递减的.
CyclicBarrier : N个线程相互等待,任何一个线程完成之前,所有的线程都必须等待。CyclicBarrier更像一个水闸, 线程执行就像水流, 在水闸处都会堵住, 等到水满(线程到齐)了, 才开始泄流.
- 使用ThreadLocal时要注意什么?比如说内存泄漏?
ThreadLocal是用来维护线程中的变量不被其他线程干扰而出现的一个结构,内部包含一个ThreadLocalMap类,该类为Thread类的一个局部变量,该Map存储的key为ThreadLocal对象自身,value为我们要存储的对象,这样一来,在不同线程中,持有的其实都是当前线程的变量副本,与其他线程完全隔离,以此来保证线程执行过程中不受其他线程的影响。
ThreadLocal的主要用途是为了保持线程自身对象和避免参数传递,主要适用场景是按线程多实例(每个线程对应一个实例)的对象的访问,并且这个对象很多地方都要用到。
- 说一说往线程池里提交一个任务会发生什么?
判断当前线程数是否小于corePoolSize,如果小于,则新建核心线程,不管核心线程是否处于空闲状态
核心线程创建满之后,后续的任务添加到workQueue中
如果workQueue满了,则开始创建非核心线程直到线程的总数为maximumPoolSize
当非核心线程数也满了,队列也满了的时候,执行拒绝策略
- 线程池的几个参数如何设置?
corePoolSize:核心线程数
-
- 核心线程会一直存活,及时没有任务需要执行
- 当线程数小于核心线程数时,即使有线程空闲,线程池也会优先创建新线程处理
- 设置allowCoreThreadTimeout=true(默认false)时,核心线程会超时关闭
queueCapacity:任务队列容量(阻塞队列)
-
- 当核心线程数达到最大时,新任务会放在队列中排队等待执行
maxPoolSize:最大线程数
-
- 当线程数>=corePoolSize,且任务队列已满时。线程池会创建新线程来处理任务
- 当线程数=maxPoolSize,且任务队列已满时,线程池会拒绝处理任务而抛出异常
- keepAliveTime:线程空闲时间
-
- 当线程空闲时间达到keepAliveTime时,线程会退出,直到线程数量=corePoolSize
- 如果allowCoreThreadTimeout=true,则会直到线程数量=0
- allowCoreThreadTimeout:允许核心线程超时
- rejectedExecutionHandler:任务拒绝处理器
两种情况会拒绝处理任务:
-
- 当线程数已经达到maxPoolSize,切队列已满,会拒绝新任务
- 当线程池被调用shutdown()后,会等待线程池里的任务执行完毕,再shutdown。如果在调用shutdown()和线程池真正shutdown之间提交任务,会拒绝新任务
- 线程池会调用rejectedExecutionHandler来处理这个任务。如果没有设置默认是AbortPolicy,会抛出异常
ThreadPoolExecutor类有几个内部实现类来处理这类情况:
-
- AbortPolicy 丢弃任务,抛运行时异常
- CallerRunsPolicy 执行任务
- DiscardPolicy 忽视,什么都不会发生
- DiscardOldestPolicy 从队列中踢出最先进入队列(最后一个执行)的任务
- 实现RejectedExecutionHandler接口,可自定义处理器
- 线程池的非核心线程什么时候会被释放?
当系统中非核心线程(额外创建的线程)闲置时间超过 keepAliveTime 之后,会被释放;或者执行过程中抛出异常时也会被释放
- 如何排查死锁?
死锁概念:1)必须是两个或者两个以上进程(线程)(2)必须有竞争资源
1.我们先用Jps来查看java进程id(或者Linux的ps命令)
2.看一下jstack的使用
3.jstack输出线程dump信息到文件
4.查看dump文件,然后进行分析
CPU100%排查
1、使用top命令查看cpu占用资源较高的PID
2、通过jps 找到当前用户下的java程序PID
执行 jps -l 能够打印出所有的应用的PID,找到有一个PID和这个cpu使用100%一样的ID!!就知道是哪一个服务了。
3、 使用 pidstat -p 1 3 -u -t
4、找到cpu占用较高的线程TID
5、将TID转换为十六进制的表示方式
6、通过jstack -l 输出当前进程的线程信息
7、查找 TID对应的线程(输出的线程id为十六进制),找到对应的代码
压力测试使用jstack找到系统的代码性能问题
1、在进行压力测试的时候,使用jps找到应用的PID
2、然后使用jstack输出出压力测试时候应用的dump信息
3、分析输出的日志文件中那个方法block线程占用最多,这里可能是性能有问题,找到对应的代码分析