1、string为什么不可继承?
答:因为string是用final修饰的不可变类,不可以被继承;
为什么要用final修饰?
答:安全,设计者将string设为共享的,子类不可以修改,因此设置为final;
效率,共享的效率更高;
String、StringBuffer和StringBuilder区别
答:运行速度StringBuilder>StringBuffer>String,String为字符串常量,一旦创建对象后不可更改;而后者是可更改的;
线程安全上StringBuffer是线程安全的,StringBuilder是非线程安全的。
2、怎样减少线程的上下文切换?
答:减少并发线程数,尽量使用无锁编程,CAS算法
什么是CAS算法?
答:compare and swap意思是先比较旧的预期值和原来内存中存储的值相等,再更改为目标值,是一种乐观锁,适用于读多写少的情况,效率较高,只有极少的情况会发生冲突
CAS会有ABA问题应怎么解决?
答:ABA问题是指旧的预期值被更改为和原来内存中存储的值相等,已经发生过一次更改,这种情况可以通过版本号(AtomicStampedReference)、时间戳来解决,比较最初获取的版本号和更改前获取的版本号是否一致,一致则更改,更改后都将版本号+1
3、说下volatile关键字的作用?
答:可见性:当多个线程共同访问同一变量,当其中一个线程修改了该变量,其他线程都能立刻看到修改的值
有序性:防止指令重排序,程序执行顺序按当前代码顺序执行
注:指令重排序不会影响单个线程的执行,但会影响多个线程并发执行的结果
https://www.cnblogs.com/dolphin0520/p/3920373.html
内存屏障会提供3个功能:
1)它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成;
2)它会强制将对缓存的修改操作立即写入主存;
3)如果是写操作,它会导致其他CPU中对应的缓存行无效。
volatile可以保证原子性吗?
答:不是,两个线程同时从内存中读取变量的值i=10到自己的工作内存,线程a和线程b同时做自增操作+1,写入工作内存,再写入主内存,两个线程分别自增一次,但主内存中的结果只增加1,因此volatile不保证原子性
4、描述下java内存模型
答:Java内存模型规定所有的变量都是存在主存当中,每个线程都有自己的工作内存。线程对变量的所有操作都必须在工作内存中进行,而不能直接对主存进行操作。并且每个线程不能访问其他线程的工作内存。
5、描述下happens-before原则(先行发生原则)
答:Java内存模型具备一些先天的“有序性”
- 程序次序规则:一个线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的操作
- 锁定规则:一个unLock操作先行发生于后面对同一个锁额lock操作
- volatile变量规则:对一个变量的写操作先行发生于后面对这个变量的读操作
- 传递规则:如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C
- 线程启动规则:Thread对象的start()方法先行发生于此线程的每个一个动作
- 线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生
- 线程终结规则:线程中所有的操作都先行发生于线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值手段检测到线程已经终止执行
- 对象终结规则:一个对象的初始化完成先行发生于他的finalize()方法的开始
6、ThreadLocal 原理
定义:线程局部变量,为每一个线程都提供了一份变量副本,每一个线程都可以随意修改自己的变量副本,而不会对其他线程产生影响;
实现:在ThreadLocal类中有一个静态内部类ThreadLocalMap(其类似于Map),用键值对的形式存储每一个线程的变量副本,ThreadLocalMap中元素的key为当前ThreadLocal对象,而value对应线程的变量副本,每个线程可能存在多个ThreadLocal。
应用:采用了“以空间换时间”的方式,解决多线程数据共享问题,实际过程中应用到了数据库连接。
问题:ThreadLocal有可能产生内存泄漏,因为map中的key弱引用指向ThreadLocal,当把threadlocal实例置为null以后,没有任何强引用指向threadlocal实例,所以threadlocal将会被gc回收. 但是,我们的value却不能回收,因为存在一条从current thread连接过来的强引用。
7、synchronized锁怎么实现的?
答:synchronized锁存在Java对象头里,每一个被锁住的对象都会和一个monitor对象关联;
执行monitorenter获取锁,读取monitor对象的count字段,count==0,则获取锁成功且count+1;
执行monitorexit指令时将count-1,若count==0,则释放锁;
synchronized为什么是重量级锁?
答:重量级锁通过对象内部的监视器(monitor)实现,其中monitor的本质是依赖于底层操作系统的Mutex Lock实现,操作系统实现线程之间的切换需要从用户态到内核态的切换,切换成本非常高。
8、Lock锁怎么实现的?
ReentrantLock是基于AQS实现的,AQS的基础是CAS
AbstractQueuedSynchronizer,简称AQS,基于volatile int state +FIFO队列(双端队列)实现的,
ReentrantLock中有一个抽象类Sync,根据传入构造方法的布尔型参数实例化出Sync的实现类FairSync和NonfairSync,并重写tryAcquire(int arg)和tryRelease(int arg)两个方法。
lock()利用CAS去判断state是不是0,如果state=0,则获取锁,state设为1并将当前线程设为主;
如果state非0,执行acquire()->tryAcquire()->(非公平锁)如果state=0,CAS尝试获取锁,若加锁成功state设为1并将当前线程设为主,
state非0,判断当前owner是否是当前线程,如果是,state+1,返回true,如果不属于当前线程,返回false,则添加FIFO等待队列队尾。
(公平锁)如果state=0,hasQueuedPredecessors()判断当前是否有等待的线程, 如果没有使用CAS尝试获取锁,若加锁成功state设为1并将当前线程设为主,
state非0,判断当前owner是否是当前线程,如果是,state+1,返回true,如果不属于当前线程,返回false,则添加FIFO等待队列队尾。
ReentrantLock获取锁定与三种方式:
a) lock(), 如果获取了锁立即返回,如果别的线程持有锁,当前线程则一直处于休眠状态,直到获取锁;
b) tryLock(), 如果获取了锁立即返回true,如果别的线程正持有锁,立即返回false;
c)tryLock(long timeout,TimeUnit unit),如果获取了锁定立即返回true,如果别的线程正持有锁,会等待参数给定的时间,在等待的过程中,如果获取了锁定,就返回true,如果等待超时,返回false;
d)lockInterruptibly():如果获取了锁定立即返回,如果没有获取锁,当前线程处于休眠状态,直到获取锁,或者当前线程被别的线程中断;
9、synchronized和Lock什么区别?
类别 | synchronized | Lock |
---|---|---|
存在层次 | Java的关键字,在jvm层面上 | 通过代码实现,是一个类 |
锁的释放 | 会自动释放锁定(代码执行完成或者出现异常) | 不会主动释放,必须将unLock()放到finally{}中 |
锁的获取 | 假设A线程获得锁,B线程等待。如果A线程阻塞,B线程会一直等待 | 分情况而定,Lock有多个锁获取的方式,具体下面会说道,大致就是可以尝试获得锁,线程可以不用一直等待 |
锁状态 | 无法判断 | 可以判断 |
锁类型 | 可重入,不可中断,非公平 | 可重入,可中断,可公平(两者皆可) |
性能 | 少量同步 | 大量同步 |
10、线程池有哪几种?
答:Java通过Executors提供四种线程池,分别为:
1、newCachedThreadPool:创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。(线程最大并发数不可控制)SynchronousQueue
2、newFixedThreadPool:创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。LinkedBlockingQueue
3、newScheduledThreadPool:创建一个定长线程池,支持定时及周期性任务执行。
4、newSingleThreadExecutor:创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。LinkedBlockingQueue
线程池参数有哪些?
答:ThreadPoolExecutor构造方法
public ThreadPoolExecutor(int corePoolSize,//核心线程池大小
int maximumPoolSize,//最大线程池大小
long keepAliveTime,//线程池中超过corePoolSize数目的空闲线程最大存活时间;可以allowCoreThreadTimeOut(true)成为核心线程的有效时间
TimeUnit unit,//keepAliveTime的时间单位
BlockingQueue<Runnable> workQueue,//阻塞任务队列
ThreadFactory threadFactory,//线程工厂
RejectedExecutionHandler handler) {//当提交任务数超过maxmumPoolSize+workQueue之和时,任务会交给RejectedExecutionHandler来处理
线程池的工作过程?
答:1.当线程池小于corePoolSize时,新提交任务将创建一个新线程执行任务,即使此时线程池中存在空闲线程;
2.当线程池达到corePoolSize时,新提交任务将被放入workQueue中,等待线程池中任务调度执行;
3.当workQueue已满,且当前线程数大于corePoolSize且小于maximumPoolSize时,新提交任务会创建新线程执行任务;
4.当提交任务数超过maximumPoolSize时,新提交任务由RejectedExecutionHandler处理;
5.当线程池中超过corePoolSize线程,空闲时间达到keepAliveTime时,关闭空闲线程;
6.当设置allowCoreThreadTimeOut(true)时,线程池中corePoolSize线程空闲时间达到keepAliveTime也将关闭;
线程池拒绝策略有几种?
答:ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务
线程池相关的几个队列:
答:ArrayBlockingQueue, 一个由数组结构组成的有界阻塞队列fifo;默认情况下不保证公平阻塞,可以通过配置创建公平阻塞的队列,实现原理是可重入锁。
LinkedBlockingQueue,一个由链表结构组成的有界阻塞队列 fifo,LinkedBlockingQueue 可无界,也可以有界,初始化指定长度则为有界,不指定则为无界
PriorityBlockingQueue,一个支持优先级排序的无界阻塞队列
synchronousQueue,一个不存储元素的阻塞队列
11、线程状态有几种?
答:线程的生命周期为5个状态,新建状态、就绪状态(start)、运行状态(run)、阻塞状态和死亡状态。
运行的线程执行sleep线程进入阻塞状态,sleep时间到了以后进入就绪状态。
线程的实现由3种方法:继承Tread类;实现Runnable接口;实现Callable接口
Sleep和wait的区别:sleep属于Thread类,wait属于Object类,
sleep线程不会释放对象锁,wait会释放对象锁;
wait需要调用notify()方法后本线程才进入对象索定池(只能同步方法或同步代码块中使用),sleep结束后进入就绪状态。
notify()与notifyAll()的区别:notify()随机通知一个线程;
notifyAll()通知所有线程;
11、Java8新特性
答:(1) lambda表达式 (2) Stream,处理集合数据(3) 接口default方法,有利于数据移植
12、抽象类和接口
答:抽象类和抽象方法都需要被abstract修饰,抽象方法一定要定义在抽象类中。抽象类所有方法都是抽象方法的时候,完全可以用接口实现。抽象类为部分方法提供实现,避免了子类重复实现这些方法,提供了代码重用性。单继承,多实现。既要定义子类的行为,又要为子类提供共性功能时才选用抽象类。
13、内部类
静态内部类:static修饰,可以访问外部类所有的静态变量和方法。应用场景如hashmap的静态内部类entry
成员内部类:可以访问外部类所有的变量和方法。和静态内部类不同,成员内部类的实例都依赖外部类的实例。
局部内部类:即定义在方法中的类,可以访问外部类的所有变量和方法,如果局部内部类用static修饰方法内,则只能访问外部类的static变量
匿名内部类:匿名内部类可以出现在任何允许表达式出现的地方,定义格式为new 类/接口。应用场景,分布式工具类,多线程,android中的绑定监听事件。
13、反射
反射是java在运行状态下,对于任意一个类,都能够知道这个类的所有属性和方法;
优点是可以动态创建对象和编译;缺点是对性能有影响。
获取方式有3种:类名.class,Class.forName(xxx); new 类名.getclass()。
反射的应用场景有:反编译、mybatis底层实现、工具类,bean转map,通过反射获取注解,可以做一些校验
14、java面向对象的三大特性
继承:是对有共同特性的多类事物,进行再抽象成一个类。java继承是extends关键字,子类中有和父类中可访问的同名同返回同参数列表的方法时,就会覆盖从父类继承来的方法
封装:隐藏对象的属性和实现细节,仅对外提供接口。java中的类属性访问权限不是private,要想隐藏该类方法需要用private修饰符
多态:两种机制:编译时多态和运行时多态.
方法重载,方法名相同,其他不同,编译时重载。方法重写(覆盖),子类覆盖父类的方法。
15、泛型
泛型只在编译阶段有效,在编译过程中,正确检测泛型结果后,会将泛型的相关信息擦除,并且添加类型检测和类型转换。
使用泛型的好处是:类型安全;消除强制类型转换,提高性能。
泛型的具体使用有3种方式:
(1) 泛型类,例如hashmap<K,V>,其中key和value只在运行时才会真正根据类型来构造和分配内存
(2) 泛型接口,当2个接口的处理方式一致,只是对象不同的时候,可以用泛型接口
(3) 泛型方法,把方法参数泛型话
16、join是怎么实现的?
join是thread类synchronized修饰的同步方法,其使用wait()方法实现等待;
17、异常
一、error
程序无法处理的错误,比较严重的问题,虚拟机错误,如OutOfMemoryError、NoClassDefFoundError
二、exception
1、已检查异常
编译器要求必须对代码try catch或者throw expetion,如IOException、SQLException、FileNotFoundException、ParseException
2、未检查异常(RuntimeException运行时异常)
编译时不检查,运行时可能发生的异常,如NullPointerException,IndexOutOfBoundsException
18、有return的情况下try catch finally的执行顺序:
(1)不管有没有出现异常,当执行到try内时,finally块中代码都会执行;
(2)当try和catch中有return时,finally仍然会执行;
(3)finally是在return后面的表达式运算后执行的(此时并没有返回运算后的值,而是先把要返回的值保存起来,不管finally中的代码怎么样,返回的值都不会改变(对象类型需要另外分析),仍然是之前保存的值),所以函数返回值是在finally执行前确定的;
(4)finally中最好不要包含return,否则程序会提前退出,返回值不是try或catch中保存的返回值。
19、对象的初始化顺序:
(1)类加载之后,按从上到下(从父类到子类)执行被static修饰的语句;
(2)当static语句执行完之后,再执行main方法;
(3)如果有语句new了自身的对象,将从上到下执行构造代码块、构造器(两者可以说绑定在一起)。
20、cookie与session
Web应用程序是使用HTTP协议传输数据的。HTTP协议是无状态的协议。一旦数据交换完毕,客户端与服务器端的连接就会关闭,再次交换数据需要建立新的连接。这就意味着服务器无法从连接上跟踪会话。要跟踪该会话,必须引入一种机制。
Cookie就是这样的一种机制。它可以弥补HTTP协议无状态的不足。在Session出现之前,基本上所有的网站都采用Cookie来跟踪会话。
Cookie实际上是一小段的文本信息。客户端请求服务器,如果服务器需要记录该用户状态,就使用response向客户端浏览器颁发一个Cookie。客户端浏览器会把Cookie保存起来。当浏览器再请求该网站时,浏览器把请求的网址连同该Cookie一同提交给服务器。服务器检查该Cookie,以此来辨认用户状态。
Cookie具有不可跨域名性。
ookie生命周期默认为浏览器会话期间,驻留内存,关闭浏览器cookie就没了
设置cookie的过期时间为永不过期,将cookie保存在硬盘上
首先浏览器请求服务器访问web站点时,程序需要为客户端的请求创建一个session的时候,服务器首先会检查这个客户端请求是否已经包含了一个session标识、称为SESSIONID,如果已经包含了一个sessionid则说明以前已经为此客户端创建过session,服务器就按照sessionid把这个session检索出来使用,如果客户端请求不包含session id,则服务器为此客户端创建一个session并且生成一个与此session相关联的session id,sessionid 的值应该是一个既不会重复,又不容易被找到规律以仿造的字符串,这个sessionid将在本次响应中返回到客户端保存,保存这个sessionid的方式就可以是cookie,这样在交互的过程中,浏览器可以自动的按照规则把这个标识发回给服务器,服务器根据这个sessionid就可以找得到对应的session,又回到了这段文字的开始
关闭浏览器不会导致session被删除,迫使服务器为seesion设置了一个失效时间,一般是30分钟
当浏览器将cookie禁用,基于cookie的session将不能正常工作,每次使用request.getSession() 都将创建一个新的session。达不到session共享数据的目的,但是我们知道原理,只需要将session id 传递给服务器session就可以正常工作的。
解决:通过URL将session id 传递给服务器:URL重写
21、通信方式
一、进程间的通信方式
# 管道( pipe ):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。
# 有名管道 (namedpipe) : 有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。
# 信号量(semophore ) : 信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
# 消息队列( messagequeue ) : 消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
# 信号 (sinal ) : 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。
#
共享内存(shared memory )
:共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC
方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量,配合使用,来实现进程间的同步和通信。
# 套接字(socket ) : 套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同及其间的进程通信。
二、线程间的通信方式
# 锁机制:包括互斥锁、条件变量、读写锁
*互斥锁提供了以排他方式防止数据结构被并发修改的方法。
*读写锁允许多个线程同时读共享数据,而对写操作是互斥的。
*条件变量可以以原子的方式阻塞进程,直到某个特定条件为真为止。对条件的测试是在互斥锁的保护下进行的。条件变量始终与互斥锁一起使用。
# 信号量机制(Semaphore):包括无名线程信号量和命名线程信号量
# 信号机制(Signal):类似进程间的信号处理
线程间的通信目的主要是用于线程同步,所以线程没有像进程通信中的用于数据交换的通信机制。
22、使用DateTimeFormatter
如果是Java8应用,可以使用DateTimeFormatter代替SimpleDateFormat,这是一个线程安全的格式化工具类。
23、BigDecimal 的等值比较应该要使用 compareTo() 方法,而不是 equals()方法。因为 equals() 会比较值和精度,而 compareTo() 会忽略精度