面试宝典

本文深入讲解Java中的核心概念和技术,包括多态性、克隆对象、深浅拷贝、线程安全集合、并发队列、线程池及其实现策略等内容。通过实例详细解析了Java中的重要知识点。

P21 多态性分为编译时多态和运行时多态
P22 如何理解clone对象
  在实际的编程过程中,我们常常遇到这种情况:有一个对象A 在某一个时刻A中已经包含了一些有效值 此时可能会需要一个和A完全相同的新对象B 并且此后对B任何改动都不会影响到A中的值 也就是说A与B是两个完全独立的对象 但B的初始值是由A对象确定的。在java语言中 用简单的赋值语句是不能满足这种需求的。

引用的复制

Person p=new Person(23,"zhangsan");
Person p1=p;
System.out.println(p);
System.out.println(p1);

打印发现地址值是相同的 那么肯定是一个对象。

Person p=new Person(23,"zhangsan");
Person p1= (Person )p.clone();
System.out.println(p);
System.out.println(p1);

两个对象的地址是不同的。

验证深拷贝还是浅拷贝

Person p=new Person(23,"zhangsan");
Person p1= (Person )p.clone();
String result = p.getName()==p1.getName()?"clone是浅拷贝":"clone是深拷贝";
System.out.println(result);

clone是浅拷贝

如何进行深拷贝
  如果想要深拷贝一个对象 这个对象必须要实现Cloneable接口 实现clone方法 并且在clone方法内部 把该对象引用的其他对象也要clone一份 这就要求这个被引用的对象必须也要实现Cloneable接口并且实现clone方法。

public class Body implements Cloneable{
            public Head head;
            public Body(Head head){
                        this.head=head;
            }
            @Override
            protected Object clone() throws CloneNotSupportedException {
                        Body newBody =(Body) super.clone();
                        newBody.head = (Head) head.clone();
                        return newBody;
            }
}
class Head implements Cloneable{
            public Head(){

            }
            @Override
            protected Object clone() throws CloneNotSupportedException {
                        return super.clone();
            }
}

public static void main(String[] args) throws Exception {
                        Body body = new Body(new Head());
                        Body body1 = (Body) body.clone();

                        System.out.println("body==body1:"+(body==body1));
                        System.out.println("body.head==body1.head:"+(body.head==body1.head));
            }

P27 username!=null && !username.equals(“”)
P28 自反性 对称性 传递性 一致性 实现高质量的equals方法的诀窍包括:
当一个对象被当做参数传递到一个方法后 此方法可改变这个对象的属性 并可返回变化后结果 那么这里到底是值传递还是引用传递
P29 重载和重写的区别?重载的方法能否根据返回值类型进行区分
P30 为什么函数不能根据返回值类型来区分重载
P31 char型变量中能不能存储一个中文汉字 为什么
P32 抽象方法是否可同时是静态的 是否可同时是本地方法 是否可同时被synchronized修饰
P33 ==和equals的区别
P34 如果经常对字符串进行各种各样的修改 或者说 不可预见的修改 那么使用String来代表字符串的话会引起很大的内存开销
P39 四舍五入的原理是在参数上加上0.5然后进行取整
switch是否能作用在byte上 能否作用在long上 是否能作用在String上
P40 什么情况下用 “+ ”运算符进行字符串连接比调用 StringBuffer/StringBuilder对象的append方法连接字符串性能更好?

在java中无论使用何种方式进行字符串的链接 实际上都使用的是StringBuilder
+和StringBuilder从运行结果来解释 是完全等效的
但是从运行效率和资源消耗方面来看 它们将有很大区别

public static void main(String[] args) {
                        String s="";
                        Random rand = new Random();
                        for (int i = 0; i < 10; i++) {
                                    s = s+rand.nextInt(1000)+" ";
                        }
                        System.out.println(s);
            }

上面代码的问题是没执行一次循环就会创建一个StringBuilder对象
解决这个问题的方法就是在程序中直接使用StringBuilder来连接字符串

public class TestStringBuilder {
            public static void main(String[] args) {
                        String s="";
                        Random rand = new Random();

                        StringBuilder result = new StringBuilder();
                        for (int i = 0; i < 10; i++) {
                                    result.append(rand.nextInt(1000));
                                    result.append(" ");
                        }
                        System.out.println(result.toString());
            }
}

  在使用StringBuilder时要注意 尽量不要+和StringBuilder混着用 否则会创建更多的StringBuilder对象
  StringBuffer和StringBuilder的基本功能一样 只是StringBuffer是线程安全的 而StringBuilder不是线程安全的 因此StringBuilder的效率会更高

P47 1.String对象的intern()方法会得到字符串对象在常量池中对应的版本的引用(如果常量池中有一个字符串与String对象的equals结果是true)如果常量池中没有对应的字符串 则该字符串将被添加到常量池中 然后返回常量池中的引用

2.字符串的+本质上创建了StringBuilder对象进行append操作 然后将拼接后的StringBuilder对象用toString方法处理成String对象 这一点可以用javap -c StringEqualTest.class命令获得class文件对应的jvm字节码指令就可以看出来

public class StringEqualTest {
            public static void main(String[] args) {
                        String s1="Programming";
                        String s2=new String("Programming");
                        String s3="Program";
                        String s4="ming";
                        String s5="Program"+"ming";
                        String s6=s3+s4;
                        System.out.println(s1==s2);//false
                        System.out.println(s1==s5);//true
                        System.out.println(s1==s6);//false
                        System.out.println(s1==s6.intern());//true
                        System.out.println(s1==s6.intern());//true
            }
}

P49 如何取得某月的最后一天

public static void main(String[] args) {
                        Calendar.getInstance().getTimeInMillis();
                        System.currentTimeMillis();
                        Clock.systemDefaultZone().millis();

                        LocalDate today = LocalDate.now();
                        LocalDate firstday = LocalDate.of(today.getYear(), today.getMonth(), 1);
                        LocalDate lastDay = today.with(TemporalAdjusters.lastDayOfMonth());
                        System.out.println("本月的第一天:"+firstday);
                        System.out.println("本月的最后一天:"+lastDay);
            }

P50 Java8中引入了新的日期时间API 包括LocalDate LocalTime LocalDateTime Clock Instant等类 这些的类的设计都使用了不变模式 此是线程安全的设计
P50 打印昨天的当前时刻

public static void main(String[] args) {          
                        LocalDateTime today2 = LocalDateTime.now();
                        LocalDateTime yesterday = today2.minusDays(2);
                        System.out.println(yesterday);
            }

P51 Java8的日期特性?

不变性 关注点分离 清晰 实用性
java.time java.time.chrono java.time.format java.time.temporal java.time.zone

P71 String是引用类型 底层用char数组实现的
short s1=1;s1=s1+1;有错吗?short s1=1;s1+=1 有错吗

P73 如果整型字面量的值是-128到127之间 那么不会new新的Integer对象 而是直接引用常量池中的Integer对象

           Integer f1=100,f2=100,f3=150,f4=150;
            System.out.println(f1==f2); true
            System.out.println(f3==f4); false

P74 String 对象定义后不可变 线程安全

P75 数据类型之间的转换

P76 如何将一个java对象序列化到文件里

P77 如何实现克隆

P81 HashMap排序题 上机题

P83 请问ArrayList HashSet HashMap是线程安全的吗?如果不是我想要线程安全的集合怎么办?
ArrayList内部是用Object[]实现的
P89 并发集合和普通集合如何区别?
  在Java中有普通集合 同步(线程安全)的集合 并发集合。普通集合通常性能最高 但是不能保证多线程的安全性和并发的可靠性。
  线程安全集合仅仅是给集合添加了synchronized同步锁 严重牺牲了性能 而且对并发的效率就更低了 并发集合则通过复杂的策略不仅保证了多线程的安全又提高了并发时的效率
  并发集合常见的有 ConcurrentHashMap ConcurrentLinkedQueue ConcurrentLinkedDeque

并发集合位于java.util.concurrent包下
  ConcurrentHashMap 基于 concurrencyLevel划分出了多个Segment来对key-value进行存储 从而避免了每次put操作都得锁住这个数组。在默认情况下 最佳情况下可允许16个线程并发无阻塞的操作集合对象 尽可能地减少并发时的阻塞现象
  
P91 List的三个子类的特点
P92 HashMap支持null值和null键
  LinkedHashSet 继承于HashSet 同时又是基于LinkedHashMap来进行实现 底层使用的是LinkedHashMap
  
P96 ArrayList和Vector使用了数组的实现 是基于动态数组的数据结构
  LinkedList链表由一系列列表项链接而成 是基于链表的数据结构
P97 List a= new ArrayList()和ArrayList a=new ArrayList()的区别
P100 ArrayList和LinkedList在性能上各有优缺点 都有各自适用的地方 总得来说可以描述如下:
  当操作是在一列数据的后面添加数据而不是在前面或中间 并且需要随机地访问其中的元素的时候 ArrayList会提供比较好的性能
  当你的操作是在一列数据的前面或中间添加或删除数据的时候 并且按照顺序访问其中的元素的时候 就应该使用LinkedList了

P105 查看Thread类的run()方法的源代码 可以看到其实这两种方式都是在调用Thread对象的run方法 如果Thread类的run方法没有被覆盖 并且该Thread类设置一个Runnable对象 该run方法会调用Runnable对象的run方法

P106 定时器Timer和TimerTask
  线程之间会存在两种制约关系 间接相互制约 直接相互制约
  间接相互制约可以称为互斥 直接相互制约可以成为同步
  对于互斥可以这样理解 线程A和线程B互斥访问某个资源 他们之间就会产生个顺序问题 要么线程A等待线程B操作完毕 要么线程B等待线程A操作完毕 这其实就是线程的同步了 因此同步包括互斥 互斥其实是一种特殊的同步
  要求:子线程运行执行10次后 主线程再运行5此 这样交替执行三遍

P104-P248 多线程和并发库 有没有资料分享

P142 常用的并发队列有阻塞队列和非阻塞队列 前者使用锁实现,后者则使用CAS非阻塞算法实现

阻塞队列提供了四种处理方法:
  处理方式
  抛出异常
  返回特殊值
  一直阻塞
  超时退出
  
插入方法
  add(e)
  offer(e)
  put(e)
  offer(e,time,unit)

移除方法
  remove
  poll
  take
  poll(time,unit)

检查方法
  element
  peek()
  不可用
  不可用

BlockingQueue的实现类
  ArrayBlockingQueue 是一个有界的阻塞队列 其内部实现是将对象放到一个数组里
  DelayQueue 对元素进行持有直到一个特定的延迟到期
  LinkedBlockingQueue 内部以一个链式结构对其元素进行存储
  PriorityBlockingQueue 该队列中的元素的排序方式取决于自己的Comparable实现
SynchronousQueue 内部只能容纳单个元素

  ArrayBlockingQueue类的构造函数必须传入队列大小参数 所以为有界队列 默认是Lock为非公平锁

所谓公平锁:
  就是在并发环境中,每个线程在获取锁时会先查看此锁维护的等待队列,如果为空,或者当前线程是等待队列中的第一个就占有锁,否则就会加入到等待队列中,以后会按照FIFO的规则从队列中取到自己
  非公平锁:比较粗鲁 上来就直接尝试占有锁 如果尝试失败 就再采用类似公平锁的那种方式

  ArrayBlockingQueue 通过使用全局独占锁实现同时只能有一个线程进行入队或者出队操作,这个锁的力度比较大,有点类似于在方法上添加synchronized的意味。其中offer poll操作通过简单的加锁进行入队出队操作,而put take则使用了条件变量实现如果队列满则等待 如果队列空则等待,然后分别在出队和入队操作中发送信号激活等待线程同步。另外相比LinkedBlockingQueue ,ArrayBlockingQueue的size操作的结果是精确的,因为计算前加了全局锁

  需求:在多线程操作下 一个数组中最多只能存入3个元素。多放入不可以存入数组 或等待某线程对数组中某个元素取走才能放入,要求使用java多线程来实现。

public class BlockingQueueTest {
            public static void main(String[] args) {
                        final BlockingQueue queue = new ArrayBlockingQueue(3);
                        for (int i = 0; i < 2; i++) {
                                    new Thread(){
                                                public void run() {
                                                            while(true){
                                                                        try {
                                                                                    Thread.sleep((long)(Math.random()*1000));
                                                                                    System.out.println(Thread.currentThread().getName()+"准备放入数据!");
                                                                                    queue.put(1);
                                                                                    System.out.println(Thread.currentThread().getName()+"已经放入了数据,队列目前有"+queue.size()+"个数据");
                                                                        } catch (InterruptedException e) {
                                                                                    e.printStackTrace();
                                                                        }

                                                            }
                                                };
                                    }.start();

                                    new Thread(){
                                                public void run() {
                                                            while(true){
                                                                        try {
                                                                                    Thread.sleep(100);
                                                                                    System.out.println(Thread.currentThread().getName()+"准备取走数据!");
                                                                                    System.err.println(queue.take());
                                                                                    System.out.println(Thread.currentThread().getName()+"已经取走了数据,队列目前有"+queue.size()+"个数据");
                                                                        } catch (InterruptedException e) {
                                                                                    e.printStackTrace();
                                                                        }

                                                            }
                                                };
                                    }.start();

                        }
            }
}

  LinkedBlockingQueue中也有两个Node 分别用来存放首尾节点,并且里面有个初始值为0的原子变量count用来记录队列元素个数,另外里面有两个ReentrantLock的独占锁,分别用来控制元素的入队和出队加锁,其中takeLock用来控制同时只有一个线程可以从队列获取元素,其他线程必须等待,putLock控制同时只能有一个线程可以获取锁去添加元素,其他线程必须等待。另外notEmpty和notFull用来实现入队和出队的同步。另外由于出入队是两个非公平独占锁,所以可以同时有一个线程入队和一个线程出队,其实这个是个生产者-消费者模型

tomcat中任务队列TaskQueue
LinkedBlockingQueue 安全分析总结
  仔细思考阻塞队列是如何实现并发安全的维护队列链表的,先分析下简单的情况就是当队列里面有多个元素时候 由于同时只有一个线程(通过独占锁putLock实现)入队元素并且是操作last结点 而同时只有一个出队线程(通过独占锁takeLock实现)操作head结点,所以不存在并发安全问题.

PriorityBlockingQueue无界阻塞优先级队列
  PriorityBlockingQueue 是带优先级的无界阻塞队列 每次出队都返回优先级最高的元素,是二叉树最小堆的实现。

public class TestPriorityBlockingQueue {
            public static PriorityBlockingQueue<User> queue=new PriorityBlockingQueue<User>();

            public static void main(String[] args) {
                        queue.add(new User(1,"wu"));
                        queue.add(new User(5,"wu5"));
                        queue.add(new User(23,"wu23"));
                        queue.add(new User(55,"wu55"));
                        queue.add(new User(9,"wu9"));
                        queue.add(new User(3,"wu3"));
                        for(User user:queue){
                                    try {
                                                System.out.println(queue.take().name);
                                    } catch (InterruptedException e) {
                                                e.printStackTrace();
                                    }
                        }
            }
            static class User implements Comparable<User>{
                        public User(int age,String name){
                                    this.age=age;
                                    this.name=name;
                        }
                        int age;
                        String name;
                        @Override
                        public int compareTo(User o) {
                                    return this.age>o.age?-1:1;
                        }
            }
}

  SynchronousQueue也是一个队列来的,但它的特别之处在于它内部没有容器 一个生产线程 当它生产产品 如果当前没有人想要消费产品 此生产线程必须阻塞,等待一个消费线程调用take操作,take操作将会唤醒该生产线程 同时消费线程会获取生产线程的产品,这样的一个过程称为一次配对过程。

/*
 * put thread start
            take thread start
            take from putThread: 1
            take thread end
            put thread end
 *
 */
public class SynchronousQueueDemo {
            public static void main(String[] args) throws InterruptedException {
                        final SynchronousQueue<Integer> queue = new SynchronousQueue<Integer>();
                        Thread putThread = new Thread(new Runnable() {
                                    @Override
                                    public void run() {
                                                            System.out.println("put thread start");
                                                            try {
                                                                        queue.put(1);
                                                            } catch (InterruptedException e) {
                                                                        e.printStackTrace();
                                                            }
                                                            System.out.println("put thread end");
                                    }
                        });

                        Thread takeThread = new Thread(new Runnable() {
                                    @Override
                                    public void run() {
                                                            System.out.println("take thread start");
                                                            try {
                                                                        System.out.println("take from putThread: "+queue.take());
                                                            } catch (InterruptedException e) {
                                                                        e.printStackTrace();
                                                            }
                                                            System.out.println("take thread end");
                                    }
                        });
                        putThread.start();
                        Thread.sleep(1000);
                        takeThread.start();;
            }
}

  队列实现策略通常可分为公平模式和非公平模式
  SynchronousQueue实现的是公平模式 总结下来就是:队尾匹配队头出队 先进先出 体现公平原则

  DelayQueue是一个无界阻塞队列,只有在延迟期满时才能从中提取元素。该队列头部是延迟期满后保存时间最长的Delayed元素
  DelayQueue是一个使用优先队列PriorityQueue实现的BlockingQueue 优先队列的比较基准是时间

P249 在java中wait和sleep方法的不同
最大的不同是在等待时 wait会释放锁 而 sleep一直持有锁 wait通常被用于线程间交互 sleep通常被用于暂停执行

  synchronized和volatile关键字的作用
一旦一个共享变量(类的成员变量 类的静态成员变量)被volatile修饰后 就具备了两层语义:
  1)保证不同线程对这个变量进行操作时的可见性 即一个线程修改了某个变量的值 这个新值对其他线程来说是立即可见的。
  2)禁止进行指令重排序
  volatile本质是在告诉jvm当前变量在寄存器(工作内存)中的值是不确定的 需要从主存中读取
  synchronized则是锁定当前变量 只有当前线程可以访问该变量 其他线程被阻塞住
  前者仅能使用在变量级别 后者可以使用在变量 方法 和类级别
  前者仅能实现变量修改可见性 并不能保证原子性 后者可以保证变量的修改可见性和原子性
  前者不会造成线程阻塞 后者会造成线程阻塞
  前者标记的变量不会被编译器优化 后者标记的变量可以被编译器优化

  分析线程并发访问代码解释原因

P252
常用的线程池
  newSingleThreadExecutor
  newFixedThreadPool
  newCachedThreadPool
  newScheduledThreadPool

线程池的启动策略
  1、线程池刚创建时 里面没有一个线程。任务队列是作为参数传递过来的。不过 就算队列里面有任务 线程池也不会马上执行它们
  2、当调用execute()方法添加一个任务时 线程池会做如下判断:
   a)如果正在运行的线程数量小于corePoolSize 那么马上创建线程运行这个任务
   b)如果正在运行线程的数量大于或等于corePoolSize 那么将这个任务放入队列
   c)如果这时候队列满了 而且正在运行的线程数量小于maximumPoolSize 那么还是要创建线程运行这个任务
   d)如果队列满了 而且正在运行的线程数量大于或等于maximumPoolSize 那么线程池会抛出异常 告诉调用者 我不能再接受任务了
  3、当一个线程完成任务时 它会从队列中去下一个任务来执行
  4、当一个线程无事可做 超过一定时间 keepAliveTime 时 线程池会判断 如果当前运行的线程数大于corePoolSize 那么这个线程就会被停掉。所以线程池的所有任务完成后 它最终会收缩到corePoolSize 的大小

P254
如何控制某个方法允许并发访问线程的个数

P256 三个线程a b c并发运行 b c需要a线程的数据怎么实现

P259 同一个类中的2个方法都加上了同步锁 多个线程能同时访问同一个类中的这两个方法吗

P260 什么情况下导致死锁 遇到线程死锁该怎么解决

P268 Java中多线程间的通信怎么实现

P272 启动一个线程是调用run()方法还是start()方法
run()方法是线程启动后要进行回调callback的方法

P272 Java内部类

P273 写一个ArrayList的动态代理类

P274 动静态代理的区别 什么场景使用

P275 单例设计模式

P276
普通工厂模式
多个工厂方法模式
静态工厂方法模式
抽象工厂模式

P289
JVM垃圾回收机制和常见算法
GC在回收对象前首先必须发现那些无用的对象 如何去发现定位这些无用的对象 常用的搜索算法如下:
  1)引用计数器算法(废弃)
引用计数器实现简单 效率高 但是不能解决循环引用问题(A对象引用B对象 B对象引用A对象 但是A B对象已不被其他任何对象引用 ) 同时每次计数器的增加和减少都带来了很多额外的开销 所以在JDK1.1之后这个算法已经不再使用了
  2 )根搜索算法(使用)
根搜索算法是通过一些 GC Roots 对象作为起点 从这些结点开始往下搜索 搜索通过的路径成为引用链 当一个对象没有被GC Roots的引用链连接的时候 说明这个对象是不可用的

根据上面的算法搜索到无用对象之后 就是回收过程 回收算法如下:
  1)标记-清除算法
  2)复制算法
  3)标记-整理算法
  4)分代收集

P291 JVM的内存结构和内存分配

  方法区 java栈 java堆

P293 java中引用类型都有哪些

P295 heap和stack有什么区别

P302 解释内存中栈stack 堆heap 方法区method area的用法

P302 java的类加载器

P303 类什么时候被初始化
java类加载体系之ClassLoader双亲委托机制

P309 内存泄漏代码演示

P310 对于java的GC 哪些内存需要回收
  程序计数器 虚拟机栈 本地方法栈 方法区 堆
  前三者是每个线程私有的内存空间 随线程而生 随线程而亡
  但方法区和堆就不同了 一个接口的多个实现类需要的内存可能不一样 我们只有在程序运行期间才会知道会创建哪些对象 这部分内存的分配和回收都是动态的
总之 GC主要进行回收的内存是JVM中的方法区和堆

P311 JAVA的GC什么时候回收垃圾
可达性分析 Reachability Analysis
根据引用类型的不同 GC回收时也会有不同的操作
  1) 强引用 只要强引用还存在 GC永远不会回收掉被引用的对象
  2) 软引用 描述一些还有用但非必须的对象 在系统将会发生内存溢出之前 会把这些对象列入回收范围进行二次回收
  3) 弱引用 这些对象只能生存到下次GC之前
  4) 虚引用 一个对象是否存在虚引用 完全不会对其生存时间构成影响

P312 java8的新特性以及使用

P339 session共享怎么做的(分布式如何实现session共享)

P342 在单点登录中 如果cookie被禁用了怎么办?

P437 Redis最适合的场景有哪些
会话缓存 Session Cache
全页缓存 PC
队列
排行榜 计数器
发布 订阅

P445
Dubbo的容错机制有哪些
Dobbo的连接方式三种 广播 直连 使用zookeeper注册中心

P452 nginx多进程模型是如何实现高并发的
异步 非阻塞 使用epoll

java8
多线程并发库
dubbo
leyou
csdn
解决面试问题

https://blog.youkuaiyun.com/mine_song/article/details/64118963
分布式事务 不常用的接口
还有一些乐优视频没看

数组和集合之间的转换
可以不可以转化为流的方式https://www.cnblogs.com/zheyangsan/p/6910476.html

7.15到7.18面试问题

多线程
https
过滤数组
计算股票前后一天的差价
redis的订阅发布机制
nginx负载均衡的配置
mongodb

https://bbs.youkuaiyun.com/topics/390704568

jdk8.0
ssm复习

SELECT a.stock_id,a.date,a.price,(a.price-b.price)/b.price as zhangfu
FROM tb_stock a LEFT JOIN tb_stock b ON a.stock_id=b.stock_id AND a.date=b.date

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值