聊聊基础

Posted 博客园

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了聊聊基础相关的知识,希望对你有一定的参考价值。

摘要:最近和女友聊天,说我的工作需要作出调整,当前状态下压力太大,急需通过提供自身的专业技能来作出改变,所以便有了这个基础知识的整理。本来这个帖子是发布在简书的,因为考虑到简书比较好编辑和阅览,但是当我发布到简书后,女友竟然惊讶和肯定我终于开始写博客了,于是比较汗颜,就还是回归发布到这里来吧。后续针对这些基础知识做更深次,更全面的研究,本基础的一些问题来源:http://blog.csdn.net/exceptional_derek/article/details/69525715

一:基础类

 1.hashmap的基本原理,内部数据结构,put操作的整体流程,是否线程安全以及为什么?jdk8对hashmap做了哪些优化?

    答:HashMap是基于哈希表的Map接口的非同步实现。此实现提供所有可选的映射操作,并允许使用null值和null键。此类不保证映射的顺序,特别是它不保证该顺序恒久不变,HashMap实际上是一个“链表散列”的数据结构,即数组和链表的结合体。我们往HashMap中put元素的时候,先根据key的hashCode重新计算hash值,根据hash值得到这个元素在数组中的位置(即下标),如果数组该位置上已经存放有其他元素了,那么在这个位置上的元素将以链表的形式存放,新加入的放在链头,最先加入的放在链尾。如果数组该位置上没有元素,就直接将该元素放到此数组中的该位置上。对HashMap的操作不是线程安全的,通过观察源码发现,当多个线程在某一个时刻同时对HashMap做结构性的修改,我们可以看到整个方法实现中没有任何的同步机制,那么存在一个线程获取或者修改数据结构时,存在另外一个线程获取了一个错误的结果。jdk8对hashMap的数据结构的改变有个调整,当数组达到一定的阈值时,bucket就会由链表转换为红黑树的方式进行存储,而不是进行table的扩容。

2.String类为什么是不可变的?StringBuilder和StringBuffer的区别,字符串常量池,StringBuffer为什么是线程安全?加号的底层原理?

答:首先String类是用final关键字修饰,这说明String不可继承,String类的成员字段value是个char[]数组,而且是用final修饰的。final修饰的字段创建以后就不可改变。不可变的好处:1.1.参考java字符串池的设计模式。比如两个字符串值相等的变量,他们只会生成一个对象放到常量池中,然后两个变量都指向它,提升效率。1.2.安全性,如果String类可以被修改,那么在多线程的情况下会造成安全漏洞。2.1 StringBuilder和StringBuffer的区别:他们都是创建字符串的常用类,长度都是可以扩充的,实现了CharSequence接口。StringBuilder非线程安全,StringBuffer线程安全,所以通常在单线程环境下可以考虑是用StringBuilder来提升速度和效率,而在多线程的环境下则应该使用SringBuffer来保证线程安全。

3.反射、accessible,动态代理的原理,jdk动态代理与cglib的区别与各自的实现原理?

答:反射的机制是在编译时并不确定的哪个类被jvm加载,在程序运行的时候才加载、探知、自审。动态代理类的字节码在程序运行时由Java反射机制动态生成,无需程序员手工编写它的源代码。cglib是针对类来实现代理的,他的原理是对指定的目标类生成一个子类,并覆盖其中方法实现增强,但因为采用的是继承,所以不能对final修饰的类进行代理。两者区别:jdk的代理是利用反射生成字节码,并生成对象,前提是只能代理实现了接口的类,cglib是直接修改目标类的字节码生成对象,因为原理是继承,所以不能对final修饰的类进行代理。http://rejoy.iteye.com/blog/1627405https://my.oschina.net/tearsky/blog/635321

4.自动装箱,赋值操作,在内存里面是如何实现的?

答:自动装箱是将内置类型转换为对应的包装类型,在自动装箱的过程中,程序会创建一个包装类型的对象,然后将该变量指向这个新创建的对象,完成装箱操作。

5.接口和抽象类的区别

答:

1、抽象类和接口都不能直接实例化,如果要实例化,抽象类变量必须指向实现所有抽象方法的子类对象,接口变量必须指向实现所有接口方法的类对象。

2、抽象类要被子类继承,接口要被类实现。

3、接口只能做方法申明,抽象类中可以做方法申明,也可以做方法实现。

4、接口里定义的变量只能是公共的静态的常量,抽象类中的变量是普通变量。

5、抽象类里的抽象方法必须全部被子类所实现,如果子类不能全部实现父类抽象方法,那么该子类只能是抽象类。同样,一个实现接口的时候,如不能全部实现接口方法,那么该类也只能为抽象类。

6、抽象方法只能申明,不能实现,接口是设计的结果 ,抽象类是重构的结果。

7、抽象类里可以没有抽象方法。

8、如果一个类里有抽象方法,那么这个类只能是抽象类。

9、抽象方法要被实现,所以不能是静态的,也不能是私有的。

10、接口可继承接口,并可多继承接口,但类只能单根继承。

6.concurrenthashmap的原理,内部数据结构,如何提高并发性,jdk8中做了哪些优化。

答:ConcurrentHashMap允许多个修改操作并发进行,其关键在于使用了锁分离技术。它使用了多个锁来控制对hash表的不同部分进行的修改。ConcurrentHashMap内部使用段(Segment)来表示这些不同的部分,每个段其实就是一个小的hash table,它们有自己的锁。只要多个修改操作发生在不同的段上,它们就可以并发进行。

改进一:取消segments字段,直接采用transient volatile HashEntry[] table保存数据,采用table数组元素作为锁,从而实现了对每一行数据进行加锁,进一步减少并发冲突的概率。

改进二:将原先table数组+单向链表的数据结构,变更为table数组+单向链表+红黑树的结构。对于hash表来说,最核心的能力在于将key hash之后能均匀的分布在数组中。如果hash之后散列的很均匀,那么table数组中的每个队列长度主要为0或者1。但实际情况并非总是如此理想,虽然ConcurrentHashMap类默认的加载因子为0.75,但是在数据量过大或者运气不佳的情况下,还是会存在一些队列长度过长的情况,如果还是采用单向列表方式,那么查询某个节点的时间复杂度为O(n);因此,对于个数超过8(默认值)的列表,jdk1.8中采用了红黑树的结构,那么查询的时间复杂度可以降低到O(logN),可以改进性能。

7.hashset的原理?

答:HashSet中add方法调用的是底层HashMap中的put()方法,而如果是在HashMap中调用put,首先会判断key是否存在,如果key存在则修改value值,如果key不存在这插入这个key-value。而在set中,因为value值没有用,也就不存在修改value值的说法,因此往HashSet中添加元素,首先判断元素(也就是key)是否存在,如果不存在这插入,如果存在着不插入,这样HashSet中就不存在重复值。

8.GC原理,分代机制,可达性分析?

答:对传统的、基本的GC实现来说,由于它们在GC的整个工作过程中都要“stop-the-world”,如果能想办法缩短GC一次工作的时间长度就是件重要的事情。如果说收集整个GC堆耗时太长,那不如只收集其中的一部分?于是就有好几种不同的划分(partition)GC堆的方式来实现部分收集,而分代式GC就是这其中的一个思路。

9.JVM参数有哪几种,如何调优?

-Xmx2g //JVM最大允许分配的堆内存,按需分配

-Xms2g //JVM初始分配的堆内存,一般和Xmx配置成一样以避免每次gc后JVM重新分配内存。-Xmn256m //年轻代内存大小,整个JVM内存=年轻代 + 年老代 + 持久代年轻代分三个区, 分别是enden区和两个survivor区。

10.JMM特性有哪些?

1.可见性:JMM提供了volatile变量定义、final、synchronized块来保证可见性。

2.有序性:这个概念是相对而言的,如果在本线程内,所有的操作都是有序的,如果在一个线程观察另一个线程,所有的操作都是无序的,前句是“线程内表现为串行行为”,后句是“指令的重排序”和“工作内存和主内存同步延迟”现象,模型提供了volatile和synchronized来保证线程之间操作的有序性。

3.重排序:在执行程序时为了提高性能,编译器和处理器常常会对指令做重排序(编译器、处理器),就是因为这些重排序,所以可能会导致多线程程序出现内存可见性问题(数据安全问题)和有序性问题。可见性、原子性、有序性.

11、什么是跳表?

二、多线程

1、线程有几种状态?之间是如何切换的?

答:新建状态、就绪状态、运行状态、阻塞状态及死亡状态。主要是通过获取锁标记来获取对该资源的使用权限,当对象调用了start()进入到就绪状态,进入就绪后,当该对象被操作系统选中,获得CPU时间片就会进入运行状态;接下来的状态切换就会比较复杂,主要通过线程调用不同的方法,就会切换不同的运行状态。

2、volatile的作用(两点),volatile的原理与应用场景。

答:volatile让变量每次在使用的时候,都从主存中取。(1.将当前处理器缓存行的数据会写回到系统内存,2.这个写回内存的操作会引起在其他CPU里缓存了该内存地址的数据无效。)而不是从各个线程的“工作内存”。volatile具有synchronized关键字的“可见性”,但是没有synchronized关键字的“并发正确性”,也就是说不保证线程执行的有序性,volatile变量对于每次使用,线程都能得到当前volatile变量的最新值。但是volatile变量并不保证并发的正确性。

3、线程安全是什么?如何做到线程安全?怎么判断一个类是不是线程安全?

答:类要成为线程安全的,首先必须在单线程环境中有正确的行为。如果一个类实现正确(这是说它符合规格说明的另一种方式),那么没有一种对这个类的对象的操作序列(读或者写公共字段以及调用公共方法)可以让对象处于无效状态,观察到对象处于无效状态、或者违反类的任何不可变量、前置条件或者后置条件的情况。此外,一个类要成为线程安全的,在被多个线程访问时,不管运行时环境执行这些线程有什么样的时序安排或者交错,它必须仍然有如上所述的正确行为,并且在调用的代码中没有任何额外的同步。其效果就是,在所有线程看来,对于线程安全对象的操作是以固定的、全局一致的顺序发生的。正确性与线程安全性之间的关系非常类似于在描述 ACID(原子性、一致性、独立性和持久性)事务时使用的一致性与独立性之间的关系:从特定线程的角度看,由不同线程所执行的对象操作是先后(虽然顺序不定)而不是并行执行的。

4、线程同步有几种方式?为何要使用同步?

答: java允许多线程并发控制,当多个线程同时操作一个可共享的资源变量时(如数据的增删改查), 将会导致数据不准确,相互之间产生冲突,因此加入同步锁以避免在该线程没有完成操作之前,被其他线程的调用, 从而保证了该变量的唯一性和准确性。

同步的实现方式总共分为七种:

1.同步方法 : 即有synchronized关键字修饰的方法,由于java的每个对象都有一个内置锁,当用此关键字修饰方法时,内置锁会保护整个方法。在调用该方法前,需要获得内置锁,否则就处于阻塞状态。

2.同步代码块:即有synchronized关键字修饰的语句块,被该关键字修饰的语句块会自动被加上内置锁,从而实现同步

3.使用特殊域变量(volatile)实现线程同步 

a.volatile关键字为域变量的访问提供了一种免锁机制。

b.使用volatile修饰域相当于告诉虚拟机该域可能会被其他线程更新。

 c.因此每次使用该域就要重新计算,而不是使用寄存器中的值。

 d.volatile不会提供任何原子操作,它也不能用来修饰final类型的变量。

 4.使用重入锁实现线程同步.

java.util.concurrent包下的ReentrantLock类是可重入、互斥、实现了Lock接口的锁,它与使用synchronized方法和快具有相同的基本行为和语义,并且扩展了其能力.

5.使用局部变量实现线程同步,如果使用ThreadLocal管理变量,则每一个使用该变量的线程都获得该变量的副本, 副本之间相互独立,这样每一个线程都可以随意修改自己的变量副本,而不会对其他线程产生影响

6.使用阻塞队列实现线程同步,前面5种同步方式都是在底层实现的线程同步,但是我们在实际开发当中,应当尽量远离底层结构。使用javaSE5.0版本中新增的java.util.concurrent包将有助于简化开发。使用LinkedBlockingQueue来实现线程的同步, LinkedBlockingQueue是一个基于已连接节点的,范围任意的blocking queue。队列是先进先出的顺序(FIFO)。

7.使用原子变量实现线程同步,需要使用线程同步的根本原因在于对普通变量的操作不是原子的。原子操作就是指将读取变量值、修改变量值、保存变量值看成一个整体来操作,即-这几种行为要么同时完成,要么都不完成。在java的util.concurrent.atomic包中提供了创建了原子类型变量的工具类,使用该类可以简化线程同步。其中AtomicInteger 表可以用原子方式更新int的值,可用在应用程序中(如以原子方式增加的计数器),但不能用于替换Integer;可扩展Number,允许那些处理机遇数字类的工具和实用工具进行统一访问。

5、threadlocal的原理

答:ThreadLocal提供了set和get访问器用来访问与当前线程相关联的线程局部变量。当线程中的threadlocalmap是null的时候,会调用createmap创建一个map。同时根据函数参数设置上初始值。也就是说,当前线程的threadlocalmap是在第一次调用set的时候创建map并且设置上相应的值的。在ThreadLocal的set函数中,可以看到,其中的map.set(this, value);把当前的threadlocal传入到map中作为键,也就是说,在不同的线程的threadlocals变量中,都会有一个以你所声明的那个线程局部变量threadlocal作为键的key-value。假设说声明了N个这样的线程局部变量变量,那么在线程的ThreadLocalMap中就会有n个分别以你的线程局部变量作为key的键值对。

6、synchronized是如何实现的?

答:每个对象有一个监视器锁(monitor)。当monitor被占用时就会处于锁定状态,线程执行monitorenter指令时尝试获取monitor的所有权,过程如下:

1、如果monitor的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程即为monitor的所有者。

2、如果线程已经占有该monitor,只是重新进入,则进入monitor的进入数加1.

3.如果其他线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再重新尝试获取monitor的所有权。

对于方法的同步,方法的同步并没有通过指令monitorenter和monitorexit来完成(理论上其实也可以通过这两条指令来实现),不过相对于普通方法,其常量池中多了ACC_SYNCHRONIZED标示符。JVM就是根据该标示符来实现方法的同步的:当方法调用时,调用指令将会检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置,如果设置了,执行线程将先获取monitor,获取成功之后才能执行方法体,方法执行完后再释放monitor。在方法执行期间,其他任何线程都无法再获得同一个monitor对象。 其实本质上没有区别,只是方法的同步是一种隐式的方式来实现,无需通过字节码来完成。

7、sleep和wait的区别?

答:sleep()方法导致了程序暂停执行指定的时间,让出cpu给其他线程,但是他的监控状态依然保持者,当指定的时间到了又会自动恢复运行状态。在调用sleep()方法的过程中,线程不会释放对象锁。而当调用wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用notify()方法后本线程才进入对象锁定池准备,获取对象锁进入运行状态。

8、线程池有几种?各自的应用场景。

答:1.newFixedThreadPool创建一个指定工作线程数量的线程池。每当提交一个任务就创建一个工作线程,如果工作线程数量达到线程池初始的最大数,则将提交的任务存入到池队列中。

2.newCachedThreadPool创建一个可缓存的线程池。这种类型的线程池特点是:

1).工作线程的创建数量几乎没有限制(其实也有限制的,数目为Interger. MAX_VALUE), 这样可灵活的往线程池中添加线程。

2).如果长时间没有往线程池中提交任务,即如果工作线程空闲了指定的时间(默认为1分钟),则该工作线程将自动终止。终止后,如果你又提交了新的任务,则线程池重新创建一个工作线程。

3.newSingleThreadExecutor创建一个单线程化的Executor,即只创建唯一的工作者线程来执行任务,如果这个线程异常结束,会有另一个取代它,保证顺序执行(我觉得这点是它的特色)。单工作线程最大的特点是可保证顺序地执行各个任务,并且在任意给定的时间不会有多个线程是活动的。

4.newScheduleThreadPool创建一个定长的线程池,而且支持定时的以及周期性的任务执行,类似于Timer。

总结: 

一.FixedThreadPool是一个典型且优秀的线程池,它具有线程池提高程序效率和节省创建线程时所耗的开销的优点。但是,在线程池空闲时,即线程池中没有可运行任务时,它不会释放工作线程,还会占用一定的系统资源。

二.CachedThreadPool的特点就是在线程池空闲时,即线程池中没有可运行任务时,它会释放工作线程,从而释放工作线程所占用的资源。但是,但当出现新任务时,又要创建一新的工作线程,又要一定的系统开销。并且,在使用CachedThreadPool时,一定要注意控制任务的数量,否则,由于大量线程同时运行,很有会造成系统瘫痪。

9、线程池的原理,主要有几个参数?线程池满了怎么办?

答:

减少在创建和销毁线程上所花的时间以及系统资源的开销。

如不使用线程池,有可能造成系统创建大量线程而导致消耗完系统内存以及”过度切换”。

10、Semaphore、countdownlatch、futureTask

答:

11、submit和execute的区别。

答:execute(Runnable x) 没有返回值。可以执行任务,但无法判断任务是否成功完成。——实现Runnable接口

submit(Runnable x) 返回一个future。可以用这个future来判断任务是否成功完成。——实现Callable接口。

12、Future接口的几个主要方法

答:

13、创建线程有几种方式

答:1.继承Thread类创建线程类

2.通过Runnable接口创建线程类

3.通过Callable和Future创建线程

14、可重入锁是如何实现的

答:

三、数据库

1、mysql索引原理?为什么是B+树?有什么优点?

1.文件很大,不可能全部存储在内存中,故要存储到磁盘上,索引的结构组织要尽量减少查找过程中磁盘I/O的存取次数,

2、事务隔离级别有哪几种?mysql默认的隔离级别是?脏读、幻读、不可重复读是什么情况?

答:,由低到高分别为Read uncommitted 、Read committed 、Repeatable read 、Serializable,

3、MVCC原理

4、mysql有哪几种锁?

答:共享读锁,独占写锁。根据数据引擎的不同,锁的类型也不一样,对于innodb

5、mysql的存储引擎有哪几种?区别和各自的适用场景。

6、query cache的配置

7、ACID

原子性、一致性、隔离性、持久性。

8、如何优化慢查询

答:1.查询条件带上索引,

9、最左前缀匹配原则,原理

四、算法

1、一致性哈希的原理

2、手写二分查找,快速排序

3、手写LRU算法

4、两个链表找交点

5、两个无限长的数字求和

6、手写生产者消费者demo

7、256M内存排序2G大小的文件

8、求数组最大子序列

五、操作系统与计算机网络

1、如何从访问日志中找出量最大的10个ip?awk语句了解吗?

2、jstack,jstat,jmap,jheap命令了解吗,如何使用?

3、系统负载情况如何查看?

4、网络分层协议了解吗?

5、tcp三次握手,四次挥手了解吗?

6、aio,bio,nio的区别

7、select,poll,epoll的区别?

8、io模型有哪些?

六、开源框架与组件

这部分主要根据简历以及项目的实际情况来问。

1、对spring了解吗?ioc,aop,transaction注解

2、spingmvc了解吗?

3、Redis与memcache的区别

4、redis持久化策略,rdb与aof的区别与应用场景

5、memcached的内存是如何分配的?一致性哈希原理

6、mq的原理与应用场景,mq是如何保证不丢消息的?

7、tomcat的原理,主要运用了哪些设计模式?

8、redis与memcached内存分别是如何回收的?

9、guava的缓存是怎么实现的?

七、场景设计与架构

1、秒杀场景,如何做技术选型?

2、设计一个支持高并发的服务,写出核心代码

3、高并发与高可用如何实现?

4、服务降级怎么做?限流、限速、超时重试、熔断、自恢复、分别如何实现?

5、什么是微服务?有什么好处?为什么要这么做?

6、CAP理论是什么?项目中的哪些场景用到了CAP理论?

7、BASE理论是什么?

8、什么时候应该使用mq?

八、其他

1、平时都通过什么方式学习技术?

2、最近学的一个知识点是什么?

3、对带人有什么经验?

4、最熟悉的一个项目是什么?

5、跳槽的时候,你最看重什么?

6、为什么跳槽?为什么选择我们公司?

以上是关于聊聊基础的主要内容,如果未能解决你的问题,请参考以下文章

聊聊kafka结构,kafka基础了解

聊聊消息队列中的基础概念

一个小栗子聊聊JAVA泛型基础

RPC基础系列1聊聊RPC

深入理解 WKWebView(基础篇)—— 聊聊 cookie 管理那些事

聊聊事务与分布式系统