面试题-Java

2019-12-13  本文已影响0人  二十二_0cbe

0. &和&&的区别?

  1. &和&&都可以用作[逻辑与]的运算符,表示逻辑与(and),当运算符两边的表达式的结果都为true时,整个运算结果才为true,否则,只要有一方为false,则结果为false。
  2. &&还具有[短路]的功能,即如果第一个表达式为false,则不再计算第二个表达式,例如,对于if(str != null && !str.equals(“”))表达式,当str为null时,后面的表达式不会执行,所以不会出现NullPointerException如果将&&改为&,则会抛出NullPointerException异常。If(x==33 & ++y>0) y会增长,If(x==33 && ++y>0)不会增长
  3. &还可以用作[位运算符],当&操作符两边的表达式不是boolean类型时,&表示按位与操作,我们通常使用0x0f来与一个整数进行&运算,来获取该整数的最低4个bit位,例如,0x31 & 0x0f的结果为0x01。

0.1 equals() 和 "==" 有什么区别?

equals()方法用来比较的是两个对象的内容是否相等,由于所有的类都是继承自java.lang.Object类的,所以适用于所有对象,如果没有对该方法进行覆盖的话,调用的仍然是Object类中的方法,而Object中的equals方法返回的却是==的判断;

"==" 比较的是变量(栈)内存中存放的对象的(堆)内存地址,用来判断两个对象的地址是否相同,即是否是指相同一个对象。

1. object类里有哪些方法?

1.所有方法:

1. getClass()
2. hashCode()
3. equals()
hashCode()和equals()要同时重写原因:如果不重写equals,那么比较的将是对象的引用是否指向同一块内存地址,重写之后目的是为了比较两个对象的value值是否相等。
4. toString()
5. clone()
6. wait()...
7. notify()
8. notifyAll()
9. finalize():实例被垃圾回收器回收的时触发的操作

2. 各个方法作用:

protected Object clone() 创建并返回此对象的一个副本。
boolean equals(Object obj) 指示某个其他对象是否与此对象“相等”。
protected void finalize() 当垃圾回收器确定不存在对该对象的更多引用时,由对象的垃圾回收器调用此方法。
Class<? extendsObject> getClass() 返回一个对象的运行时类。
int hashCode() 返回该对象的哈希码值。
void notify() 唤醒在此对象监视器上等待的单个线程。
void notifyAll() 唤醒在此对象监视器上等待的所有线程。
String toString() 返回该对象的字符串表示。
void wait() 导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法。
void wait(long timeout) 导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者超过指定的时间量。
void wait(long timeout, int nanos) 导致当前的线程等待,直到其他线程调用此对象的 notify()

2. hashcode是什么?

Java中的hashCode方法就是根据一定的规则将与对象相关的信息(比如对象的存储地址,对象的字段等)映射成一个数值,这个数值称作为散列值。即在散列集合包括HashSet、HashMap以及HashTable里,对每一个存储的桶元素都有一个唯一的"块编号",即它在集合里面的存储地址;当你调用contains方法的时候,它会根据hashcode找到块的存储地址从而定位到该桶元素。

3. HashMap1.7和1.8的区别

1)JDK1.7用的是头插法,而JDK1.8及之后使用的都是尾插法,那么他们为什么要这样做呢?因为JDK1.7是用单链表进行的纵向延伸,当采用头插法时会容易出现逆序且环形链表死循环问题。但是在JDK1.8之后是因为加入了红黑树使用尾插法,能够避免出现逆序且链表死循环的问题。

2)扩容后数据存储位置的计算方式也不一样:1. 在JDK1.7的时候是直接用hash值和需要扩容的二进制数进行&(这里就是为什么扩容的时候为啥一定必须是2的多少次幂的原因所在,因为如果只有2的n次幂的情况时最后一位二进制数才一定是1,这样能最大程度减少hash碰撞)(hash值 & length-1),而在JDK1.8的时候直接用了JDK1.7的时候计算的规律,也就是扩容前的原始位置+扩容的大小值=JDK1.8的计算方式,而不再是JDK1.7的那种异或的方法。但是这种方式就相当于只需要判断Hash值的新增参与运算的位是0还是1就直接迅速计算出了扩容后的储存方式。

3)JDK1.7的时候使用的是数组+ 单链表的数据结构。但是在JDK1.8及之后时,使用的是数组+链表+红黑树的数据结构(当链表的深度达到8的时候,也就是默认阈值,就会自动扩容把链表转成红黑树的数据结构来把时间复杂度从O(n)变成O(logN)提高了效率)

4)JDK1.7的时候是先进行扩容后进行插入,而在JDK1.8的时候则是先插入后进行扩容。

4. 哈希表如何解决Hash冲突?

5. synchronized 和 Reentrantlock

  1. 当synchronized作用于普通方法是,锁对象是this;
  2. 当synchronized作用于静态方法是,锁对象是当前类的Class对象;
  3. 当synchronized作用于代码块时,锁对象是synchronized(obj)中的这个obj。

6. 怎么用无锁方案实现线程安全?

ThreadLocal:如果一个变量要被线程独享,可以通过java.lang.ThreadLocal类实现线程本地存储的功能。既然是本地存储的,那么就只有当前线程可以访问,自然是线程安全的。
1、Thread里面有个属性是一个类似于HashMap一样的东西,只是它的名字叫ThreadLocalMap,这个属性是default类型的,因此同一个package下面所有的类都可以引用到,因为是Thread的局部变量,所以每个线程都有一个自己单独的Map,相互之间是不冲突的,所以即使将ThreadLocal定义为static线程之间也不会冲突。

2、ThreadLocal和Thread是在同一个package下面,可以引用到这个类,可以对他做操作,此时ThreadLocal每定义一个,用this作为Key,你传入的值作为value,而this就是你定义的ThreadLocal,所以不同的ThreadLocal变量,都使用set,相互之间的数据不会冲突,因为他们的Key是不同的,当然同一个ThreadLocal做两次set操作后,会以最后一次为准。

3、综上所述,在线程之间并行,ThreadLocal可以像局部变量一样使用,且线程安全,且不同的ThreadLocal变量之间的数据毫无冲突。

ThreadLocal为什么会产生溢出?原博地址

ThreadLocal里面使用了一个存在弱引用的map, map的类型是ThreadLocal.ThreadLocalMap. Map中的key为一个threadlocal实例。这个Map的确使用了弱引用,不过弱引用只是针对key。每个key都弱引用指向threadlocal。 当把threadlocal实例置为null以后,没有任何强引用指向threadlocal实例,所以threadlocal将会被gc回收。

但是,我们的value却不能回收,而这块value永远不会被访问到了,所以存在着内存泄露。因为存在一条从current thread连接过来的强引用。只有当前thread结束以后,current thread就不会存在栈中,强引用断开,Current Thread、Map value将全部被GC回收。最好的做法是将调用threadlocal的remove方法,这也是等会后边要说的。

其实,ThreadLocalMap的设计中已经考虑到这种情况,也加上了一些防护措施:在ThreadLocal的get(),set(),remove()的时候都会清除线程ThreadLocalMap里所有key为null的value

7. 线程池的核心参数、在线程池提交了以后的执行过程

public ThreadPoolExecutor(
          int corePoolSize,  //核心池的大小。
          int maximumPoolSize, //池中允许的最大线程数,这个参数表示了线程池中最多能创建的线程数量
          long keepAliveTime,  //当线程数大于corePoolSize时,终止前多余的空闲线程等待新任务的最长时间
          TimeUnit unit, //keepAliveTime时间单位
          BlockingQueue<Runnable> workQueue,    //存储还没来得及执行的任务
          ThreadFactory threadFactory,  //执行程序创建新线程时使用的工厂
          RejectedExecutionHandler handler   //由于超出线程范围和队列容量而使执行被阻塞时所使用的处理程序
)  

corePoolSize与maximumPoolSize举例理解
1、池中线程数小于corePoolSize,新任务都不排队而是直接添加新线程。
2、池中线程数大于等于corePoolSize,workQueue未满,首选将新任务加入workQueue而不是添加新线程。
3、池中线程数大于等于corePoolSize,workQueue已满,但是线程数小于maximumPoolSize,添加新的线程来处理被添加的任务。
4、池中线程数大于大于corePoolSize,workQueue已满,并且线程数大于等于maximumPoolSize,新任务被拒绝,使用handler处理被拒绝的任务。

执行过程

8. 线程间的通信方式

Java中线程通信协作的最常见的两种方式:

9. 多线程实现方式

public class MyRunnable implements Runnable {
    public void run() {
        // 重写run方法
    }
}

public static void main(String[] args) {
    MyRunnable instance = new MyRunnable();
    Thread thread = new Thread(instance);
    // 通过 Thread 调用 start() 方法来启动线程。
    thread.start();
}

交替打印奇偶数:

public class PrintAlternately {
    private static class Counter {
        public int value = 1;
        public boolean odd = true;
    }
    
    private static Counter counter = new Counter();
    
    private static class PrintOdd implements Runnable {
        @Override
        public void run() {
            while (counter.value <= 100) {
                synchronized(counter) {
                    if (counter.odd) {
                        System.out.println(counter.value);
                        counter.value++;
                        counter.odd = !counter.odd;
                        //很重要,要去唤醒打印偶数的线程
                        counter.notify();
                    } else {
                        //交出锁,让打印偶数的线程执行
                        try {
                            counter.wait();
                        } catch (InterruptedException e) {}
                    }
                }
            }//while
        }
    }
    
    private static class PrintEven implements Runnable {
        @Override
        public void run() {
            while (counter.value <= 100) {
                synchronized (counter) {
                    if (counter.odd) {
                        try {
                            counter.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    } else {
                        System.out.println(counter.value);
                        counter.value++;
                        counter.odd = !counter.odd;
                        counter.notify();
                    }
                }//synchronized
            }
        }
    }
    
    public static void main(String[] args) {
        new Thread(new PrintOdd()).start();
        new Thread(new PrintEven()).start();
    }
}
// 原文链接:https://blog.csdn.net/guohengcook/article/details/81638024

10. JVM划分区域

线程共享:方法区,堆
非线程共享:虚拟机栈,本地方法栈,程序计数器
静态变量方法区内,线程间共享,不安全。
局部变量内,线程间不共享,安全。

11.Java GC、新生代、老年代

堆被划分成两个不同的区域:新生代 ( Young )、老年代 ( Old )。新生代 ( Young ) 又被划分为三个区域:Eden、From Survivor、To Survivor。


11. 常用垃圾回收器

上一篇 下一篇

猜你喜欢

热点阅读