面试题
- java加锁有哪几种锁?
- 怎么处理并发?线程池有哪些核心参数?
- 简单说下lock
1.乐观锁和悲观锁
悲观锁:
认为自己在使用数据的时候一定有别的线程来修改线程,因此在获取数据的时候先加锁,确保数据不会被别的线程修改。
Synchronized关键字 和 Lock的实现类 都是悲观锁
适合写操作多的场景,先加锁可以保证写操作时数据正确。
显示的锁定之后在操作同步资源
一句话: 狼性锁
乐观锁:
认为自己在使用数据时 不会有别的线程修改数据 或 资源,所有不会添加锁。
在Java中通过使用 无锁编程 来实现,只是在更新数据的时候去判断,之前有没有别的线程更新了这个数据。
如果这个数据没有被更新,当前线程将自己修改的数据成功写入。
如果这个数据已经被其他线程更新,则根据不同的实现方式执行不同的操作,比如放弃修改、重试抢锁等等
判断规则
- 版本号机制Version
- 最常采用的是CAS算法,Java原子类中递增操作就通过CAS自旋实现的
2.八锁案例
8种锁的案例体现在3个地方
- 作用于实例方法,当前实例加锁,进入同步代码前要获得当前实例的锁;
- 作用于代码块,对括号里配置的对象加锁;
- 作用于静态方法,当前类加锁,进去同步代码前要获得当前类对象的锁;
1.标准访问有ab两个线程,请问先打印邮件还是短信
class Phone{
public synchronized void sendEmail(){
System.out.println("------sendEmail");
}
public synchronized void sendSMS(){
System.out.println("---------sendMessage");
}
}
public class Lock8Demo {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(()->{
phone.sendEmail();
},"a").start();
try {
TimeUnit.MILLISECONDS.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone.sendSMS();
},"b").start();
}
}
------sendEmail
---------sendMessage
2. SendEmail方法种加入了暂停3秒钟,请问先打印邮件还是短信
class Phone{
public synchronized void sendEmail(){
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("------sendEmail");
}
public synchronized void sendSMS(){
System.out.println("---------sendMessage");
}
}
public class Lock8Demo {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(()->{
phone.sendEmail();
},"a").start();
try {
TimeUnit.MILLISECONDS.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone.sendSMS();
},"b").start();
}
}
------sendEmail
--------sendMessage
3.添加一个普通的hello方法,请问先打印邮件还是hello
class Phone{
public synchronized void sendEmail(){
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("------sendEmail");
}
public synchronized void sendSMS(){
System.out.println("---------sendMessage");
}
public void hello(){
System.out.println("hello");
}
}
public class Lock8Demo {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(()->{
phone.sendEmail();
},"a").start();
try {
TimeUnit.MILLISECONDS.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
// phone.sendSMS();
phone.hello();
},"b").start();
}
}
------Hello
------sendEmail
4.有两部手机后,请问先打印哪一个
public class Lock8Demo {
public static void main(String[] args) {
Phone phone = new Phone();
Phone phone2 = new Phone();
new Thread(()->{
phone.sendEmail();
},"a").start();
try {
TimeUnit.MILLISECONDS.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
// phone.sendSMS();
//phone.hello();
phone2.sendSMS();
},"b").start();
}
}
class Phone{
public synchronized void sendEmail(){
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("------sendEmail");
}
public synchronized void sendSMS(){
System.out.println("---------sendMessage");
}
public void hello(){
System.out.println("hello");
}
}
---------sendMessage
------sendEmail
5.有两个静态同步方法,有1部手机,请问先打印邮件还是短信
class Phone{
public static synchronized void sendEmail(){
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("------sendEmail");
}
public static synchronized void sendSMS(){
System.out.println("---------sendMessage");
}
public void hello(){
System.out.println("hello");
}
}
public class Lock8Demo {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(()->{
phone.sendEmail();
},"a").start();
try {
TimeUnit.MILLISECONDS.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone.sendSMS();
//phone.hello();
//phone2.sendSMS();
},"b").start();
}
}
------sendEmail
---------sendMessage
6.有两个静态同步方法,有2部手机,请问先打印邮件还是短信
class Phone{
public static synchronized void sendEmail(){
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("------sendEmail");
}
public static synchronized void sendSMS(){
System.out.println("---------sendMessage");
}
public void hello(){
System.out.println("hello");
}
}
public class Lock8Demo {
public static void main(String[] args) {
Phone phone = new Phone();
Phone phone2 = new Phone();
new Thread(()->{
phone.sendEmail();
},"a").start();
try {
TimeUnit.MILLISECONDS.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
//phone.sendSMS();
//phone.hello();
phone2.sendSMS();
},"b").start();
}
}
------sendEmail
---------sendMessage
7. 有一个静态同步方法,有一个普通同步方法,有1部手机,请问先打印邮件还是短信
class Phone{
public synchronized void sendEmail(){
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("------sendEmail");
}
public static synchronized void sendSMS(){
System.out.println("---------sendMessage");
}
public void hello(){
System.out.println("hello");
}
}
public class Lock8Demo {
public static void main(String[] args) {
Phone phone = new Phone();
Phone phone2 = new Phone();
new Thread(()->{
phone.sendEmail();
},"a").start();
try {
TimeUnit.MILLISECONDS.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone.sendSMS();
//phone.hello();
//phone2.sendSMS();
},"b").start();
}
}
---------sendMessage
------sendEmail
8.有一个静态同步方法,有一个普通同步方法,有2部手机,请问先打印邮件还是短信
class Phone{
public synchronized void sendEmail(){
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("------sendEmail");
}
public static synchronized void sendSMS(){
System.out.println("---------sendMessage");
}
public void hello(){
System.out.println("hello");
}
}
public class Lock8Demo {
public static void main(String[] args) {
Phone phone = new Phone();
Phone phone2 = new Phone();
new Thread(()->{
phone.sendEmail();
},"a").start();
try {
TimeUnit.MILLISECONDS.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
//phone.sendSMS();
//phone.hello();
phone2.sendSMS();
},"b").start();
}
}
---------sendMessage
------sendEmail
八锁总结
- 1-2 :一个对象里面的如果有多个Synchronized 方法,某一时刻内,只要一个线程去调用其中的一个synchronized方法了,其他的线程就只能等待,换句话说,某一时刻内,只能有唯一的一个线程去访问这些synchronized方法 ,锁的是当前对象this,被锁定后,其他的线程都不能进入到当前对象的其它的synchronized方法
- 3-4: 加个普通方法后发现和同步锁无关,换成两个对象后,不是同一把锁了 , 情况立刻变化
- 5-6:都换成静态同步方法后,情况又变化,三种 synchronized 锁的内容有一些差别:1. 对于普通的同步方法,锁的是当前实例对象 ,通常指this,具体的一部手机,所有的普通同步方法用的都是同一把锁--》实例对象本身,2. 对于静态同步方法,锁的是当前类的class对象 ,如phone,class唯一的一个模板,3. 对于同步方法块,锁的是synchronized括号内的对象
- 7-8:对象锁和类锁是不同的锁,锁的东西不一样, 当一个线程试图访问同步代码时它首先必须得到锁,正常退出或抛出异常时必须释放锁。所有的静态同步方法用的也是同一把锁——类对象本身,就是我们说过的唯一模板Class 具体实例对象this和唯一模板Class,这两把锁是两个不同的对象,所以静态同步方法与普通同步方法之间是不会有竞态条件的 但是一旦一个静态同步方法获取锁后,其他的静态同步方法都必须等待该方法释放锁后才能获取锁。.
3.从字节码角度分析synchronized实现
1.JDK源码notify说明
锁的三个地方:
- 修饰实例方法,作用于当前实例,进入同步代码前需要先获取实例的锁
- 修饰静态方法,作用于类的Class对象,进入修饰的静态方法前需要先获取类的Class对象的锁
- 修饰代码块,需要指定加锁对象(记做lockobj),在进入同步代码块前需要先获取lockobj的锁
2.syschronized实现
javap -c ***.class 文件 反编译
1.同步代码块
public class LockSynDemo {
Object object = new Object();
public LockSynDemo() {
}
public void m1() {
synchronized(this.object) {
System.out.println("----hello synchronized");
}
}
public static void main(String[] args) {
}
}
monitorenter 进入锁 monitorexit 退出锁
为什么两个monitorexit: 保证异常情况也能退出
一定时一个enter 两个exit吗?
一般情况就是一个enter 对应两个exit
极端,当同步代码块里面出现了 抛出异常
2.普通同步方法
javap -v ***.class
调用指令将会检查方法的ACC_SYNCHRONIZED访问标志是否被设置。
如果设置了,执行线程会将现持有monitor锁,然后在执行方法,最后在方法完成(无论是正常完成还是非正常完成)时释放monitor
3.静态同步方法
javap -v ***.class
3.反编译synchronized锁的是什么
面试题:为什么每一个对象都可以成为一个锁
所有对象都继承Object
在HotSpot虚拟机中,monitor采用ObjectMonitor实现
- c++ 源码解读 : ObjectMonitor.java ->ObjectMonitor.cp-->ObjectMonitor.hpp
- ObjectMonitor.hpp
- 每个对象都天生带着一个对象监视器
- 每一个被锁住的对象都会和Monitor关联起来
4.公平锁和非公平锁
1.恢复挂起的线程到真正锁的获取还是有时间差的,从开发人员来看这个时间微乎其微,但是从CPU的角度来看,这个时间差存在还是很明显的,所以非公平锁能更充分的利用CPU的时间片,尽量减少CPU空闲状态时间。
2.使用多线程很重要的考量点是线程切换的开销,当采用非公平锁时,当1个线程请求锁获取同步状态,然后释放同步状态,所以刚释放锁的线程在此刻再次获取同步状态的概率就变得非常大,所以就减少了线程的开销
5.可重入锁
可重入锁又名递归锁
是指在同一个线程在外层方法获取锁的时候,在进入该线程的内层方法会自动获取锁(前提,锁对象是同一个对象),不会因为之前已经获取过还没释放而阻塞。
如果是1个有synchronized修饰的递归调用方法,程序第二次进入被自己阻塞了岂不是天大的笑话。
所以Java中的ReentrantLock 和 synchronized都是可重入锁,可重入锁的一个优点是可一定程度避免死锁
可:可以
重:再次
入:进入
锁:同步锁
进入什么:进入同步域(即同步代码块/方法或显式锁锁定的代码)
一句话: 1.一个线程中的多个流程可以获取同一把锁,持有这把锁的可以再次进入
2.自己可以获取自己的内部锁
1.可重入锁种类
隐式锁
即Synchronized关键字使用的锁默认是可重入锁
- 同步块
- 同步方法
Synchronized的重入的实现机理
为什么任何一个对象都可以成为一个锁?
objectMonitor.hpp(监视器)
_owner | 指向持有ObjectMonitor对象的线程 |
---|---|
_WaitSet | 存放处理wait状态的线程队列 |
_EntryList | 存放处于等待锁BLOCK状态的线程队列 |
_recursions | 锁的重入次数 |
_count | 用来记录该线程获取锁的次数 |
每个锁对象拥有一个锁计数器和一个指向持有该锁的线程的指针
当执行monitorenter时,如果目标锁对象的计数器为零,那么说明它没有被其他线程持有,java虚拟器会将该锁对象的持有线程设置为当前线程,并且将计数器加1。
在目前锁对象的计数器不为零的情况下,如果锁对象的持有对象是当前线程,那么java虚拟机可以将其计数器加1,否则需要等待,直至持有线程释放该锁。
当执行monitorexit,Java虚拟器则需将锁对象的计数器减1。计数器为零代表锁已经被释放
显式锁(即Lock)
6.死锁及排查
1.死锁是什么
-
互斥条件:该资源任意一个时刻只由一个线程占用。
-
请求与保持条件:一个线程因请求资源而阻塞时,对已获得的资源保持不放。
-
不剥夺条件:线程已获得的资源在未使用完之前不能被其他线程强行剥夺,只有自己使用完毕后才释放资源。
-
循环等待条件:若干线程之间形成一种头尾相接的循环等待资源关系。
死锁是指两个或两个以上的线程在执行过程中,因争夺资源而造成一种互相等待的现象,若无外力干涉那它们无法推进下去,如果系统资源充足,进程的资源请求都能够得到满足,死锁出现的可能性就很低,否则就会因争夺有限的资源而陷入死锁。
2.死锁代码
package com.lyj.sc.duoThread.lockStudy;
import java.util.concurrent.TimeUnit;
/**
* @Author: liyangjing
* @Date: 2022/08/11/22:49
* @Description:
*/
public class DeadLockDemo {
public static void main(String[] args) {
final Object objectA = new Object();
final Object objectB = new Object();
new Thread(()->{
synchronized (objectA){
System.out.println(Thread.currentThread().getName()+"\t 自己持有A锁,希望获取B锁");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (objectB){
System.out.println(Thread.currentThread().getName()+"\t 成功获取B锁");
}
}
},"A").start();
new Thread(()->{
synchronized (objectB){
System.out.println(Thread.currentThread().getName()+"\t 自己持有b锁,希望获取a锁");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (objectA){
System.out.println(Thread.currentThread().getName()+"\t 成功获取a锁");
}
}
},"B").start();
}
}
A 自己持有A锁,希望获取B锁
B 自己持有b锁,希望获取a锁
系统资源不足,资源分配不合理
3.如何排查死锁
jps 类似于 ps ef|
jstack + 进程编号 打印堆栈信息可以查看到死锁
图形控制台 jconsole
win+R 打开
检测死锁
小总结
指针指向monitor对象(也称为管程或监视器锁)的起始地址。每个对象都存在着一个monitor与之关联,当一个monitor被某个线程持有后,他便处于锁定状态,在Java虚拟机(HotSpot)中,monitor是由ObjectMonitor实现的,其主要数据结构如下(位于HotSpot虚拟机源码ObjectMonitor.hpp文件,c++实现的)