面经

  1. 用户自己写一个String类会发生什么?
    会找不到响应的执行main方法
    基于JVM的双亲委派机制,类加载器收到了加载类的请求,会把这个请求委派给他的父类加载器。而只有父类加载器自己无法完成加载请求时,子类才会自己加载。(而String类在根加载器(rt.jar)下的java.lang包中已经加载)这样用户自定义的String类的加载请求就会最终达到顶层的BootStrap ClassLoader启动类加载器,启动类加载器加载的是系统中的String对象,而用户编写的java.lang.String不会被加载

  2. 什么是双亲委派机制
    当类加载器加载一个类时,首先会向他的父类加载器进行委派,直到启动类加载器。当启动类加载器检查是否能加载这个类,能加载就结束,如果发现自己无法加载这个类时,会委派给他的子类加载器进行加载,如果直到最后一个子类还不能加载,就抛出ClassNotFound异常。

  3. 双亲委派机制作用
    避免重复加载,保证安全

  4. 如何打破双亲委派
    自定义类加载器,重写loadClass方法
    使用线程上下文类加载器

  5. sleep()和wait()的区别
    sleep属于Thread类,wait属于Object类
    sleep不会释放对象锁,而wait会释放锁
    sleep可以在任何地方使用,而wait需要在同步代码块中使用

  6. == 和equals的区别
    == 的作用是判断两个对象的地址是不是相等。即,判断两个对象是不是同一个对象(基本数据类型 == 比较的是值,引用数据类型 == 比较的是内存地址)。
    equals()的作用也是判断两个对象是否相等。但它一般有两种使用情况:
    类没有覆盖 equals() 方法。则通过 equals() 比较该类的两个对象时,等价于通过“==”比较这两个对象。
    类覆盖了 equals() 方法。一般,我们都覆盖 equals() 方法来比较两个对象的内容是否相等;若它们的内容相等,则返回 true (即,认为这两个对象相等)。

public boolean equals(Object obj) {
        return (this == obj);
    }

 public boolean equals(Object anObject) {
        if (this == anObject) {
            return true;
        }
        if (anObject instanceof String) {
            String anotherString = (String)anObject;
            int n = value.length;
            if (n == anotherString.value.length) {
                char v1[] = value;
                char v2[] = anotherString.value;
                int i = 0;
                while (n-- != 0) {
                    if (v1[i] != v2[i])
                        return false;
                    i++;
                }
                return true;
            }
        }
        return false;
    }

String 中的 equals 方法是被重写过的,因为 object 的 equals 方法是比较的对象的内存地址,而 String 的 equals 方法比较的是对象的值。
当创建 String 类型的对象时,虚拟机会在常量池中查找有没有已经存在的值和要创建的值相同的对象,如果有就把它赋给当前引用。如果没有就在常量池中重新创建一个 String 对象。

  1. 讲⼀下equals()与hashcode(),什么时候重写,为什么重写,怎么重写?
    要想保证元素的唯一性,必须同时覆盖hashCode和equals才行。
    在Obeject类中,equals()比较的是两个对象的内存地址是否相等,而hashCode()返回的是对象的内存地址。所以hashCode主要是用于查找使用的,而equals()是用于比较两个对象是否相等的。但有时候我们根据特定的需求,可能要重写这两个方法,在重写这两个方法的时候,主要注意保持一下几个特性:
    1)如果两个对象的equals()结果为true,那么这两个对象的hashCode一定相同;
    2)两个对象的hashCode()结果相同,并不能代表两个对象的equals()一定为true,只能够说明这两个对象在一个散列存储结构中。
    3)如果对象的equals()被重写,那么对象的hashCode()也要重写。
    由于元素放入集合时先比较hashcode在调用equals方法判断,所以假设我们我们重写了对象的equals(),但是不重写hashCode()方法,由于超类Object中的hashcode()方法始终返回的是一个对象的内存地址,而不同对象的这个内存地址永远是不相等的。这时候,即使我们重写了equals()方法,也不会有特定的效果的,因为不能确保两个equals()结果为true的两个对象会被散列在同一个存储区域,即obj1.equals(obj2) 的结果为true,但是不能保证 obj1.hashCode() == obj2.hashCode() 表达式的结果也为true;这种情况,就会导致数据出现不唯一,因为如果连hashCode()都不相等的话,就不会调用equals方法进行比较了,所以重写equals()就没有意义了。
    hashmap底层put方法源码
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            Node<K,V> e; K k;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }
  1. java多态如何实现,静态绑定和动态绑定
    静态绑定:编译时绑定 定义在同一个类(方法重载)
    动态绑定:运行时绑定 定义在不同的类(方法重写)
    静态绑定只用到类型信息,方法的解析根据引用变量的类型决定,而动态绑定则根据实际引用的的对象决定
    多态是通过动态绑定实现的。

  2. java安全程度
    面经

  3. java如何保证多线程安全(在线程兼容的等级下实现线程安全)
    Lock类 ReentrantLock
    Synchronized关键字
    volatile变量 弱同步机制(volatile变量的单次读/写操作可以保证原子性的,如long和double类型变量,但是并不能保证i++这种操作的原子性,因为本质上i++是读、写两次操作。在某些场景下可以代替Synchronized。但是,volatile的不能完全取代Synchronized的位置,只有在一些特殊的场景下,才能适用volatile。)

  4. lock锁和synchronized锁的区别

  • lock锁是一个类,synchronized是一个关键字
  • lock锁需要手动加锁和释放,synchronized则不需要
  • lock锁只能锁代码块,synchronized可以锁代码块和方法
  • lock锁可以通过lock.newCondition()方法精确唤醒线程,synchronized则不可以
  • lock锁一个线程阻塞后,另一个线程会尝试获取锁,synchronized则会一直等待释放锁
  1. 生产者消费者问题的lock锁实现以及synchronized实现
public class order {
    public static void main(String[] args) {
        ProjectLock projectLock=new ProjectLock();
        ProjectSynchronized projectSynchronized=new ProjectSynchronized();
        new Thread(()->{
            for (int i = 0; i < 100; i++) {
                projectLock.increment();
            }
        },"A").start();
        new Thread(()->{
            for (int i = 0; i < 100; i++) {
                projectLock.decrement();
            }
        },"B").start();
        new Thread(()->{
            for (int i = 0; i < 100; i++) {
                projectLock.decrement();
            }
        },"C").start();
    }
}
class ProjectLock{
    private int num;
    Lock lock=new ReentrantLock();
    Condition condition=lock.newCondition();
    public void increment(){
        lock.lock();
        try{
            while(num==10){
                condition.await();
            }
            num++;
            System.out.println(Thread.currentThread().getName()+": "+num);
            condition.signal();

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        finally {
            lock.unlock();
        }
    }
    public void decrement(){
        lock.lock();
        try{
            while(num==0){
                condition.await();
            }
            num--;
            System.out.println(Thread.currentThread().getName()+": "+num);
            condition.signal();

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        finally {
            lock.unlock();
        }
    }
}
class ProjectSynchronized{
    private int num;
    public synchronized void increment(){
        try {
            while(num!=0){
                this.wait();
            }
            num++;
            System.out.println(Thread.currentThread().getName()+": "+num);
            this.notifyAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    public synchronized void decrement(){
        try {
            while(num==0){
                this.wait();
            }
            num--;
            System.out.println(Thread.currentThread().getName()+": "+num);
            this.notifyAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
  1. 线程状态
    新生,运行,阻塞,等待(死等),超时等待,终止

  2. java虚方法
    所谓的虚方法就是java类在继承中,在上转型中,java类对象实际调用的方法是子类重写的方法;也就是编译器和jvm调用的不是同一个类的方法

  3. 手写单例模式

public class Single {
    private static volatile Single single;
    private static boolean key=false;
    private Single(){
        synchronized (Single.class){
            if(key==false){
                key=true;
            }
            else{
                throw new RuntimeException("不要破坏单例");
            }
        }
    }
    public static Single getInstance(){
        if(single==null){
            synchronized (Single.class){
                if(single==null)
                {
                    single=new Single();
                }
            }
        }
        return single;
    }

    public static void main(String[] args) {
        Single single=Single.getInstance();
        Single single1=Single.single;
        System.out.println(single.hashCode());
        System.out.println(single1.hashCode());
    }
}
  1. IO多路复用
    I/O多路复用,I/O就是指的我们网络I/O,多路指多个TCP连接(或多个Channel),复用指复用一个或少量线程。串起来理解就是很多个网络I/O复用一个或少量的线程来处理这些连接。
上一篇:glossary - cfx


下一篇:小键盘不能使用,小键盘无法使用,与 Num Lock 无关