• java----面试题


    1、new String创建了几个对象

    String str = "a"+"b";产生几个对象?答案是3个,字符串常量区存储"a","b","ab"三个对象

    String str = "a"+new String("b");产生几个对象?答案是3个,字符串常量区存储"a","b",堆中存储new String("b")的对象。

      注释:只有使用引号包含文本的方式创建的String对象之间使用“+”连接产生的新对象才会被加入字符串池中。对于所有包含new方式新建对象(包括null)的“+”连接表达式,它所产生的新对象都不会被加入字符串池中

    2、abstract interface区别

      抽象类和接口中都可以包含静态成员变量,抽象类中的静态成员变量的访问类型可以任意,但接口中定义的变量只能是 static或者是final类型(默认,所以可以不加),并且默认即为public static final类型。抽象类中可以有普通成员变量,接口中没有普通成员变量。

      抽象类可以有构造方法,接口中不能有构造方法。

      一个类可以实现多个接口,但只能继承一个抽象类。

      有抽象方法不一定是抽象类,也可能是接口。抽象类不一定有抽象方法,可以有非抽象的普通方法。(接口中的方法没有方法体就是抽象方法)

    3、Java的反射机制

      在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为Java语言的反射机制。

      反射的核心是JVM在运行时才动态加载类或调用方法/访问属性,它不需要事先知道运行对象是谁。

    4、super()和this()能不能同时使用

      不能同时使用,this和super不能同时出现在一个构造函数里面,因为this必然会调用其它的构造函数,其它的构造函数必然也会有super语句的存在,所以在同一个构造函数里面有相同的语句,就失去了语句的意义,编译器也不会通过。

    5、hashcode,equals,Object的这两个方法默认返回什么?描述了一下为什么重写equals方法必须重写hashcode方法

      默认的hashCode方法会利用对象的地址来计算hashcode值,不同对象的hashcode值是不一样的。

      Object类中的equals方法与“==”是等价的,也就是说判断对象的地址是否相等。Object类中的equals方法进行的是基于内存地址的比较。

      一般对于存放到Set集合或者Map中键值对的元素,需要按需要重写hashCode与equals方法,以保证唯一性。(基本上所有可能用来做为map的键的类都应该重写hashcode)

    6、map、list、set的区别 

    List

    1.可以允许重复的对象。

    2.可以插入多个null元素。

    3.是一个有序容器,保持了每个元素的插入顺序,输出的顺序就是插入的顺序。

    4.常用的实现类有 ArrayList、LinkedList 和 Vector。ArrayList 最为流行,它提供了使用索引的随意访问,而 LinkedList 则对于经常需要从 List中添加或删除元素的场合更为合适。

    List都是可以通过index来获取元素。(所以List不能使用链表加数组作为数据结构,否则就失去了index的意义)

    Set

    1.不允许重复对象

    2.无序容器,你无法保证每个元素的存储顺序,TreeSet通过 Comparator 或者 Comparable 维护了一个排序顺序。

    3.只允许一个 null 元素

    4.Set 接口最流行的几个实现类是 HashSet、LinkedHashSet 以及 TreeSet。最流行的是基于 HashMap 实现的 HashSet;TreeSet 还实现了 SortedSet 接口,因此 TreeSet 是一个根据其 compare() 和 compareTo() 的定义进行排序的有序容器。

    Map

    1.Map不是collection的子接口或者实现类。Map是一个接口。

    2.Map 的 每个 Entry 都持有两个对象,也就是一个键一个值,Map 可能会持有相同的值对象但键对象必须是唯一的。

    3.TreeMap 也通过 Comparator 或者 Comparable 维护了一个排序顺序。

    4.Map 里你可以拥有随意个 null 值但最多只能有一个 null 键。

    5.Map 接口最流行的几个实现类是 HashMap、LinkedHashMap、Hashtable 和 TreeMap。(HashMap、TreeMap最常用)

    7、有没有有序的set?

      有,LinkedHashSet和TreeSet

    7、Array和ArrayList的区别,什么时候用ArrayList和什么时候用Array

      Array只能存法相同数据类型的数据,ArrayList只能可以存放不同数据类型的数据。

      Array可以包含基本类型和对象类型,ArrayList只能包含对象类型。(ArrayList添加基本数据类型时,基本数据类型会被包装成对象类型)

      Array长度不可以改变,占空间小,ArrayList长度可以改变(动态的扩充数组),所以如果我们保存的数据长度已知,建议使用Array;

      ArrayList比Array有跟多的Api可以操作(Sort indexOf等)

    8、Set如何保证不重复?

      HashSet的add()中调用了HashMap的put(),将一个key-value对放入HashMap中时,首先根据key的hashCode()返回值决定该Entry的存储位置,如果两个key的hash值相同,那么它们的存储位置相同。如果这个两个key的equals比较返回true。那么新添加的Entry的value会覆盖原来的Entry的value,key不会覆盖。因此,如果向HashSet中添加一个已经存在的元素,新添加的集合元素不会覆盖原来已有的集合元素。

    9、对Java io的理解

      其实意味着:数据不停地搬入搬出缓冲区而已(使用了缓冲区)。

    10、nio与bio的了解以及说一下区别

    相关博客:https://www.cnblogs.com/blackjoyful/p/11534985.html

      BIO:同步阻塞式IO,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。

      NIO:同步非阻塞式IO,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。

    11、Java并发的理解

      Java是一种多线程编程语言,我们可以使用Java来开发多线程程序。多线程程序包含两个或多个可同时运行的部分,每个部分可以同时处理不同的任务,从而能更好地利用可用资源,特别是当您的计算机有多个CPU时。多线程使您能够写入多个活动,可以在同一程序中同时进行操作处理。 

    12、wait和sleep的区别

      wait和notify方法定义在Object类中,因此会被所有的类所继承。这些方法都是final的,即它们都是不能被重写的,不能通过子类覆写去改变它们的行为。而sleep方法是在Thread类中是由native修饰的,本地方法。

      当线程调用了wait()方法时,它会释放掉对象的锁。另一个会导致线程暂停的方法:Thread.sleep(),它会导致线程睡眠指定的毫秒数,但线程在睡眠的过程中是不会释放掉对象的锁的。

      因为wait方法会释放锁,所以调用该方法时,当前的线程必须拥有当前对象的monitor,也即lock,就是锁。要确保调用wait()方法的时候拥有锁,即wait()方法的调用必须放在synchronized方法或synchronized块中。

    13、为什么HashMap的初始容量会是16,为什么是2倍扩容,加载因子是0.75?

    参考:https://blog.csdn.net/apeopl/article/details/88935422

    if ((p = tab[i = (n - 1) & hash]) == null)
    

      原因在于HashMap源码中,这个算法只有当n时2的幂次方的时候,这样与添加元素的hash值进行位运算时,能够充分的散列,使得添加的元素均匀分布在HashMap的每个位置上,减少hash碰撞

      扩容2倍和加载因子当然也是为了减少Hash碰撞

    14、解决哈希冲突的三种方法

    一、拉链法

      HashMap,HashSet其实都是采用的拉链法来解决哈希冲突的,就是在每个位桶实现的时候,我们采用链表(jdk1.8之后采用链表+红黑树)的数据结构来去存取发生哈希冲突的输入域的关键字(也就是被哈希函数映射到同一个位桶上的关键字)

    二、开放地址法

      当发生地址冲突时,按照某种方法继续探测哈希表中的其他存储单元,直到找到空位置为止.

    15、多线程实现方法

      继承Thread类创建线程类,重写run方法,run方法就是代表线程需要完成的任务,调用线程对象的start()来启动该线程,线程类已经继承了Thread类,所以不能再继承其他父类。

      实现Runnable接口创建线程类,定义Runnable实现类,重写run方法

      实现Callable接口,重写call()方法,call()作为线程的执行体,具有返回值

      线程池,使用线程池产生线程对象java.util.concurrent.ExecutorService、java.util.concurrent.Executors

    16、Synchronized和lock区别

    synchronized的原理:https://www.jianshu.com/p/7b0d6264fe10

    wait/notify:https://www.cnblogs.com/shen-qian/p/11265631.html

    相关博客:https://blog.csdn.net/luzhensmart/article/details/81568620

      Lock提供了synchronized关键字所不具备的主要特性有:

        尝试非阻塞地获取锁boolean tryLock():当前线程尝试获取锁,如果这一时刻没有被其他线程获取到,则成功获取并持有锁

        能被中断地获取锁void lockInterruptibly():当获取到锁的线程被中断时,中断异常抛出同时会释放锁

        超时获取锁boolean trylock(long time, TimeUnit unit):在指定截止时间之前获取锁,如果在截止时间仍旧无法获取锁,则返回

      synchronized是JVM提供的加锁,悲观锁;lock是Java语言实现的,而且是乐观锁。

      ReentrantLock是基于AQS实现的,由于AQS是基于FIFO队列的实现

    package com.cn.test.thread.lock;
    
    import java.util.concurrent.TimeUnit;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    public class LockTest {
        private Lock lock = new ReentrantLock();
        public void tryLockParamTest(Thread thread) throws InterruptedException {
            if(lock.tryLock(3000, TimeUnit.MILLISECONDS)) { //尝试获取锁 获取不到锁,就等3秒,如果3秒后还是获取不到就返回false  
                try {
                    System.out.println("线程"+thread.getName() + "获取当前锁"); //打印当前锁的名称
                    Thread.sleep(4000);//为看出执行效果,是线程此处休眠2秒
                } catch (Exception e) {
                    System.out.println("线程"+thread.getName() + "发生了异常释放锁");
                }finally {
                    System.out.println("线程"+thread.getName() + "执行完毕释放锁");
                    lock.unlock(); //释放锁
                }
            }else{
                System.out.println("我是线程"+Thread.currentThread().getName()+"当前锁被别人占用,等待3s后仍无法获取,放弃");
            }
        }
        public static void main(String[] args) {
            LockTest lockTest = new LockTest();
            Thread thread1 = new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        lockTest.tryLockParamTest(Thread.currentThread());
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
            }, "thread1");
            //声明一个线程 “线程二”
            Thread thread2 = new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        lockTest.tryLockParamTest(Thread.currentThread());
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
            }, "thread2");
            // 启动2个线程
            thread2.start();
            thread1.start();
        }
    }

    17、Java中都有什么锁

      重量级锁、显式锁、并发容器、并发同步器、CAS、volatile、AQS等

    18、可重入锁的设计思路是什么

      在获取锁的时候,如果当前线程之前已经获取到了锁,就会把state加1,在释放锁的时候会先减1

      目的:保证了同一个锁可以被同一个线程获取多次,而不会出现死锁的情况。这就是ReentrantLock的可重入性。

    19、CAS如何实现

      compare and swap

      CAS的思想很简单:三个参数,一个当前内存值V、旧的预期值A、即将更新的值B,当且仅当预期值A和内存值V相同时,将内存值修改为B并返回true,否则什么都不做,并返回false。
    AtomicInteger使用Unsafe来实现cas机制,unsafe是CAS的核心类,由于Java方法无法直接访问底层系统,需要通过本地(native)方法来访问,Unsafe相当于一个后门,基于该类可以直接操作特定内存的数据。

      CAS存在ABA问题,针对这种情况,java并发包中提供了一个带有标记的原子引用类AtomicStampedReference,它可以通过控制变量值的版本来保证CAS的正确性。 

    20、CAS并发问题

      CAS无锁自旋和同步锁线程切换使用场景对比

      1、对于资源竞争较少的情况,使用同步锁进行线程阻塞和唤醒切换以及用户态内核态间的切换操作额外浪费消耗CPU资源;而CAS基于硬件实现,不需要进入内核,不需要切换线程,操作自旋几率较少,因此可以获得更高的性能。
      2、对于资源竞争严重的情况,CAS自旋的概率会比较大,从而浪费更多的CPU资源,效率低于锁。CAS在判断两次读取的值不一样的时候会放弃操作,但为了保证结果正确,通常都会继续尝试循环再次发起CAS操作,如Jdk1.6版本的AtomicInteger类的getAndIncrement()方法,就是利用了自旋实现多次尝试:

      如果compareAndSet(current, next)方法成功执行,则直接返回;如果线程竞争激烈,导致compareAndSet(current, next)方法一直不能成功执行,则会一直循环等待,直到耗尽cpu分配给该线程的时间片,从而大幅降低效率。
      总之:
      1、使用CAS在线程冲突严重时,因为自旋会大幅降低程序性能;CAS只适合于线程冲突较少的情况使用。
      2、线程冲突严重的情况下,同步锁能实现线程堵塞和唤醒切换,不会出现自旋,避免了上述的情况,从而让性能远高于CAS。

    21、线程池原理

      首先线程是cpu执行的最小单位。如果我们需要异步处理额外的任务。就需要开辟一个线程来执行该任务,如果任务非常多,每次都需要开辟线程来处理该任务是非常耗时耗性能的。而线程池就是线程的复用,线程完成任务之后不会被销毁,

      参考:https://blog.csdn.net/weixin_28760063/article/details/81266152

      参考:https://blog.csdn.net/java2000_wl/article/details/22097059

    Java 线程池是如何工作的

      https://blog.csdn.net/linuxprobe2017/article/details/78254281

    22、线程池的类型,详细介绍cached和fixed

    1、newCachedThreadPool

    作用:创建一个可根据需要创建新线程的线程池,但是在以前构造的线程可用时将重用它们,并在需要时使用提供的 ThreadFactory 创建新线程。

    特征: 
    (1)线程池中数量没有固定,可达到最大值(Interger. MAX_VALUE) 
    (2)线程池中的线程可进行缓存重复利用和回收(回收默认时间为1分钟) 
    (3)当线程池中,没有可用线程,会重新创建一个线程

    创建方式: Executors.newCachedThreadPool();

    2、newFixedThreadPool

    作用:创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程。在任意点,在大多数 nThreads 线程会处于处理任务的活动状态。如果在所有线程处于活动状态时提交附加任务,则在有可用线程之前,附加任务将在队列中等待。如果在关闭前的执行期间由于失败而导致任何线程终止,那么一个新线程将代替它执行后续的任务(如果需要)。在某个线程被显式地关闭之前,池中的线程将一直存在。

    特征: 
    (1)线程池中的线程处于一定的量,可以很好的控制线程的并发量 
    (2)线程可以重复被使用,在显示关闭之前,都将一直存在 
    (3)超出一定量的线程被提交时候需在队列中等待

    创建方式: 
    (1)Executors.newFixedThreadPool(int nThreads);//nThreads为线程的数量 
    (2)Executors.newFixedThreadPool(int nThreads,ThreadFactory threadFactory);//nThreads为线程的数量,threadFactory创建线程的工厂方式

    3、newSingleThreadExecutor

    作用:创建一个使用单个 worker 线程的 Executor,以无界队列方式来运行该线程。(注意,如果因为在关闭前的执行期间出现失败而终止了此单个线程,那么如果需要,一个新线程将代替它执行后续的任务)。可保证顺序地执行各个任务,并且在任意给定的时间不会有多个线程是活动的。与其他等效的 newFixedThreadPool(1) 不同,可保证无需重新配置此方法所返回的执行程序即可使用其他的线程。

    特征: 
    (1)线程池中最多执行1个线程,之后提交的线程活动将会排在队列中以此执行

    创建方式: 
    (1)Executors.newSingleThreadExecutor() ; 
    (2)Executors.newSingleThreadExecutor(ThreadFactory threadFactory);// threadFactory创建线程的工厂方式

    4、newScheduleThreadPool

    作用: 创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。

    特征: 
    (1)线程池中具有指定数量的线程,即便是空线程也将保留 
    (2)可定时或者延迟执行线程活动

    创建方式: 
    (1)Executors.newScheduledThreadPool(int corePoolSize);// corePoolSize线程的个数 
    (2)newScheduledThreadPool(int corePoolSize, ThreadFactory threadFactory);// corePoolSize线程的个数,threadFactory创建线程的工厂

    5、newSingleThreadScheduledExecutor

     

    作用: 创建一个单线程执行程序,它可安排在给定延迟后运行命令或者定期地执行。

    特征: 
    (1)线程池中最多执行1个线程,之后提交的线程活动将会排在队列中以此执行 
    (2)可定时或者延迟执行线程活动

    创建方式: 
    (1)Executors.newSingleThreadScheduledExecutor() ; 
    (2)Executors.newSingleThreadScheduledExecutor(ThreadFactory threadFactory) ;//threadFactory创建线程的工厂

    23、corePoolSize参数的意义

    核心线程数

      核心线程会一直存活,即使没有任务需要执行

      当线程数小于核心线程数时,即使有线程空闲,线程池也会优先创建新线程处理

      设置allowCoreThreadTimeout=true(默认false)时,核心线程会超时关闭

    24、Java并发包里面的CountdownLatch怎么使用

      这个类是一个同步计数器,主要用于线程间的控制,当CountDownLatch的count计数>0时,await()会造成阻塞,直到count变为0,await()结束阻塞,使用countDown()会让count减1。CountDownLatch的构造函数可以设置count值,当count=1时,它的作用类似于wait()和notify()的作用。如果我想让其他线程执行完指定程序,其他所有程序都执行结束后我再执行,这时可以用CountDownLatch,但计数无法被重置,如果需要重置计数,请考虑使用 CyclicBarrier 。

    25、volatile和synchronized区别

      volatile是变量修饰符,其修饰的变量具有可见性,Java的做法是将该变量的操作放在寄存器或者CPU缓存上进行,之后才会同步到主存,使用volatile修饰符的变量是直接读写主存,volatile不保证原子性(https://blog.csdn.net/hellokandy/article/details/87912086),同时volatile禁止指令重排

      synchronized作用于一段代码或者方法,保证可见性,又保证原子性,可见性是synchronized或者Lock能保证通一个时刻只有一个线程获取锁然后执行不同代码,并且在释放锁之前会对变量的修改刷新到主存中去,原子性是指要么不执行,要执行就执行到底

    26、线程池使用时一般要考虑哪些问题

      死锁

      任何多线程程序都有死锁的风险,最简单的情形是两个线程AB,A持有锁1,请求锁2,B持有锁2,请求锁1。(这种情况在mysql的排他锁也会出现,不会数据库会直接报错提示)。线程池中还有另一种死锁:假设线程池中的所有工作线程都在执行各自任务时被阻塞,它们在等待某个任务A的执行结果。而任务A却处于队列中,由于没有空闲线程,一直无法得以执行。这样线程池的所有资源将一直阻塞下去,死锁也就产生了。

      系统资源不足

       如果线程池中的线程数目非常多,这些线程会消耗包括内存和其他系统资源在内的大量资源,从而严重影响系统性能。

      并发错误

      线程池的工作队列依靠wait()和notify()方法来使工作线程及时取得任务,但这两个方法难以使用。如果代码错误,可能会丢失通知,导致工作线程一直保持空闲的状态,无视工作队列中需要处理的任务。因为最好使用一些比较成熟的线程池。

      线程泄漏

      使用线程池的一个严重风险是线程泄漏。对于工作线程数目固定的线程池,如果工作线程在执行任务时抛出RuntimeException或Error,并且这些异常或错误没有被捕获,那么这个工作线程就异常终止,使线程池永久丢失了一个线程。(这一点太有意思)

      另一种情况是,工作线程在执行一个任务时被阻塞,如果等待用户的输入数据,但是用户一直不输入数据,导致这个线程一直被阻塞。这样的工作线程名存实亡,它实际上不执行任何任务了。如果线程池中的所有线程都处于这样的状态,那么线程池就无法加入新的任务了。

      任务过载

      当工作线程队列中有大量排队等待执行的任务时,这些任务本身可能会消耗太多的系统资源和引起资源缺乏。

  • 相关阅读:
    jQuery选择器ID、CLASS、标签获取对象值、属性、设置css样式
    shell脚本,awk取奇数行与偶数行方法。
    shell脚本,awk取中间列的方法。
    shell脚本,每5个字符之间插入"|",行末不插入“|”。
    shell脚本,tee小工具的用法。
    shell脚本,逻辑结构题练习。
    shell脚本,实现奇数行等于偶数行。
    shell脚本,编程题练习。
    shell脚本,用awk实现替换文件里面的内容。
    shell脚本,awk替换{}里面的内容
  • 原文地址:https://www.cnblogs.com/yanxiaoge/p/11668585.html
Copyright © 2020-2023  润新知