基础知识面试题

1 Java基础

1、JDK和JRE有什么区别?

JDK是java的开发工具包,有JDK8,9甚至到14的差别,安装以后,不仅包含了java 的开发环境,比如java.exe,还包含了运行环境(jre)相关包。

2、说下你对==和equals的认识,它们有什么差别?(高频)

对于==号:

1、比较基本类型,比如int等,==比较的是值是否相同;

2、引用类型,比如自定义对象:比较地址是否相同;

对于equals方法

equals方法只能用来比较引用数据类型,在Object类中所定义的equals方法底层也是通过==号比较对象的地址值。但是如果一个类重写了equals方法此时

就可以比较内容了。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6dZf9I1W-1684156951429)(images/image-20220501110826738.png)]

3、如果两个对象的 hashCode值一样,则它们用equals()比较也是为 true,是不是?(高频)

不是。

Object里,hashCode和equals是两个不同的方法,默认hashCode是返回对象地址,equals方法也是对比地址;

两者不是一回事,可以通过重写对象的hashCode方法,让不同值的对象有相同的hashCode,但它们的equals方法未必相同。

如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pDGrbXpL-1684156951430)(images/image-20220501111756208.png)]

4、综合说下final的作用?

修饰在类上,该类不能被继承。

修饰在方法上,该方法不能被重写。

修饰在变量上,叫常量,该常量必须初始化,初始化之后值就不能被修改,而常量一般全都是用大写来命名。

5、Math.round(-2.5) 等于多少?

结果是-2

源码注释说明:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qnPfIWOH-1684156951431)(images/image-20220501171234230.png)]

6、String 是基本数据类型吗?

String 不是基本数据类型,基础类型有8种:byte、boolean、char、short、int、float、long、double。String属于引用数据类型!

7、对字符串操作都有哪些类并详细的介绍一下它们之间的区别?(高频)

具体有String、StringBuffer 和 StringBuilder 这三个类。

String是不可变类,每次操作都会生成新的String对象,并将结果指针指向新的对象,由此会产生内存碎片。

如果要频繁对字符串修改,建议采用StringBuffer 和 StringBuilder。

StringBuffer 和 StringBuilder的差别在于,StringBuffer 是线程安全的,而 StringBuilder 是非线程安全的,由于无需维护线程安全的操作,所以

StringBuilder 的性能要高于 StringBuffer,所以在单线程环境下推荐使用 StringBuilder,多线程环境下推荐使用 StringBuffer。由于大多数环境下是

单线程,所以大多是用 StringBuilder。

8、String str="abc"与 String str=new String(“abc”)的定义方法一样吗?

不一样

String str = “abc” ; 是在常量池中创建一个字符串对象abc

String str = new String(“abc”); 创建了两个对象,“abc"字符串对象在常量池中,new String(”")在堆内存中又创建了一个新的对象。

9、String 类的常用方法都有那些?(高频)

indexOf():返回指定字符的索引。

length():返回字符串长度。

equals():字符串比较。

replace():字符串替换。

trim():去除字符串两端空白。

split():分割字符串,返回一个分割后的字符串数组。

toLowerCase():将字符串转成小写字母。

toUpperCase():将字符串转成大写字符。

substring():截取字符串。

reverse(): 对字符串进行反转

10、抽象类必须要有抽象方法吗?

不需要的,抽象类中可以没有抽象方法。但是存在抽象方法的类必须是抽象类!抽象类常见的应用场景是模板设计模式,通过具体的方法定义代码执行的骨

架,将变化的内容定义成抽象方法,交由子类进行实现。

11、一般的类和抽象类有哪些区别?

一般的类不能包含没有方法体的抽象方法,而抽象类可以包含抽象方法。抽象类不能直接用new来实例化,普通类可以直接实例化。

12、抽象类能使用 final 修饰吗?

不能

定义抽象类的本意是被其他的类进行继承,如果定义为 final 该类就不能被继承,这样就会有矛盾,所以 final 不能修饰抽象类。

13、接口和抽象类有什么区别?(高频)

抽象类的子类要用 extends 来继承;而实现接口要用 implements 。

抽象类可以定义构造函数,而接口不能。

抽象类里可以定义 main 方法,但接口不能有 main 方法。

实现数量:类可以实现很多个接口;但是只能继承一个抽象类。

访问修饰符:接口中的方法默认使用 public 修饰;抽象类中的方法可以是任意访问修饰符。

14、java 中 IO 流分为几种?

按功能来分可以分输入流(input)和输出流(output)。

从类型来分可以是字节流和字符流。

15、BIO、NIO、AIO 有什么区别?(高频)

BIO的英语全称是Block IO, 同步阻塞式 IO,就是平常经常使用的传统 IO,特点是简单方便,但并发处理能力低。

NIO,叫New IO, 同步非阻塞 IO,客户端和服务器端通过 Channel(通道)通讯,实现了多路复用(由一个线程管理多个通道默认管理的通道数量为

1024个)。

AIO,Asynchronous IO, 是 NIO 的升级,实现了异步非堵塞 IO ,它是基于事件和回调机制。

16、Files的常用方法都有哪些?

Files.exists():检测路径是否存在。

Files.createFile():创建文件。

Files.createDirectory():创建文件夹。

Files.delete():删除文件或文件夹。

Files.copy():复制文件。

Files.move():移动文件,即复制后删除。

Files.size():查看文件的个数。

Files.read():读取文件。

Files.write():写入文件。

2 Java集合

17、java 的集合容器都有哪些?(高频)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tV1U4S3Z-1684156951431)(images/image-20220501174636413.png)]

18、Collection和Collections有什么区别?

Collection 是一个单列集合的顶层接口。

Collections是集合类的一个工具类,包含了对集合元素进行排序和线程安全等各种操作方法。

19、List、Set、Map 之间的区别是什么?(高频)

List集合

1、属于单列集合,存储的都是一个一个元素

2、List和存在重复元素

3、每一个元素都有对应的索引,可以通过索引获取元素

4、元素的存取顺序一致

Set集合

1、属于单列集合,存储的都是一个一个元素

2、元素唯一

3、元素没有索引,不能通过索引获取元素

4、不能保证元素的存取顺序一致

Map集合:

1、属于双列集合,存储的是一对一对的元素

20、HashMap 和 Hashtable 有什么区别?(高频)

相同点:

1、HashMap和Hashtable都实现了Map接口

2、都可以存储key-value数据

不同点:

1、HashMap可以把null作为key或value,Hashtable不可以

2、HashMap线程不安全,效率高。Hashtable线程安全,效率低。

21、如何决定使用 HashMap 还是 TreeMap?

TreeMap可以对元素进行排序,因此如果需要对元素按照某一种规则进行排序,此时可以选择TreeMap。没有特殊要求一般选择HashMap。

22、ArrayList 和 LinkedList 的区别是什么?(高频)

ArrrayList底层实现的数据结构是数组,查询元素较快,增删较慢!LinkedList 的底层数据结构是双向循环链表,查询速度较慢,增删较快!

23、如何做到数组和 List之间的转换?

List对象转换成为数组:可以调用ArrayList(或其它List)的toArray方法。

数组转换成为List:调用Arrays的asList方法。

24、ArrayList 和 Vector 的区别是什么?

Vector是线程安全的,而ArrayList不是。所以在单线程情况下,建议使用ArrayList

在扩容时,Vector是扩容100%,但ArrayList是50%,后者更节省内存

结论:大多数开发场景是单线程环境,所以建议使用ArrayList

25、哪些集合类是线程安全的?

Vector:就比arraylist多了个同步化机制(线程安全)

Stack :堆栈类,先进后出,项目中用得并不多。

Hashtable:就比hashmap多了个线程安全,所以建议使用HashMap。

26、ConcurrentHashMap和Hashtable的区别?(高频)

ConcurrentHashMap仅仅锁定map的某个部分,而Hashtable则会锁定整个map

hashtable(同一把锁):使用 synchronized 来保证线程安全,但效率非常低下。当一个线程访问同步方法时,其他线程也访问同步方法,可能会进入阻塞或

轮询状态,如使用 put添加元素,另一个线程不能使用 put 添加元素,也不能使用 get,竞争会越来越激烈效率越低。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8DX36IOy-1684156952143)(images/image-20220204160641098.png)]

concurrenthashmap(分段锁):(锁分段技术)每一把锁只锁容器其中一部分数据,多线程访问容器里不同数据段的数据,就不会存在锁竞争,提高并发访问

率。

首先将数据分为一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据时,其他段的数据也能被其他线程访问。

concurrenthashmap 是由 Segment 数组结构和 HahEntry 数组结构组成。

Segment 是一种可重入锁 ReentrantLock,扮演锁的角色。HashEntry 用于存储键值对数据。一个 concurrenthashmap 里包含一个 Segment 数组。

Segment 的结构和 Hashmap类似,是一种数组和链表结构,一个 Segment 包含一个 HashEntry 数组,每个 HashEntry是一个链表结构的元素,每个

Segment 守护着一个HashEntry数组里的元素,当对 HashEntry数组的数据进行修改时,必须首先获得对应的Segment。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-spJuaKcU-1684156952143)(images/image-20220204163028963.png)]

在JDK1.8中为了进一步优化ConcurrentHashMap的性能,去掉了Segment分段锁的设计。在数据结构方面,则是跟HashMap一样,使用一个哈希表

table

数组。(数组 + 链表 + 红黑树) 而线程安全方面是结合CAS机制 + 局部锁实现的,减低锁的粒度,提高性能。同时在HashMap的基础上,对哈希表table数

组和链表节点的value,next指针等使用volatile来修饰,从而实现线程可见性。

② ConcurrentHashMap在进行遍历的时候使用的是安全失败,Hashtable在遍历的时候使用的是快速失败。

27、jdk1.7到jdk1.8HashMap发生了什么变化(底层)?(高频)

1.8 之后 hashMap 的数据结构发生了变化,从之前的单纯的数组+链表结构变成数组 + 链表 + 红黑树。也就是说在 JVM 存储 hashMap 的 K-V 时仅仅

通过 key 来决定每一个 entry 的存储槽位(Node[]中的 index)。并且 Value 以链表的形式挂在到对应槽位上(1.8 以后如果 value长度大于 8 则转为红

黑树)。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3FWaPbzd-1684156952144)(images/image-20220204150843628.png)]

28、HashMap的扩容原理?(高频)

初始化容量为16,达到阈值进行扩容。阈值 = 最大容量 * 负载因子(0.75),扩容每次2倍,总是2的n次方。

扩容机制:使用一个容量更大的数组替代已有的容量小的数组,transfer()方法将原有的Entry数组的元素拷贝到新的Entry数组里。

3 Java多线程

29、并行和并发有什么区别?

并行:多个任务在计算机中同时执行

并发:多个任务在计算机中交替执行

举例:吃馒头

30、线程和进程的区别?

进程是程序运行和资源分配的基本单位,一个程序至少有一个进程,一个进程至少有一个线程,但一个进程一般有多个线程。

进程在运行过程中,需要拥有独立的内存单元,否则如果申请不到,就会挂起。而多个线程能共享内存资源,这样就能降低运行的门槛,从而效率更高。

线程是是cpu调度和分派的基本单位,在实际开发过程中,一般是考虑多线程并发。

31、守护线程是什么?

守护线程(daemon thread),是个服务线程,用来监视和服务其它线程。

32、线程的创建方式?(高频)

继承Thread类

public class MyThread extends Thread {

    @Override
    public void run() {
        System.out.println("MyThread...run...");
    }

    
    public static void main(String[] args) {

        // 创建MyThread对象
        MyThread t1 = new MyThread() ;
        MyThread t2 = new MyThread() ;

        // 调用start方法启动线程
        t1.start();
        t2.start();

    }
    
}

实现runnable接口

public class MyRunnable implements Runnable{

    @Override
    public void run() {
        System.out.println("MyRunnable...run...");
    }

    public static void main(String[] args) {

        // 创建MyRunnable对象
        MyRunnable mr = new MyRunnable() ;

        // 创建Thread对象
        Thread t1 = new Thread(mr) ;
        Thread t2 = new Thread(mr) ;

        // 调用start方法启动线程
        t1.start();
        t2.start();

    }

}

实现Callable接口

public class MyCallable implements Callable<String> {

    @Override
    public String call() throws Exception {
        System.out.println("MyCallable...call...");
        return "OK";
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {

        // 创建MyCallable对象
        MyCallable mc = new MyCallable() ;

        // 创建F
        FutureTask<String> ft = new FutureTask<String>(mc) ;

        // 创建Thread对象
        Thread t1 = new Thread(ft) ;
        Thread t2 = new Thread(ft) ;

        // 调用start方法启动线程
        t1.start();

        // 调用ft的get方法获取执行结果
        String result = ft.get();

        // 输出
        System.out.println(result);

    }

}

线程池创建线程

public class MyRunnable implements Runnable{

    @Override
    public void run() {
        System.out.println("MyRunnable...run...");
    }

    public static void main(String[] args) {

        // 创建线程池对象
        ExecutorService threadPool = Executors.newFixedThreadPool(3);
        threadPool.submit(new MyRunnable()) ;

        // 关闭线程池
        threadPool.shutdown();

    }

}
33、说一下 runnable 和 callable 有什么区别?(高频)

Runnable接口中的run()方法的返回值是void,在其中可以定义线程的工作任务,但无法返回值。

Callable接口中的call()方法是有返回值的,是一个泛型,一般会和Future、FutureTask配合,能异步地得到线程的执行结果。

34、sleep() 和 wait() 有什么区别?(高频)

sleep():这是线程类(Thread)的静态方法,让线程进入睡眠状态,等休眠时间结束后,线程进入就绪状态,和其他线程一起竞争cpu的执行时间。

因为sleep() 是static静态的方法,他不能改变对象的机锁,当一个synchronized块中调用了sleep() 方法,线程虽然进入休眠,但是对象的机锁没有被释

放,其他线程依然无法访问这个对象,这时就会引发问题,此类现象请注意。

wait():wait()是Object类的方法,当一个线程执行到wait方法时,它就进入到一个和该对象相关的等待池,同时释放对象的机锁,使得其他线程能够访

问,可以通过notify,notifyAll方法来唤醒等待的线程。

35、notify()和 notifyAll()有什么区别?(高频)

notifyAll:唤醒所有wait的线程

notify:只随机唤醒一个 wait 线程

36、线程的 run()和 start()有什么区别?(高频)

start(): 用来启动线程,通过该线程调用run方法执行run方法中所定义的逻辑代码。start方法只能被调用一次。

run(): 封装了要被线程执行的代码,可以被调用多次。

37、如何停止一个正在运行的线程?(高频)

使用退出标志,使线程正常退出

public class MyThread extends Thread {

    volatile boolean flag = false ;     // 线程执行的退出标记

    @Override
    public void run() {
        while(!flag) {
            System.out.println("MyThread...run...");
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {

        // 创建MyThread对象
        MyThread t1 = new MyThread() ;
        t1.start();

        // 主线程休眠6秒
        Thread.sleep(6000);

        // 更改标记为true
        t1.flag = true ;

    }
}

使用stop方法强行终止

public class MyThread extends Thread {

    volatile boolean flag = false ;     // 线程执行的退出标记

    @Override
    public void run() {
        while(!flag) {
            System.out.println("MyThread...run...");
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {

        // 创建MyThread对象
        MyThread t1 = new MyThread() ;
        t1.start();

        // 主线程休眠2秒
        Thread.sleep(6000);

        // 调用stop方法
        t1.stop();

    }
}

使用interrupt方法中断线程

public class MyThread extends Thread {

    volatile boolean flag = false ;     // 线程执行的退出标记

    @Override
    public void run() {
        while(!flag) {
            System.out.println("MyThread...run...");
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {

        // 创建MyThread对象
        MyThread t1 = new MyThread() ;
        t1.start();

        // 主线程休眠2秒
        Thread.sleep(6000);

        // 调用interrupt方法
        t1.interrupt();

    }
}
38、有三个线程 T1 , T2 , T3 ,如何保证顺序执行?(高频)

在多线程中有多种方法让线程按特定顺序执行,你可以用线程类的join()方法在一个线程中启动另一个线程,另外一个线程完成该线程继续执行。

public class JoinTest {

    public static void main(String[] args) {

        // 创建线程对象
        Thread t1 = new Thread(() -> {
            System.out.println("t1");
        }) ;

        Thread t2 = new Thread(() -> {
            try {
                t1.join();                          // 加入线程t1,只有t1线程执行完毕以后,再次执行该线程
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("t2");
        }) ;


        Thread t3 = new Thread(() -> {
            try {
                t2.join();                              // 加入线程t2,只有t2线程执行完毕以后,再次执行该线程
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("t3");
        }) ;

        // 启动线程
        t1.start();
        t2.start();
        t3.start();

    }

}

39、线程有哪些状态?

Java中的线程状态被定义在了java.lang.Thread.State枚举类中,State枚举类的源码如下:

public class Thread {
    
    public enum State {
    
        /* 新建 */
        NEW , 

        /* 可运行状态 */
        RUNNABLE , 

        /* 阻塞状态 */
        BLOCKED , 

        /* 无限等待状态 */
        WAITING , 

        /* 计时等待 */
        TIMED_WAITING , 

        /* 终止 */
        TERMINATED;
    
	}
    
    // 获取当前线程的状态
    public State getState() {
        return jdk.internal.misc.VM.toThreadState(threadStatus);
    }
    
}

通过源码我们可以看到Java中的线程存在6种状态,每种线程状态的含义如下

线程状态具体含义
NEW一个尚未启动的线程的状态。也称之为初始状态、开始状态。线程刚被创建,但是并未启动。还没调用start方法。MyThread t = new MyThread()只有线程象,没有线程特征。
RUNNABLE当我们调用线程对象的start方法,那么此时线程对象进入了RUNNABLE状态。那么此时才是真正的在JVM进程中创建了一个线程,线程一经启动并不是立即得到执行,线程的运行与否要听令与CPU的调度,那么我们把这个中间状态称之为可执行状态(RUNNABLE)也就是说它具备执行的资格,但是并没有真正的执行起来而是在等待CPU的度。
BLOCKED当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked状态;当该线程持有锁时,该线程将变成Runnable状态。
WAITING一个正在等待的线程的状态。也称之为等待状态。造成线程等待的原因有两种,分别是调用Object.wait()、join()方法。处于等待状态的线程,正在等待其他线程去执行一个特定的操作。例如:因为wait()而等待的线程正在等待另一个线程去调用notify()或notifyAll();一个因为join()而等待的线程正在等待另一个线程结束。
TIMED_WAITING一个在限定时间内等待的线程的状态。也称之为限时等待状态。造成线程限时等待状态的原因有三种,分别是:Thread.sleep(long),Object.wait(long)、join(long)。
TERMINATED一个完全运行完成的线程的状态。也称之为终止状态、结束状态

各个状态的转换,如下图所示:

image-1571652681276
40、讲一下synchronized关键字的底层原理?(高频)

① synchronized同步代码块的情况

public class SynchronizedDemo {

    public void method() {

        synchronized (this) {
            System.out.println("synchronized 代码块");
        }

    }
    
}

通过javap查看字节码文件信息,如下所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sCaCrAvJ-1684156951432)(images/image-20220205094004602.png)]

从上面我们可以看出:synchronized 同步语句块的实现使用的是 monitorentermonitorexit 指令,其中monitorenter 指令指向同步代码块的开始

位置,monitorexit 指令则指明同步代码块的结束位置。当执行 monitorenter 指令时,线程试图获取锁也就是获取 monitor(monitor对象存在于每个

Java对象的对象头中,synchronized 锁便是通过这种方式获取锁的,也是为什么Java中任意对象可以作为锁的原因) 的持有权。当计数器为0则可以成功

获取,获取后将锁计数器设为1也就是加1。相应的在执行monitorexit 指令后,将锁计数器设为0,表明锁被释放。如果获取对象锁失败,那当前线程就要

阻塞等待,直到锁被另外一个线程释放为止。

② synchronized修饰方法的的情况

public class SynchronizedDemo2 {

    public synchronized void method() {
        System.out.println("synchronized 方法");
    }

}

通过javap(javap -v xxx.class)查看字节码文件信息,如下所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-17YFqZGY-1684156951432)(images/image-20220205094926130.png)]

synchronized 修饰的方法并没有 monitorenter 指令和 monitorexit 指令,取得代之的确实是ACC_SYNCHRONIZED 标识,该标识指明了该方法是一

个同步方法,JVM 通过该 ACC_SYNCHRONIZED访问标志来辨别一个方法是否声明为同步方法,从而执行相应的同步调用。

41、Java中synchronized 和 ReentrantLock有什么不同?(高频)

相似点:

​ 这两种同步方式有很多相似之处,它们都是加锁方式同步,而且都是阻塞式的同步,也就是说当如果一个线程获得了对象锁,进入了同步块,其他访问

该同步块的线程都必须阻塞在同步块外面等待,而进行线程阻塞和唤醒的代价是比较高的。

区别:

这两种方式最大区别就是对于Synchronized来说,它是java语言的关键字,是原生语法层面的互斥,需要jvm实现。而ReentrantLock它是JDK 1.5之后

提供的API层面的互斥锁,需要lock()和unlock()方法配合try/finally语句块来完成。

相比Synchronized,ReentrantLock类提供了一些高级功能,主要有以下2项:

​ ① 等待可中断,持有锁的线程长期不释放的时候,正在等待的线程可以选择放弃等待,这相当于Synchronized来说可以避免出现死锁的情况。

​ ② Synchronized锁非公平锁,ReentrantLock默认的构造函数是创建的非公平锁,可以通过参数true设为公平锁,但公平锁表现的性能不是很好。

(公平锁,多个线程等待同一个锁时,必须按照申请锁的时间顺序获得锁。)

24、简述一下你对线程池的理解?(高频)

合理利用线程池能够带来三个好处:

第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。

第二:提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。

第三:提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程

池可以进行统一的分配,调优和监控。

42、Java中活锁和死锁有什么区别?

活锁:一个线程在执行的时候影响到了另外一个线程的执行,而另外一个线程的执行同时影响到了该线程的执行那么就有可能发生活锁。同死锁一样,发生

活锁的线程无法继续执行。然而线程并没有阻塞——他们在忙于响应对方无法恢复工作。这就相当于两个在走廊相遇的人:甲向他自己的左边靠想让乙过

去,而乙向他的右边靠想让甲过去。可见他们阻塞了对方。甲向他的右边靠,而乙向他的左边靠,他们还是阻塞了对方。

死锁:线程死锁是指由于两个或者多个线程互相持有对方所需要的资源,导致这些线程处于等待状态,无法前往执行。

死锁演示:

Thread的子类

public class DeadThread extends Thread {

    // 定义成员变量,来切换线程去执行不同步代码块的执行
    private boolean flag ;
    public DeadThread(boolean flag) {
        this.flag = flag ;
    }

    @Override
    public void run() {

        if(flag) {

            synchronized (MyLock.R1) {   
                System.out.println(Thread.currentThread().getName() + "---获取到了R1锁,申请R2锁....");
                synchronized (MyLock.R2) {
                    System.out.println(Thread.currentThread().getName() + "---获取到了R1锁,获取到了R2锁....");
                }
            }

        }else {

            synchronized (MyLock.R2) {	
                System.out.println(Thread.currentThread().getName() + "---获取到了R2锁,申请R1锁....");
                synchronized (MyLock.R1) {
                    System.out.println(Thread.currentThread().getName() + "---获取到了R2锁,获取到了R1锁....");
                }
            }
            
        }

    }

}

锁接口

public interface MyLock {

    // 定义锁对象
    public static  final Object R1 = new Object() ;
    public static  final Object R2 = new Object() ;

}

测试类

public class DeadThreadDemo1 {

    public static void main(String[] args) {

        // 创建线程对象
        DeadThread deadThread1 = new DeadThread(true) ;
        DeadThread deadThread2 = new DeadThread(false) ;

        // 启动两个线程
        deadThread1.start();
        deadThread2.start();

    }

}

控制台输出结果

Thread-0---获取到了R1锁,申请R2锁....
Thread-1---获取到了R2锁,申请R1锁....

此时程序并没有结束,这种现象就是死锁现象…线程Thread-0持有R1的锁等待获取R2锁,线程Thread-1持有R2的锁等待获取R1的锁。

43、如何进行死锁诊断?

当程序出现了死锁现象,我们应该如何进行诊断呢?使用jdk自带的工具: jstack

对上面的程序使用jstack进行死锁诊断

C:\Users\Administrator>jps
7408
8144 DeadThreadDemo1
3620
9108 Launcher
9180 Jps

C:\Users\Administrator>jstack -l 8144
2019-10-19 14:52:01
Full thread dump Java HotSpot(TM) 64-Bit Server VM (11+28 mixed mode):
...
"Thread-0":
        at com.itheima.javase.security.demo10.DeadThread.run(DeadThread.java:19)
        - waiting to lock <0x000000008c591218> (a java.lang.Object)   // 等待锁0x000000008c591218
        - locked <0x000000008c591208> (a java.lang.Object)			  // 已经拥有的锁0x000000008c591208
"Thread-1":
        at com.itheima.javase.security.demo10.DeadThread.run(DeadThread.java:29)
        - waiting to lock <0x000000008c591208> (a java.lang.Object)   // 等待锁0x000000008c591208
        - locked <0x000000008c591218> (a java.lang.Object)			  // 已经拥有的锁0x000000008c591218

Found 1 deadlock.		// 发现了一个1个死锁


C:\Users\Administrator>
44、Linux环境下如何查找哪个线程使用CPU最长?

步骤如下:

1、通过ps -ef | grep java查询指定的进程id

2、通过top -H -p pid查看指定进程中的线程信息

3、将线程的id的十进制数据转换成十六进制:printf “%x” tid

4、通过jstack -l pid查询进行中的线程nid(native thread id)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5Ly2dI3Q-1684156952144)(images/image-20220205115846018.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GiXXRtIW-1684156952145)(images/image-20220205122500032.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9UaI7qDh-1684156952145)(images/image-20220205122541752.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qJEa6kTM-1684156952145)(images/image-20220205122648546.png)]
45、ThreadLocal 是什么?有哪些使用场景?(高频)

ThreadLocal是一个线程工具类,可以在一个线程内共享数据,在我们的项目中使用ThreadLocal存储的是解析token以后的用户数据。

底层实现是同一个Map进行数据的存储,Map的键是当前线程对象,值就是要共享的数据。

46、线程池中分配线程的依据?(高频)

① 高并发、任务执行时间短的业务,线程池程数可以设置为CPU核数+1,减少线程上下文的切换

② 并发不高、任务执行时间长的业务要区分开看

  • 假如是业务时间长集中在IO操作上,也就是IO密集型的任务,因为IO操作并不占用CPU,所以不要让所有的CPU闲下来,可以加大线程池中的线程数

    目,让CPU处理更多的业务

  • 假如是业务时间长集中在计算操作上,也就是计算密集型任务,这个就没办法了,和(1)一样吧,线程池中的线程数设置得少一些,减少线程上下文

    的切换

③ 并发高、业务执行时间长,解决这种类型任务的关键不在于线程池而在于整体架构的设计,看看这些业务里面某些数据是否能做缓存是第一步,增加服

务器是第二步,至于线程池的设置,设置参考(2)。最后,业务执行时间长的问题,也可能需要分析一下,看看能不能使用中间件对任务进行拆分和解

耦。

47、如果你提交任务时,核心线程池已经满了,这时会发生什么?(高频)

① 无界队列

​ 如果使用的是无界队列LinkedBlockingQueue,也就是无界队列的话,没关系,继续添加任务到阻塞队列中等待执行,因为LinkedBlockingQueue可

以近乎认为是一个无穷大的队列,可以无限存放任务

② 有界队列

​ 如果使用的是有界队列比如ArrayBlockingQueue,任务首先会被添加到ArrayBlockingQueue中,ArrayBlockingQueue满了,会根

maximumPoolSize的值增加线程数量,如果增加了线程数量还是处理不过来,ArrayBlockingQueue继续满,那么则会使用拒绝策略

RejectedExecutionHandler处理满了的任务,默认是AbortPolicy

48、创建线程池有哪几种方式?(高频)

① 通过Executor框架的工具类Executors来实现我们可以创建三种类型的:

  • FixedThreadPool: 该方法返回一个固定线程数量的线程池。该线程池中的线程数量始终不变。当有一个新的任务提交时,

    线程池中若有空闲线程,则立即执行。若没有,则新的任务会被暂存在一个任务队列中,待有线程空闲时,便处理在任务

    队列中的任务。

  • SingleThreadExecutor: 方法返回一个只有一个线程的线程池。若多余一个任务被提交到该线程池,任务会被保存在一个

    任务队列中,待线程空闲,按先入先出的顺序执行队列中的任务。

  • CachedThreadPool: 该方法返回一个可根据实际情况调整线程数量的线程池。线程池的线程数量不确定,但若有空闲线

    程可以复用,则会优先使用可复用的线程。若所有线程均在工作,又有新的任务提交,则会创建新的线程处理任务。所有

    线程在当前任务执行完毕后,将返回线程池进行复用。

② 《阿里巴巴Java开发手册》中强制线程池不允许使用 Executors 去创建,而是通过ThreadPoolExecutor 的方式:

ThreadPoolExecutor最完整的构造方法:

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler)

参数说明

corePoolSize:   核心线程的最大值,不能小于0
maximumPoolSize:最大线程数,不能小于等于0,maximumPoolSize >= corePoolSize
keepAliveTime:  空闲线程最大存活时间,不能小于0
unit:           时间单位
workQueue:      任务队列,不能为null
threadFactory:  创建线程工厂,不能为null      
handler:        任务的拒绝策略,不能为null    

任务的拒绝策略种类:

ThreadPoolExecutor.AbortPolicy: 		    丢弃任务并抛出RejectedExecutionException异常。是默认的策略。
ThreadPoolExecutor.DiscardPolicy: 		   丢弃任务,但是不抛出异常 这是不推荐的做法。
ThreadPoolExecutor.DiscardOldestPolicy:    抛弃队列中等待最久的任务 然后把当前任务加入队列中。
ThreadPoolExecutor.CallerRunsPolicy:        调用任务的run()方法绕过线程池直接执行。
49、如何控制某个方法允许并发访问线程的数量?

Semaphore两个重要的方法就是semaphore.acquire() 请求一个信号量,这时候的信号量个数-1(一旦没有可使用的信号量,也即信号量个数变为负数

时,再次请求的时候就会阻塞,直到其他线程释放了信号量)semaphore.release()释放一个信号量,此时信号量个数+1

线程任务类:

public class CarThreadRunnable implements Runnable {

    // 创建一个Semaphore对象,限制只允许2个线程获取到许可证
    private Semaphore semaphore = new Semaphore(2) ;

    @Override
    public void run() {                         // 这个run只允许2个线程同时执行

        try {

            // 获取许可证
            semaphore.acquire();
            
            System.out.println(Thread.currentThread().getName() + "----->>正在经过十字路口");

            // 模拟车辆经过十字路口所需要的时间
            Random random = new Random();
            int nextInt = random.nextInt(7);
            TimeUnit.SECONDS.sleep(nextInt);

            System.out.println(Thread.currentThread().getName() + "----->>驶出十字路口");

            // 释放许可证
            semaphore.release();

        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }

}

测试类:

public class SemaphoreDemo01 {

    public static void main(String[] args) {

        // 创建线程任务类对象
        CarThreadRunnable carThreadRunnable = new CarThreadRunnable() ;

        // 创建5个线程对象,并启动。
        for(int x = 0 ; x < 5 ; x++) {
            new Thread(carThreadRunnable).start();
        }

    }

}
50、线程池中 submit()和 execute()方法有什么区别?(高频)

1、接收的参数不一样

2、submit有返回值,而execute没有

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9fchuGHC-1684156951433)(images/image-20220502150530184.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RlYtJQiw-1684156951434)(images/image-20220502150632642.png)]

51、什么是Java内存模型?

JMM(Java Memory Model)Java内存模型,是java虚拟机规范中所定义的一种内存模型。

Java内存模型(Java Memory Model)描述了Java程序中各种变量(线程共享变量)的访问规则,以及在JVM中将变量存储到内存和从内存中读取变量这样的

底层细节。

特点:

  1. 所有的共享变量都存储于主内存(计算机的RAM)这里所说的变量指的是实例变量和类变量。不包含局部变量,因为局部变量是线程私有的,因此不存在

    竞争问题。

  2. 每一个线程还存在自己的工作内存,线程的工作内存,保留了被线程使用的变量的工作副本。

  3. 线程对变量的所有的操作(读,写)都必须在工作内存中完成,而不能直接读写主内存中的变量,不同线程之间也不能直接访问对方工作内存中的变量,

    线程间变量的值的传递需要通过主内存完成。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nKd3G96c-1684156952146)(images/image-20220204222010008.png)]
52、volatile 是什么?可以保证有序性吗?

一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义:

① 保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的,volatile关键字会强制将修改的

值立即写入主存。

② 禁止进行指令重排序,可以保证有序性。

指令重排:计算机在执行程序时,为了提高性能,编译器和处理器常常会对指令重排。处理器在进行重排序时,必须要考虑指令之间的数据依赖性。如下代

码:

public void mySort() {
	int x = 11;
	int y = 12;
	x = x + 5;
	y = x * x;
}

按照正常的顺序进行执行,那么执行顺序应该是:1 2 3 4 。但是如果发生了指令重排,那么此时的执行顺序可能是:① 1 3 2 4 ② 2 1 3 4 但是肯定不

会出现:4 3 2 1这种顺序,因为处理器在进行重排时候,必须考虑到指令之间的数据依赖性。多线程环境里编译器和CPU指令优化根本无法识别多个线程

之间存在的数据依赖性,比如说下面的程序代码如果两个方法在两个不同的线程里面调用就可能出现问题。

private static int value;
private static volatile boolean flag;

public static void init(){
    value=8;     //语句1 
    flag=true;   //语句2  
}

public static void getValue(){
    if(flag){
        System.out.println(value);
    }
}

根据上面代码,如果程序代码运行都是按顺序的,那么getValue() 中打印的value值必定是等于8的,不过如果init()方法经过了指令重排序,那么结果就不

一定了。进行重排序后代码执行顺序可能如下。

flag=true;  //语句2  
value=8;    //语句1

如果init()方法经过了指令重排序后,这个时候两个线程分别调用 init()和getValue()方法,那么就有可能出现下图的情况,导致最终打印出来的value数据

等于0。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bdH6G9uu-1684156952146)(images/image-20220205092454233.png)]

解决方案:使用volatile修饰flag,禁止指令重排。

原理说明:添加了一个内存屏障,通过插入内存屏障禁止在内存屏障前后的指令执行重排序优化

53、什么是CAS?

CAS的全称是: Compare And Swap(比较再交换); 是现代CPU广泛支持的一种对内存中的共享数据进行操作的一种特殊指令。CAS可以将read-modify-

write转换为原子操作,这个原子操作直接由CPU保证。CAS有3个操作数:内存值V,旧的预期值A,要修改的新值B。当且仅当旧预期值A和内存值V相同

时,将内存值V修改为B并返回true,否则什么都不做,并返回false。

54、CAS有什么缺陷,如何解决?

① ABA问题

​ 并发环境下,假设初始条件是A,去修改数据时,发现是A就会执行修改。但是看到的虽然是A,中间可能发生了A变B,B又变回A的情况。此时A已经非

彼A,数据即使成功修改,也可能有问题。可以通过AtomicStampedReference「解决ABA问题」,它是一个带有标记(数据版本号)的原子引用类,通过

控制变量值的版本来保证CAS的正确性。

② 循环时间长开销

​ 自旋CAS,如果一直循环执行,一直不成功,会给CPU带来非常大的执行开销。很多时候,CAS思想体现,是有个自旋次数的,就是为了避开这个耗时

问题~

③ 只能保证一个变量的原子操作

​ CAS 保证的是对一个变量执行操作的原子性,如果对多个变量操作时,CAS 目前无法直接保证操作的原子性的。

​ 可以通过这两个方式解决这个问题:

  • 使用互斥锁来保证原子性
  • 将多个变量封装成对象,通过AtomicReference来保证原子性
55、什么是AQS?

问答:

1、AQS:翻译过来应该是抽象队列同步器 ,本质上是一个框架,通过这个框架可以实现阻塞锁的操作

2、juc包下的工具类,在实现相关功能的时候,都是以AQS为基础进行实现

3、ReentrantLock的互斥功能使用的就是AQS中独占模式进行实现的,Semaphore在实现允许多个线程同时访问某一个共享的资源的时候使用的就是

AQS中的共享模式。

AQS全名:AbstractQueuedSynchronizer翻译过来应该是抽象队列同步器,是并发容器J.U.C(java.util.concurrent)下locks包内的一个类。如果说

java.util.concurrent的基础是CAS的话,那么AQS就是整个Java并发包的核心了,ReentrantLock、CountDownLatch、Semaphore等都用到了它。

AQS提供了一种实现阻塞锁和一系列依赖FIFO等待队列的同步器的框架。它实现了一个FIFO(FirstIn、FisrtOut先进先出)的队列。底层实现的数据结构是

一个双向链表。如下所示:

image-20220216171842136

AQS中维护了一个volatile int state(共享资源)和一个CLH队列。我们可以通过修改state字段表示的同步状态来实现多线程的** 独占模式共享模式**(加

锁过程)。具体过程如下所示:

image-20220216173301640image-20220216173331463

AS 保证的是对一个变量执行操作的原子性,如果对多个变量操作时,CAS 目前无法直接保证操作的原子性的。

​ 可以通过这两个方式解决这个问题:

  • 使用互斥锁来保证原子性
  • 将多个变量封装成对象,通过AtomicReference来保证原子性
55、什么是AQS?

问答:

1、AQS:翻译过来应该是抽象队列同步器 ,本质上是一个框架,通过这个框架可以实现阻塞锁的操作

2、juc包下的工具类,在实现相关功能的时候,都是以AQS为基础进行实现

3、ReentrantLock的互斥功能使用的就是AQS中独占模式进行实现的,Semaphore在实现允许多个线程同时访问某一个共享的资源的时候使用的就是

AQS中的共享模式。

AQS全名:AbstractQueuedSynchronizer翻译过来应该是抽象队列同步器,是并发容器J.U.C(java.util.concurrent)下locks包内的一个类。如果说

java.util.concurrent的基础是CAS的话,那么AQS就是整个Java并发包的核心了,ReentrantLock、CountDownLatch、Semaphore等都用到了它。

AQS提供了一种实现阻塞锁和一系列依赖FIFO等待队列的同步器的框架。它实现了一个FIFO(FirstIn、FisrtOut先进先出)的队列。底层实现的数据结构是

一个双向链表。如下所示:

image-20220216171842136

AQS中维护了一个volatile int state(共享资源)和一个CLH队列。我们可以通过修改state字段表示的同步状态来实现多线程的** 独占模式共享模式**(加

锁过程)。具体过程如下所示:

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值