一聊就会,一问就退,一写就废。这是很多公司程序员的真实写照,基/中层管理者尤甚。早早的和技术渐行渐远,导致裁员潮到来时很容易获得一张“飞机票”,年纪越大,焦虑感越强。
扎不扎心,老铁。不过不用悲观,习大大都说了实干才能兴邦嘛。
扯远了,言归正传。我们来说下面试中常涉及的Java专业术语。
StringBuffer和StringBuilder区别是什么?
StringBuffer线程安全,StringBuilder非线程安全但是效率更高非并发场景使用。
什么是线程安全?
多个线程访问同一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他操作,调用这个对象的行为都可以获得正确的结果,那么这个对象就是线程安全的。或者说:一个类或者程序所提供的接口对于线程来说是原子操作或者多个线程之间的切换不会导致该接口的执行结果存在二义性,也就是说我们不用考虑同步的问题。线程安全问题大多是由全局变量及静态变量引起的,局部变量逃逸也可能导致线程安全问题。若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。类要成为线程安全的,首先必须在单线程环境中有正确的行为。如果一个类实现正确(这是说它符合规格说明的另一种方式),那么没有一种对这个类的对象的操作序列(读或者写公共字段以及调用公共方法)可以让对象处于无效状态,观察到对象处于无效状态、或者违反类的任何不可变量、前置条件或者后置条件的情况。此外,一个类要成为线程安全的,在被多个线程访问时,不管运行时环境执行这些线程有什么样的时序安排或者交错,它必须仍然有如上所述的正确行为,并且在调用的代码中没有任何额外的同步。其效果就是,在所有线程看来,对于线程安全对象的操作是以固定的、全局一致的顺序发生的。正确性与线程安全性之间的关系非常类似于在描述 ACID(原子性、一致性、独立性和持久性)事务时使用的一致性与独立性之间的关系:从特定线程的角度看,由不同线程所执行的对象操作是先后(虽然顺序不定)而不是并行执行的。
什么是死锁?
线程死锁是指由于两个或者多个线程互相持有对方所需要的资源,导致这些线程处于等待状态,无法前往执行。当线程进入对象的synchronized代码块时,便占有了资源,直到它退出该代码块或者调用wait方法,才释放资源,在此期间,其他线程将不能进入该代码块。当线程互相持有对方所需要的资源时,会互相等待对方释放资源,如果线程都不主动释放所占有的资源,将产生死锁。
当然死锁的产生是必须要满足一些特定条件的:1.互斥条件:进程对于所分配到的资源具有排它性,即一个资源只能被一个进程占用,直到被该进程释放 2.请求和保持条件:一个进程因请求被占用资源而发生阻塞时,对已获得的资源保持不放。3.不剥夺条件:任何一个资源在没被该进程释放之前,任何其他进程都无法对他剥夺占用 4.循环等待条件:当发生死锁时,所等待的进程必定会形成一个环路(类似于死循环),造成永久阻塞。
synchronized的实现原理是什么?
Synchronized的作用主要有三个:
(1)确保线程互斥的访问同步代码
(2)保证共享变量的修改能够及时可见
(3)有效解决重排序问题。
同步语句块的实现使用的是monitorenter 和 monitorexit 指令,其中monitorenter指令指向同步代码块的开始位置,monitorexit指令则指明同步代码块的结束位置,当执行monitorenter指令时,当前线程将试图获取 objectref(即对象锁) 所对应的 monitor 的持有权,当 objectref 的 monitor 的进入计数器为 0,那线程可以成功取得 monitor,并将计数器值设置为 1,取锁成功。如果当前线程已经拥有 objectref 的 monitor 的持有权,那它可以重入这个 monitor,重入时计数器的值也会加 1。倘若其他线程已经拥有 objectref 的 monitor 的所有权,那当前线程将被阻塞,直到正在执行线程执行完毕,即monitorexit指令被执行,执行线程将释放 monitor(锁)并设置计数器值为0 ,其他线程将有机会持有 monitor 。
方法级的同步是隐式,即无需通过字节码指令来控制的。当方法调用时,调用指令将会 检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置,如果设置了,执行线程将先持有monitor(虚拟机规范中用的是管程一词), 然后再执行方法,最后再方法完成(无论是正常完成还是非正常完成)时释放monitor。
有了synchronized,还需要volatile做什么事?
一方面是因为synchronized是一种锁机制,存在阻塞问题和性能问题,而volatile并不是锁,所以不存在阻塞和性能问题。另外一方面,因为volatile借助了内存屏障来帮助其解决可见性和有序性问题,而内存屏障的使用还为其带来了一个禁止指令重排的附件功能,所以在有些场景中是可以避免发生指令重排的问题的。volatile保证了变量在多个线程间的可见性,可以有效避免脏读现象。
synchronized的锁优化是怎么处理的?
JDK1.6对锁的实现引入了大量的优化,如自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁等技术来减少锁操作的开销。自旋锁,就是让该线程等待一段时间,不会被立即挂起(就是不让前来获取该锁(已被占用)的线程立即阻塞),看持有锁的线程是否会很快释放锁。适应自旋锁就意味着自旋的次数不再是固定的,它是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定。在有些情况下,JVM检测到不可能存在共享数据竞争,这是JVM会对这些同步锁进行锁消除。锁消除的依据是逃逸分析的数据支持。锁粗化就是将多个连续的加锁、解锁操作连接在一起,扩展成一个范围更大的锁。轻量级锁的主要目的是在多没有多线程竞争的前提下,减少传统的重量级锁使用操作系统互斥量产生的性能消耗。当关闭偏向锁功能或者多个线程竞争偏向锁导致偏向锁升级为轻量级锁,则会尝试获取轻量级锁。重量级锁通过对象内部的监视器(monitor)实现,其中monitor的本质是依赖于底层操作系统的Mutex Lock实现,操作系统实现线程之间的切换需要从用户态到内核态的切换,切换成本非常高。
JMM是什么?
JMM是一种规范,目的是解决由于多线程通过共享内存进行通信时,存在的本地内存数据不一致、编译器会对代码指令重排序、处理器会对代码乱序执行等带来的问题。
Java并发包都有哪些,性能怎样?
1、locks部分:包含在java.util.concurrent.locks包中,提供显式锁(互斥锁和速写锁)相关功能;
2、atomic部分:包含在java.util.concurrent.atomic包中,提供原子变量类相关的功能,是构建非阻塞算法的基础;
3、executor部分:散落在java.util.concurrent包中,提供线程池相关的功能;
4、collections部分:散落在java.util.concurrent包中,提供并发容器相关功能;
5、tools部分:散落在java.util.concurrent包中,提供同步工具类,如信号量、闭锁、栅栏等功能;
什么是fail-fast?
fail-fast 机制是java集合(Collection)中的一种错误机制。当多个线程对同一个集合的内容进行操作时,就可能会产生fail-fast事件。
什么是fail-safe?
fail-safe是集合容器,在遍历时不是直接在集合内容上访问的,而是先复制原有集合内容,在拷贝的集合上进行遍历。
什么是CopyOnWrite?
CopyOnWrite容器即写时复制的容器。通俗的理解是当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。这样做的好处是我们可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。
什么是AQS呢?
AQS(AbstractQueuedSynchronizer)同步器是用来构建锁和其他同步组件的基础框架,它的实现主要依赖一个int成员变量来表示同步状态以及通过一个FIFO队列构成等待队列。它的子类必须重写AQS的几个protected修饰的用来改变同步状态的方法,其他方法主要是实现了排队和阻塞机制。状态的更新使用getState,setState以及compareAndSetState这三个方法。子类被推荐定义为自定义同步组件的静态内部类,同步器自身没有实现任何同步接口,它仅仅是定义了若干同步状态的获取和释放方法来供自定义同步组件的使用,同步器既支持独占式获取同步状态,也可以支持共享式获取同步状态,这样就可以方便的实现不同类型的同步组件。同步器是实现锁(也可以是任意同步组件)的关键,在锁的实现中聚合同步器,利用同步器实现锁的语义。可以这样理解二者的关系:锁是面向使用者,它定义了使用者与锁交互的接口,隐藏了实现细节;同步器是面向锁的实现者,它简化了锁的实现方式,屏蔽了同步状态的管理,线程的排队,等待和唤醒等底层操作。锁和同步器很好的隔离了使用者和实现者所需关注的领域。
什么是CAS呢?
当多个线程同时对某个资源进行CAS操作,只能有一个线程操作成功,但是并不会阻塞其他线程,其他线程只会收到操作失败的信号。可见 CAS 其实是一个乐观锁。
CAS的两个缺点:ABA 的问题,就是一个值从A变成了B又变成了A,使用CAS操作不能发现这个值发生变化了,处理方式是可以使用携带类似时间戳的版本AtomicStampedReference 性能问题,我们使用时大部分时间使用的是 while true 方式对数据的修改,直到成功为止。优势就是相应极快,但当线程数不停增加时,性能下降明显,因为每个线程都需要执行,占用CPU时间。
乐观锁是怎样的?
乐观锁顾名思义就是在操作时很乐观,认为操作不会产生并发问题(不会有其他线程对数据进行修改),因此不会上锁。但是在更新时会判断其他线程在这之前有没有对数据进行修改,一般会使用版本号机制或CAS(compare and swap)算法实现。
乐观锁悲观锁区别是什么?
悲观锁(Pessimistic Lock) 在每次拿数据时都会上锁,传统的关系型数据库里边经常用到用到比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现,主要依靠数据库提供的锁机制。
乐观锁(Optimistic Lock) 每次拿数据的时候都不会上锁,在更新的时候会判断一下在此期间别人有没有去更新这个数据。乐观锁适用于多读的应用类型,这样可以提高吞吐量。
区别 两种锁各有优缺点,乐观锁适用于写比较少的情况下,即冲突真的很少发生的时候,这样可以省去了锁的开销,加大了系统的整个吞吐量。悲观锁适用于经常产生冲突,上层应用会不断的进行retry。
数据库如何实现悲观锁和乐观锁?
乐观锁不是数据库自带的,需要我们自己去实现。乐观锁是指操作数据库时(更新操作),想法很乐观,认为这次的操作不会导致冲突,在操作数据时,并不进行任何其他的特殊处理(也就是不加锁),而在进行更新后,再去判断是否有冲突了。
通常实现是这样的:在表中的数据进行操作时(更新),先给数据表加一个版本(version)字段,每操作一次,将那条记录的版本号加1。也就是先查询出那条记录,获取出version字段,如果要对那条记录进行操作(更新),则先判断此刻version的值是否与刚刚查询出来时的version的值相等,如果相等,则说明这段期间,没有其他程序对其进行操作,则可以执行更新,将version字段的值加1;如果更新时发现此刻的version值与刚刚获取出来的version的值不相等,则说明这段期间已经有其他程序对其进行操作了,则不进行更新操作。
悲观锁就是在操作数据时,认为此操作会出现数据冲突,所以在进行每次操作时都要通过获取锁才能进行对相同数据的操作。
数据库有实现悲观锁,共享锁和排它锁就是两种实现方式。共享锁指的就是对于多个不同的事务,对同一个资源共享同一个锁,在执行语句后面加上lock in share mode就代表对某些资源加上共享锁了。排它锁与共享锁相对应,就是指对于多个不同的事务,对同一个资源只能有一把锁,在需要执行的语句后面加上for update就可以了。
数据库锁和隔离级别有什么关系?
据库的四种隔离级别:未提交读(Read Uncommitted):允许脏读,也就是可能读取到其他会话中未提交事务修改的数据 提交读(Read Committed):只能读取到已经提交的数据。Oracle等多数数据库默认都是该级别 (不重复读) 可重复读(Repeated Read):可重复读。在同一个事务内的查询都是事务开始时刻一致的,InnoDB默认级别。在SQL标准中,该隔离级别消除了不可重复读,但是还存在幻象读 串行读(Serializable):完全串行化的读,每次读都需要获得表级共享锁,读写相互都会阻塞。
数据库锁和索引有什么关系?
索引分为主键索引和非主键索引两种,如果一条sql语句操作了主键索引,MySQL就会锁定这条主键索引;如果一条语句操作了非主键索引,MySQL会先锁定该非主键索引,再锁定相关的主键索引。
什么是聚簇索引?
聚簇索引是指将数据存储与索引放到了一块,找到索引也就找到了数据。
什么是非聚簇索引?
非聚簇索引是指将数据存储于索引分开结构,索引结构的叶子节点指向了数据的对应行,myisam通过key_buffer把索引先缓存到内存中,当需要访问数据时(通过索引访问数据),在内存中直接搜索索引,然后通过索引找到磁盘相应数据,这也就是为什么索引不在key buffer命中时,速度慢的原因。
索引最左前缀是什么?
最左前缀顾名思义,就是最左优先,上例中我们创建了lname_fname_age多列索引,在创建多列索引时,根据业务需求,where子句中使用最频繁的一列放在最左边,因为MySQL索引查询会遵循最左前缀匹配的原则。
什么是B+树索引?
首先,B+ Tree是B Tree的升级版,其最大的特点便是中间节点只有链指针和key,而不存储数据,所有的value都会存储在B+Tree的叶子节点中,所以基于树的深度,B+Tree的查询时间复杂度很稳定,每次都要搜索到叶子节点。由此,因为中间文档小,B+Tree作为索引可以提升查找效率,同时在MySQL的索引中,叶子结点各有指针指向相邻的数据,便于范围查找。
什么是联合索引?
对多个字段同时建立的索引(有顺序,ABC,ACB是完全不同的两种联合索引。)
什么是回表?
简单来说就是数据库根据索引找到了指定的记录所在行后,还需要根据rowid再次到数据块里取数据的操作。
分布式锁有了解吗?
类似于jvm的锁,不过分布式锁是针对集群的,当某个机器需要对共享资源进行操作的时候,需要先获取到锁,然后进行操作,最后释放锁,如果在当前机器进行操作的时候,其他机器也有请求进来,此时需要等待当前锁释放之后才能尝试获取锁,获取成功后进行操作,分布式锁的实现可以使用数据库,redis,zk等实现。
Redis怎么实现分布式锁?
setnx命令,存储数据时,如果当前key已经存在,则返回0,表示没有设置成功,否则返回1,表示设置成功。分布式锁的实现就可以通过是否设置成功某个key来判断是否获取到锁,为了防止死锁问题,还应该为该key设置超时时间,为了防止误删别人的锁,需要返回value,在释放锁时判断当前想要删除的锁是否是自己的锁。
为什么要用Redis?
·Redis内存操作,速度快
·Redis数据会同步保存到硬盘,数据有持久性(存在一定丢失可能)
·支持分布式
·用的人多,社区支持好,有问题能及时得到解决
Redis和memcache区别是什么?
数据类型:redis支持的数据类型更多
缓存值大小:redis支持的缓存值更大
数据持久性:redis支持数据持久,memcache数据只保存在内存中,容易丢失
Zookeeper怎么实现分布式锁?
使用临时节点:这里需要使用当zk的监听zk节点的变化,在请求进来时,尝试去创建一个约定名称的零时节点,如果创建成功,则表示能获取锁,可以进行操作,操作完成之后关闭连接,zk会自动删除该临时节点。如果创建临时节点失败,则等待,监听该节点名称,当该节点发生变化时,会通知到所有监听带节点的客户端,所以当服务器监听到该节点的删除事件时,可以尝试再次创建节点,重复之前的操作,知道获取锁成功,断开连接。使用有序节点时需要手动删除自己创建的节点,是否获取到锁就判断当前客户端节点在zk中是否排在第一位。
什么是Zookeeper?
ZooKeeper是一个分布式的,开放源码的分布式应用程序协调服务,是Google的Chubby一个开源的实现,是Hadoop和Hbase的重要组件。
什么是CAP?
CAP原则又称CAP定理,指的是在一个分布式系统中,一致性(Consistency)、可用性(Availability)、分区容错性(Partition tolerance)。
什么是BASE?和CAP什么区别?
CAP: C: 一致性 A:可用性 P:分区容错性
对于分布式系统而言,分区容错性是必须的,需要把精力花在如何根据业务特点在C和A之间寻求平衡。
BASE: BA:基本可用 S:软状态,允许系统在不同节点的数据副本之间进行数据同步过程存在延时 E:最终一致性
CAP怎么推导?如何取舍?
在理论计算机科学中,CAP定理(CAP theorem),又被称作布鲁尔定理(Brewer’s theorem),它指出对于一个分布式计算系统来说,不可能同时满足,网络发生故障宁可停止服务也不能发生服务泄露。
分布式系统怎么保证数据一致性?
对于同步的调用的方式,比较简单,我们能够及时获取结果;对于异步的通知,就必须采用请求,应答的方式进行。
什么是分布式事务?分布式事务方案?
分布式事务的核心是解决多机系统中的原子性和一致性(AC)问题,ID可由单机系统的事务保证。
线程安全的单例?
在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例。这些应用都或多或少具有资源管理器的功能。每台计算机可以有若干个打印机,但只能有一个Printer Spooler,以避免两个打印作业同时输出到打印机中。每台计算机可以有若干通信端口,系统应当集中管理这些通信端口,以避免一个通信端口同时被两个请求同时调用。总之,选择单例模式就是为了避免不一致状态,避免政出多头。
public class Singleton {
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton (){}
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
不用synchronized和lock能实现线程安全的单例吗?
public class Singleton {
private static final AtomicReference INSTANCE = new AtomicReference();
private Singleton() {}
public static Singleton getInstance() {
for (;;) {
Singleton singleton = INSTANCE.get();
if (null != singleton) {
return singleton;
}
singleton = new Singleton();
if (INSTANCE.compareAndSet(null, singleton)) {
return singleton;
}
}
什么是Paxos算法?
Paxos算法是基于消息传递且具有高度容错特性的一致性算法,是目前公认的解决分布式一致性问题最有效的算法之一。
ArrayList和LinkedList和Vector的区别
ArrayList和Vector底层是数据结构实现,LinkedList是双向链表,ArrayList和Vector查询快,删除慢,LinkedList与之相反,ArrayList是线程不安全的,性能优于Vector,Vector是线程安全的,能解决线程安全问题。
SynchronizedList和Vector的区别
SynchronizedList采用的同步代码块方式,vector采用的同步方法实现。所以主要区别:1.SynchronizedList有很好的扩展和兼容功能。可以将所有的List的子类转成线程安全的类 2.使用SynchronizedList的时候,进行遍历时要手动进行同步处理。3.SynchronizedList可以指定锁定的对象。
Arrays.asList获得的List使用时需要注意什么
asList 得到的只是一个 Arrays 的内部类,一个原来数组的视图 List,因此如果对它进行增删操作会报错,用 ArrayList 的构造器可以将其转变成真正的 ArrayList。
List和原始类型List之间的区别?
在编译时编译器不会对原始类型进行类型安全检查,却会对带参数的类型进行检查。
List>和List之间的区别是什么?
List 是一个未知类型的List,而List 其实是任意类型的List。
synchronized是如何实现的?
synchronized. 在Java中,synchronized关键字是用来控制线程同步的。
@author Hollis 18/08/04.
public class SynchronizedDemo {
public synchronized void doSth(){
System.out.println("Hello World");
}
public void doSth1(){
synchronized (SynchronizedDemo.class){
System.out.println("Hello World");
}
}
}
BIO、NIO和AIO的区别、三种IO的用法与原理
IO的方式通常分为几种,同步阻塞的BIO、同步非阻塞的NIO、异步非阻塞的AIO。
1、BIO
在JDK1.4出来之前,我们建立网络连接的时候采用BIO模式,需要先在服务端启动一个ServerSocket,然后在客户端启动Socket来对服务端进行通信,默认情况下服务端需要对每个请求建立一堆线程等待请求,而客户端发送请求后,先咨询服务端是否有线程相应,如果没有则会一直等待或者遭到拒绝请求,如果有的话,客户端会线程会等待请求结束后才继续执行。
2、NIO
NIO本身是基于事件驱动思想来完成的,其主要想解决的是BIO的大并发问题:在使用同步I/O的网络应用中,如果要同时处理多个客户端请求,或是在客户端要同时和多个服务器进行通讯,就必须使用多线程来处理。也就是说,将每一个客户端请求分配给一个线程来单独处理。这样做虽然可以达到我们的要求,但同时又会带来另外一个问题。由于每创建一个线程,就要为这个线程分配一定的内存空间(也叫工作存储器),而且操作系统本身也对线程的总数有一定的限制。如果客户端的请求过多,服务端程序可能会因为不堪重负而拒绝客户端的请求,甚至服务器可能会因此而瘫痪。
NIO基于Reactor,当socket有流可读或可写入socket时,操作系统会相应的通知引用程序进行处理,应用再将流读取到缓冲区或写入操作系统。也就是说,这个时候,已经不是一个连接就要对应一个处理线程了,而是有效的请求,对应一个线程,当连接没有数据时,是没有工作线程来处理的。
BIO与NIO一个比较重要的不同,是我们使用BIO的时候往往会引入多线程,每个连接一个单独的线程;而NIO则是使用单线程或者只使用少量的多线程,每个连接共用一个线程。
NIO的最重要的地方是当一个连接创建后,不需要对应一个线程,这个连接会被注册到多路复用器上面,所以所有的连接只需要一个线程就可以搞定,当这个过程中的多路复用器进行轮询的时候,发现连接上有请求的话,才开启一个线程进行处理,也就是一个请求一个线程模式。
在NIO的处理方式中,当一个请求来的话,开启线程进行处理,可能会等待后端应用的资源(JDBC连接等),其实这个线程就被阻塞了,当并发上来的话,还是会有BIO一样的问题。
HTTP/1.1出现后,有了Http长连接,这样除了超时和指明特定关闭的http header外,这个链接是一直打开的状态的,这样在NIO处理中可以进一步的进化,在后端资源中可以实现资源池或者队列,当请求来的话,开启的线程把请求和请求数据传送给后端资源池或者队列里面就返回,并且在全局的地方保持住这个现场(哪个连接的哪个请求等),这样前面的线程还是可以去接受其他的请求,而后端的应用的处理只需要执行队列里面的就可以了,这样请求处理和后端应用是异步的.当后端处理完,到全局地方得到现场,产生响应,这个就实现了异步处理。
3、AIO
与NIO不同,当进行读写操作时,只须直接调用API的read或write方法即可。这两种方法均为异步的,对于读操作而言,当有流可读取时,操作系统会将可读的流传入read方法的缓冲区,并通知应用程序;对于写操作而言,当操作系统将write方法传递的流写入完毕时,操作系统主动通知应用程序。即可以理解为,read/write方法都是异步的,完成后会主动调用回调函数。在JDK1.7中,这部分内容被称作NIO.2,主要在java.nio.channels包下增加了下面四个异步通道:
AsynchronousSocketChannel AsynchronousServerSocketChannel AsynchronousFileChannel AsynchronousDatagramChannel 其中的read/write方法,会返回一个带回调函数的对象,当执行完读取/写入操作后,直接调用回调函数。
BIO是一个连接一个线程。
NIO是一个请求一个线程。
AIO是一个有效请求一个线程。
先来个例子理解一下概念,以银行取款为例:
同步 :自己亲自出马持银行卡到银行取钱(使用同步IO时,Java自己处理IO读写);异步 :委托一小弟拿银行卡到银行取钱,然后给你(使用异步IO时,Java将IO读写委托给OS处理,需要将数据缓冲区地址和大小传给OS(银行卡和密码),OS需要支持异步IO操作API);阻塞 :ATM排队取款,你只能等待(使用阻塞IO时,Java调用会一直阻塞到读写完成才返回);非阻塞 :柜台取款,取个号,然后坐在椅子上做其它事,等号广播会通知你办理,没到号你就不能去,你可以不断问大堂经理排到了没有,大堂经理如果说还没到你就不能去(使用非阻塞IO时,如果不能读写Java调用会马上返回,当IO事件分发器会通知可读写时再继续进行读写,不断循环直到读写完成)。
ConcurrentSkipListMap
ConcurrentSkipListMap是线程安全的有序的哈希表,适用于高并发的场景。
String.valueOf和Integer.toString的区别
String.valueOf 当值为null 返回字符null 否则调起tostring。
Integer的缓存机制
* Returns an {@code Integer} instance representing the specified
* {@code int} value. If a new {@code Integer} instance is not
* required, this method should generally be used in preference to
* the constructor {@link #Integer(int)}, as this method is likely
* to yield significantly better space and time performance by
* caching frequently requested values.
*
* This method will always cache values in the range -128 to 127,
* inclusive, and may cache other values outside of this range.
*
* @param i an {@code int} value.
* @return an {@code Integer} instance representing {@code i}.
* @since 1.5
*/
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
static final int low = -128;
static final int high;
static final Integer cache[];
```
Integer会有个常量数组,缓存 -128 到 127的值。与此类似的还有Long。
#Set如何保证元素不重复?
HashSet底层使用的是hashMap的key来保证元素不重复的,hashMap中的key通过equals方法比较key是否相等。
#Java中如何保证线程安全?
单体服务线程安全可使用Synchronized和ReentrantLock锁住相关代码,锁对象或是块必须是多线程共享的。
unsafe提供的CAS或是手动定义的乐观锁在并发较小但是有并发可能的情况下可以使用。
确保共享变量和对象的访问是串行的,或整体操作是原子的(例如不并发使用SimpleDataFormat);并发的数据修改要选择使用线程安全的静态类,如JUC包下的ConsurrentHashMap等。
>更多文章和资料 ↓↓↓
> 阿里云K8s实战手册 [ K8s](https://mp.weixin.qq.com/s/y3fS1tGCWRWSHWq6Ps78hg)
> 阿里云CDN排坑指南 [ CDN](https://mp.weixin.qq.com/s/tedaOubuMo6MjI9d5raclA)
> ECS运维指南 [ ECS](https://mp.weixin.qq.com/s/HbvVUnWJxi4ioZGw8l3jIw)
> DevOps实践手册 [ DevOps](https://mp.weixin.qq.com/s/GuGgx4NhHtGPaXmfF8Derg)
> Hadoop大数据实战手册 [ Hadoop](https://mp.weixin.qq.com/s/f7TZQYKeY4_s35ElCrSHFA)