本文是Android面试题整理中的一篇,结合右下角目录食用更佳,包括:html
线程是操做系统可以进行调度的最小单位,它被包含在进程之中,是进程中的实际运做单位,可使用多线程对进行运算提速。java
- 一种是继承Thread类;
- 另外一种是实现Runnable接口。两种方式都要经过重写run()方法来定义线程的行为,推荐使用后者,由于Java中的继承是单继承,一个类有一个父类,若是继承了Thread类就没法再继承其余类了,显然使用Runnable接口更为灵活。
- 实现Callable接口,该接口中的call方法能够在线程执行结束时产生一个返回值
FutureTask实现了Future接口和Runnable接口,能够对任务进行取消和获取返回值等操做。android
作不到,和gc同样,只能通知系统,具体什么时候启动有系统控制git
启动一个线程是调用start()方法,使线程所表明的虚拟处理机处于可运行状态,这意味着它能够由JVM 调度并执行,这并不意味着线程就会当即运行程序员
- wait( ):Object方法,必须在同步代码块或同步方法中使用,使当前线程处于等待状态,释放锁
- notify ( ):Object方法,和wait方法联合使用,通知一个线程,具体通知哪一个由jvm决定,使用不当可能发生死锁
- notifyAll ( ):Object方法,和wait方法联合使用,通知全部线程,具体哪一个线程得到运行权jvm决定
- sleep( ):使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要处理InterruptedException异常
- Synchronized修饰方法
- Synchronized修饰代码块
- Lock/ReadWriteLock
- ThreadLocal:每一个线程都有一个局部变量的副本,互不干扰。一种以空间换时间的方式
- java中有不少线程安全的容器和方法,能够帮助咱们实现线程同步:如Collections.synchronizedList()方法将List转为线程同步;用ConurrentHashMap 实现hashmap的线程同步。BlockingQueue阻塞队列也是线程同步的,很是适用于生产者消费者模式
- 扩展:volatile(volatile修饰的变量不会缓存在寄存器中,每次使用都会从主存中读取):保证可见性,不保证原子性,所以不是线程安全。在一写多读/状态标志的场景中使用
所谓重入锁,指的是以线程为单位,当一个线程获取对象锁以后,这个线程能够再次获取本对象上的锁,而其余的线程是不能够的github
- Java提供了很丰富的API但没有为中止线程提供API
- 能够用volatile 布尔变量来退出run()方法的循环或者是取消任务来中断线程
- 若是异常没有被捕获该线程将会中止执行
- 能够用UncaughtExceptionHandler来捕获这种异常
- 使用同一个runnable对象
- 使用不一样的runnable对象,将同一共享数据实例传给不一样的runnable
- 使用不一样的runnable对象,将这些Runnable对象做为一个内部类,将共享数据做为成员变量
- 给线程起个有意义的名字
- 避免使用锁和缩小锁的范围
- 多用同步辅助类(CountDownLatch、CyclicBarrier、Semaphore)少用wait、notify
- 多用并发集合少用同步集合
- 供线程内的局部变量,线程独有,不与其余线程共享
- 适用场景:多线程状况下某一变量不须要线程间共享,须要各个线程间相互独立
- ThreadLocal经过得到Thread实例内部的ThreadLocalMap来存取数据
- ThreadLocal实例自己做为key值
- 若是使用线程池,Threadlocal多是上一个线程的值,须要咱们显示的控制
- ThreadLocal的key虽然采用弱引用,可是仍然可能形成内存泄漏(key为null,value还有值)
扩展:Android中的ThreadLocal实现略有不一样,使用Thread实例中的是数组存值,经过ThreadLocal实例计算一个惟一的hash肯定下标。
- 线程内的异常能够捕获,若是没有捕获,该线程会中止运行退出
- 不管是正常退出仍是异常退出,同步块中的锁都会释放
两个线程互相等待对方释放资源才能继续执行下去,这个时候就造成了死锁,谁都没法继续执行(或者多个线程循环等待)面试
以一样的顺序加锁和释放锁编程
处于等待状态的线程可能会收到错误警报和伪唤醒,若是不在循环中检查等待条件,程序就会在没有知足结束条件的状况下退出数组
- 同步集合与并发集合都为多线程和并发提供了合适的线程安全的集合
- 并发集合性能更高
这是上题的扩展,活锁和死锁相似,不一样之处在于处于活锁的线程或进程的状态是不断改变的,活锁能够认为是一种特殊的饥饿。一个现实的活锁例子是两个 人在狭小的走廊碰到,两我的都试着避让对方好让彼此经过,可是由于避让的方向都同样致使最后谁都不能经过走廊。简单的说就是,活锁和死锁的主要区别是前者进程的状态能够改变可是却不能继续执行缓存
java.lang.Thread中有一个方法叫holdsLock(),它返回true若是当且仅当当前线程拥有某个具体对象的锁
ConcurrentHashMap把实际map划分红若干部分来实现它的可扩展性和线程安全。这种划分是使用并发度得到的,它是 ConcurrentHashMap类构造函数的一个可选参数,默认值为16,这样在多线程状况下就能避免争用
阻塞式方法是指程序会一直等待该方法完成期间不作其余事情,ServerSocket的accept()方法就是一直等待客户端链接。这里的阻塞是 指调用结果返回以前,当前线程会被挂起,直到获得结果以后才会返回。此外,还有异步和非阻塞式方法在任务完成前就返回。
忙循环就是程序员用循环让一个线程等待,不像传统方法wait(), sleep() 或 yield() 它们都放弃了CPU控制,而忙循环不会放弃CPU,它就是在运行一个空循环。这么作的目的是为了保留CPU缓存,在多核系统中,一个等待线程醒来的时候可 能会在另外一个内核运行,这样会重建缓存。为了不重建缓存和减小等待重建的时间就可使用它了。
可使用synchronized保证原子性,也可使用AtomicInteger类
扩展:volatile只能保证可见性,不能保证原子性,所以不行
Java中能够对类、对象、方法或是代码块上锁
- 同步代码块能够指定更小的粒度
- 同步代码块能够给指定实例加锁
类锁其实时一种特殊的对象锁,它锁的其实时类对应的class对象
- 两个方法都是暂停线程,释放cpu资源给其余线程
- sleep是Thread的静态方法,wait是Object的方法。
- sleep使线程进入阻塞状态;wait使线程进入等待状态,靠其余线程notify或者notifyAll来改变状态
- sleep能够在任何地方使用,必须捕获异常;而wait必须在同步方法或者同步块中使用,不然会抛出运行时异常
- 最重要的:sleep继续持用锁,wait释放锁 扩展:yield中止当前线程,让同优先级或者优先级高的线程先执行(但不会释放锁);join方法在某一个线程的执行过程当中调用另外一个线程执行,等到被调用的线程执行结束后,再继续执行当前线程
- sleep方法使当前线程阻塞指定时间,随后进入就绪状态
- yield方法使当前线程进入就绪状态,让同优先级或者更高优先级的线程先执行
- sleep方法会抛出interruptedException
JAVA提供的锁是对象级的而不是线程级的,每一个对象都有锁,通 过线程得到。若是线程须要等待某些锁那么调用对象中的wait()方法就有意义了。若是wait()方法定义在Thread类中,线程正在等待的是哪一个锁 就不明显了
- java规定必须在同步块中,不在同步块中会抛出异常
- 若是不在同步块中,有可能notify在执行的时候,wait没有收到陷入死锁
synchronized 用于线程同步
- 能够修饰方法
- 能够修饰代码块
- 当持有的锁是类时,那么全部实例对象调用该方法或者代码块都会被锁
- synchronized修饰静态方法时,锁是类,全部的对象实例用同一把锁
- 修饰普通方法时,锁是类的实例
不能。其它线程只能访问该对象的非同步方法。第一个线程持有了对象锁,第二个线程的同步方法也须要该对象的锁才能运行,只能在锁池中等待了。
- volatile是一个修饰符,只能修饰成员变量
- volatile保证了变量的可见性(A线程的改变,B线程立刻能够获取到)
- volatile禁止进行指令重排序
private static volatile Singleton instance;
private Singleton(){}
public Singleton getInstance(
if(singleton == null){
synchronized(Singleton.class){
if(singleton == null){
singleton = new Singleton();
}
}
}
return sinlgeton;
)
复制代码
- 要加
- 两个线程同时访问双检锁,有可能指令重排序,线程1初始化一半,切换到线程2;由于初始化不是一个原子操做,此时线程2读到不为null直接使用,可是由于尚未初始化完成引发崩溃
- Synchronized时java关键字,Lock/ReadWriteLock接口,它们都是可重入锁
- Synchronized由虚拟机控制,不须要用户去手动释放锁,执行完毕后自动释放;而Lock是用户显示控制的,要用户去手动释放锁,若是没有主动释放锁,就有可能致使出现死锁现象。
- Lock能够用更多的方法,好比tryLock()拿到锁返回true,不然false;tryLock(long time, TimeUnit unit)方法和tryLock()方法是相似的,只不过区别在于这个方法在拿不到锁时会等待必定的时间;Lock有lockInterruptibly()方法,是可中断锁
- ReentrantLock能够实现公平锁(等得久的先执行)
- ReadWriteLock是一个接口,ReentrantReadWriteLock是它的一个实现,将对一个资源(好比文件)的访问分红了2个锁,一个读锁和一个写锁,提升了读写效率。
LockSupport是JDK中比较底层的类,用来建立锁和其余同步工具类的基本线程阻塞原语
park 方法获取许可。许可默认是被占用的,调用park()时获取不到许可,因此进入阻塞状态 unpark 方法颁发许可
- 读写分离的锁,能够提高效率
- 读读能共存,读写、写写不能共存
- RetrantLock 是经过CAS和AQS实现的
- CAS(Compare And Swap):三个参数,一个当前内存值V、旧的预期值A、即将更新的值B,当且仅当预期值A和内存值V相同时,将内存值修改成B并返回true,不然什么都不作,并返回false。原子性操做
- RetrantLock内部有一个AbstractQueuedSynchronizer实例,AbstractQueuedSynchronizer是一个抽象类,RetrantLock中有两种对他的实现,一种是公平锁,一种是非公平锁
- 在lock时,调用一个CAS的方法compareAndSet来将state设置为1,state是一个volitale的变量,并将当前线程和锁绑定
- 当compareAndSet失败时,尝试获取锁:若是和锁绑定的线程时当前线程,state+1
- 若是获取锁失败,将其加入到队列中等待,从而保证了并发执行的操做变成了串行
- 扩展:公平锁和非公平锁的区别:非公平锁无视队列,直接查看当前可不能够拿到锁;公平锁会先查看队列,队列非空的话会加入队列
synchronized 的实现原理以及锁优化?:Monitor
volatile 的实现原理?:内存屏障
CAS?CAS 有什么缺陷,如何解决?CompareAndSwap,经过cpu指令实现的
AQS :AbstractQueueSynchronizer,是ReentrantLock一个内部类
如何检测死锁?怎么预防死锁?:死锁必须知足四个条件,破坏任意一个条件均可以解除死锁
Fork/Join框架
- 频繁的建立和销毁对象很耗费资源,因此java引入了线程池。Java 5+中的Executor接口定义一个执行线程的工具。它的子类型即线程池接口是ExecutorService。
- Executors 是一个工具类,能够帮咱们生成一些特性的线程池
newSingleThreadExecutor:建立一个单线程化的Executor,保证全部任务按照指定顺序(FIFO, LIFO, 优先级)执行。
newFixedThreadPool:建立一个指定工做线程数量的线程池。每当提交一个任务就建立一个工做线程,若是工做线程数量达到线程池初始的最大数,则将提交的任务存入到池队列中。
newCachedThreadPool:建立一个可缓存线程池,若是线程池长度超过处理须要,可灵活回收空闲线程,若无可回收,则新建线程。
newScheduleThreadPool:建立一个定长的线程池,并且支持定时的以及周期性的任务执行,支持定时及周期性任务执行。
复制代码
- 咱们经常使用的ThreadPoolExecutor实现了ExecutorService接口,如下是原理和参数说明
原理:
step1.调用ThreadPoolExecutor的execute提交线程,首先检查CorePool,若是CorePool内的线程小于CorePoolSize,新建立线程执行任务。
step2.若是当前CorePool内的线程大于等于CorePoolSize,那么将线程加入到BlockingQueue。
step3.若是不能加入BlockingQueue,在小于MaxPoolSize的状况下建立线程执行任务。
step4.若是线程数大于等于MaxPoolSize,那么执行拒绝策略。
参数说明:
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)
corePoolSize 核心线程池大小
maximumPoolSize 线程池最大容量大小
keepAliveTime 线程池空闲时,线程存活的时间
TimeUnit 时间单位
ThreadFactory 线程工厂
BlockingQueue任务队列
RejectedExecutionHandler 线程拒绝策略
扩展:ThreadPoolExecutor 的submit和excute方法都能执行任务,有什么区别?
1. 入参不一样:excute只能接受Runnable,submit能够接受Runnable和Callable
2. submit有返回值
3. 在异常处理时,submit能够经过Future.get捕获抛出的异常
复制代码
1.若是还没达到最大线程数,则新建线程 2.若是已经达到最大线程数,交给RejectExecutionHandler处理。 3.若是没有设置自定义RejectExecutionHandler,则抛出RejectExecutionExcuption
优点: 实现对线程的复用,避免了反复建立及销毁线程的开销;使用线程池统一管理线程能够减小并发线程的数目,而线程数过多每每会在线程上下文切换上以及线程同步上浪费过多时间。
用法: 咱们能够调用ThreadPoolExecutor的某个构造方法来本身建立一个线程池。但一般状况下咱们可使用Executors类提供给咱们的静态工厂方法来更方便的建立一个线程池对象。建立了线程池对象后,咱们就能够调用submit或者excute方法提交任务到线程池中去执行了;线程池使用完毕后咱们要记得调用shutdown方法来关闭它。
- CountDownLatch:利用它能够实现相似计数器的功能。好比有一个任务A,它要等待其余4个任务执行完毕以后才能执行,此时就能够利用CountDownLatch来实现这种功能了
public class Test {
public static void main(String[] args) {
final CountDownLatch latch = new CountDownLatch(2);
new Thread(){
public void run() {
try {
System.out.println("子线程"+Thread.currentThread().getName()+"正在执行");
Thread.sleep(3000);
System.out.println("子线程"+Thread.currentThread().getName()+"执行完毕");
latch.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
};
}.start();
new Thread(){
public void run() {
try {
System.out.println("子线程"+Thread.currentThread().getName()+"正在执行");
Thread.sleep(3000);
System.out.println("子线程"+Thread.currentThread().getName()+"执行完毕");
latch.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
};
}.start();
try {
System.out.println("等待2个子线程执行完毕...");
latch.await();
System.out.println("2个子线程已经执行完毕");
System.out.println("继续执行主线程");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
复制代码
- CyclicBarrier: 实现让一组线程等待至某个状态以后再所有同时执行
public class Test {
public static void main(String[] args) {
int N = 4;
CyclicBarrier barrier = new CyclicBarrier(N);
for(int i=0;i<N;i++)
new Writer(barrier).start();
}
static class Writer extends Thread{
private CyclicBarrier cyclicBarrier;
public Writer(CyclicBarrier cyclicBarrier) {
this.cyclicBarrier = cyclicBarrier;
}
@Override
public void run() {
System.out.println("线程"+Thread.currentThread().getName()+"正在写入数据...");
try {
Thread.sleep(5000); //以睡眠来模拟写入数据操做
System.out.println("线程"+Thread.currentThread().getName()+"写入数据完毕,等待其余线程写入完毕");
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
}catch(BrokenBarrierException e){
e.printStackTrace();
}
System.out.println("全部线程写入完毕,继续处理其余任务...");
}
}
}
扩展(CyclicBarrier和CountdownLatch的区别):1.CountdownLatch等待几个任务执行完毕,CyclicBarrier等待达到某个状态;2.CyclicBarrier能够调用reset,循环使用;3.CyclicBarrier能够有含Runnable的构造方法,当达到某一状态时执行某一任务。
复制代码
- Semaphore:Semaphore能够控同时访问的某个资源的线程个数
public class Test {
public static void main(String[] args) {
int N = 8; //工人数
Semaphore semaphore = new Semaphore(5); //机器数目
for(int i=0;i<N;i++)
new Worker(i,semaphore).start();
}
static class Worker extends Thread{
private int num;
private Semaphore semaphore;
public Worker(int num,Semaphore semaphore){
this.num = num;
this.semaphore = semaphore;
}
@Override
public void run() {
try {
semaphore.acquire();
System.out.println("工人"+this.num+"占用一个机器在生产...");
Thread.sleep(2000);
System.out.println("工人"+this.num+"释放出机器");
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
复制代码
- Semaphore能够控制当前资源被访问的线程个数,超过最大个数后线程处于阻塞等待状态
- 当线程个数指定为1时,能够当锁使用
全部线程须要阻塞等待,而且观察到事件状态改变知足条件时自动执行,能够用如下方法实现
- 闭锁CountDownLatch:闭锁是典型的等待事件发生的同步工具类,将闭锁的初始值设置1,全部线程调用await方法等待,当事件发生时调用countDown将闭锁值减为0,则全部await等待闭锁的线程得以继续执行。
- 阻塞队列BlockingQueue:全部等待事件的线程尝试从空的阻塞队列获取元素,将阻塞,当事件发生时,向阻塞队列中同时放入N个元素(N的值与等待的线程数相同),则全部等待的线程从阻塞队列中取出元素后得以继续执行。
- 信号量Semaphore:设置信号量的初始值为等待的线程数N,一开始将信号量申请完,让剩余的信号量为0,待事件发生时,同时释放N个占用的信号量,则等待信号量的全部线程将获取信号量得以继续执行。
- 扩展:经过sychronized关键字实现
- 阻塞队列的特征是当取或放元素是,队列不知足条件(好比队列为空时进行取操做)能够阻塞等待,知道知足条件
public class BlockingQueueTest {
private int size = 20;
private ArrayBlockingQueue<Integer> queue = new ArrayBlockingQueue<>(size);
public static void main(String[] args) {
BlockingQueueTest test = new BlockingQueueTest();
Producer producer = test.new Producer();
Consumer consumer = test.new Consumer();
producer.start();
consumer.start();
}
class Consumer extends Thread{
@Override public void run() {
while(true){
try {
//从阻塞队列中取出一个元素
queue.take();
System.out.println("队列剩余" + queue.size() + "个元素");
} catch (InterruptedException e) {
} } }
}
class Producer extends Thread{
@Override public void run() {
while (true) {
try {
//向阻塞队列中插入一个元素
queue.put(1);
System.out.println("队列剩余空间:" + (size - queue.size()));
} catch (InterruptedException e) {} }}
}
}
复制代码
- ArrayBlockingQueue:一个基于数组实现的阻塞队列,它在构造时须要指定容量。当试图向满队列中添加元素或者从空队列中移除元素时,当前线程会被阻塞。
- CountDownLatch:同步计数器,是一个线程工具类,可让一个或几个线程等待其余线程
Condition是一个接口,有await和signal方法,和Object的wait、notify相似 Condition 经过lock得到:Condition condition = lock.newCondition(); 相对于Object的wait、notify,Condition的控制更加灵活,能够知足唤起某一线程的目的
- 就绪状态:得到CPU调度时由 就绪状态 转换为 运行状态
- 运行状态:CPU时间片用完了由 运行状态 转换为 就绪状态 运行状态
- 阻塞状态:因等待某个事件发生而进入 阻塞状态,事件发生后由 阻塞状态 转换为 就绪状态
- 互斥:两个进程因为不能同时使用同一临界资源,只能在一个进程使用完了,另外一进程才能使用,这种现象称为进程间的互斥。
- 对于互斥的资源,A进程到达了该点后,若此时B进程正在对此资源进行操做,则A停下来,等待这些操做的完成再继续操做。这就是进程间的同步
- 互斥:一个资源一次只能被一个进程所使用,便是排它性使用
- 不剥夺条件:一个资源仅能被占有它的进程所释放,而不能被别的进程强占
- 请求与保持条件:进程已经保持了至少一个资源,但又提出了新的资源要求,而该资源又已被其它进程占有,此时请求进程阻塞,但又对已经得到的其它资源保持不放
- 环路等待条件:当每类资源只有一个时,在发生死锁时,必然存在一个进程—资源的环形链
类加载器的做用是根据指定全限定名称将class文件加载到JVM内存中,并转为Class对象。
- 启动类加载器(根加载器 Bootstrap ClassLoader):由native代码实现,负责将存放在<JAVA_HOME>\lib目录或-Xbootclasspath参数指定的路径中的类库加载到内存中
- 扩展加载器(Extension ClassLoader):java语言实现,父加载器是Bootstrap,:负责加载<JAVA_HOME>\lib\ext目录或java.ext.dirs系统变量指定的路径中的全部类库。
- 应用程序类加载器(Application ClassLoader):java实现,负责加载用户类路径(classpath)上的指定类库,咱们能够直接使用这个类加载器。通常状况,若是咱们没有自定义类加载器默认就是用这个加载器。
- 自定义类加载器:有时为了安全会将类加密,或者从远程(服务器)加载类 ,这个时候就须要自定义类加载器。自定义经过继承ClassLoader类实现,loadClass方法已经实现了双亲委派模式,当父类没有加载成功时,调用当前类的findclass方法,因此咱们通常重写该方法。
- 类加载器采用双亲委派模型进行加载:每次经过先委托父类加载器加载,当父类加载器没法加载时,再本身加载。
- 类的生命周期能够分为七个阶段:加载 -> 链接(验证 -> 准备*(为静态变量分配内存并设置默认的初始值)* -> 解析*(将符号引用替换为直接引用)*)-> 初始化 -> 使用 -> 卸载
- 使用双亲委派模式,保证只加载一次该类
- 咱们可使用自定义的类加载器加载同名类,这样就阻止了系统双亲委派模式的加载
- JVM 及 Dalvik 对类惟一的识别是 ClassLoader id + PackageName + ClassName
- 两个相同的类可能由于两个ClassLoader加载而不兼容
- 经过类的class对象类得到类的各类信息,建立对应的对象或者调用方法
- App的动态加载或者Android中调用其余对象private方法,都须要反射
- String.class:不执行静态块和动态构造块
- "hello".getClass();:执行静态块和动态构造块
- Class.forName("java.lang.String");:执行静态块,不执行动态构造块
- String.class.newInstance();
- String.class.getConstrutor(Stirng.class).newInstance("hello word");
- 经过类对象的getDeclaredField()方法得到(Field)对象
- 调用Field对象的setAccessible(true)方法将其设置为可访问
- 经过get/set方法来获取/设置字段的值
- 经过类对象的getMethod方法得到Method对象
- 调用对象的invoke()方法
- 范型能够用于类定义和方法定义
- 范型的实现是经过擦除实现的,也就是说编译以后范型信息会被擦出
- 通配符有两种用法:?extends A 和 ? super A
- ?extends A 表示?的上界是A,具体什么类型并不清楚,适合于获取,获取到的必定是A类型
- ? super A 表示?的下界是A,具体什么类型并不清楚,适合于插入,必定能够插入A类型
注解分为三种:源码级别(source),类文件级别(class)或者运行时级别(runtime);butternife是类文件级别 参考:https://blog.csdn.net/javazejian/article/details/71860633 https://blog.csdn.net/u013045971/article/details/53509237
https://www.cnblogs.com/likeshu/p/5526187.html
http://www.jcodecraeer.com/a/chengxusheji/java/2015/0206/2421.html