# Android之Java进阶

# 线程

# 1. 什么是线程

线程是操作系统能够进行调度的最小单位,它被包含在进程之中,是进程中的实际运作单位,可以使用多线程对进行运算提速。

# 2. 编写多线程的几种方式

  1. 一种是继承Thread类;
  2. 另一种是实现Runnable接口。两种方式都要通过重写run()方法来定义线程的行为,推荐使用后者,因为Java中的继承是单继承,一个类有一个父类,如果继承了Thread类就无法再继承其他类了,显然使用Runnable接口更为灵活。
  3. 实现Callable接口,该接口中的call方法可以在线程执行结束时产生一个返回值

更多参考

# 3. 什么是FutureTask

FutureTask实现了Future接口和Runnable接口,可以对任务进行取消和获取返回值等操作。

更多参考

# 4. 如何强制启动一个线程

做不到,和gc一样,只能通知系统,具体何时启动有系统控制

# 5. 启用一个线程是调用run()还是start()方法

启动一个线程是调用start()方法,使线程所代表的虚拟处理机处于可运行状态,这意味着它可以由JVM 调度并执行,这并不意味着线程就会立即运行

# 6. 说出线程调度和线程同步的方法

# 线程调度

  1. wait( ):Object方法,必须在同步代码块或同步方法中使用,使当前线程处于等待状态,释放锁
  2. notify ( ):Object方法,和wait方法联合使用,通知一个线程,具体通知哪个由jvm决定,使用不当可能发生死锁
  3. notifyAll ( ):Object方法,和wait方法联合使用,通知所有线程,具体哪个线程获得运行权jvm决定
  4. sleep( ):使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要处理InterruptedException异常

# 7. 线程同步

  1. Synchronized修饰方法
  2. Synchronized修饰代码块
  3. Lock/ReadWriteLock
  4. ThreadLocal:每个线程都有一个局部变量的副本,互不干扰。一种以空间换时间的方式
  5. java中有很多线程安全的容器和方法,可以帮助我们实现线程同步:如Collections.synchronizedList()方法将List转为线程同步;用ConurrentHashMap 实现hashmap的线程同步。BlockingQueue阻塞队列也是线程同步的,非常适用于生产者消费者模式
  6. 扩展:volatile(volatile修饰的变量不会缓存在寄存器中,每次使用都会从主存中读取):保证可见性,不保证原子性,因此不是线程安全。在一写多读/状态标志的场景中使用

# 8. 什么是可重入锁

所谓重入锁,指的是以线程为单位,当一个线程获取对象锁之后,这个线程可以再次获取本对象上的锁,而其他的线程是不可以的

更多参考

# 9. Java中如何停止一个线程

  1. Java提供了很丰富的API但没有为停止线程提供API
  2. 可以用volatile 布尔变量来退出run()方法的循环或者是取消任务来中断线程

# 10. 一个线程运行时发生异常会怎样

  1. 如果异常没有被捕获该线程将会停止执行
  2. 可以用UncaughtExceptionHandler来捕获这种异常

# 11. 多线程共享数据

  1. 使用同一个runnable对象
  2. 使用不同的runnable对象,将同一共享数据实例传给不同的runnable
  3. 使用不同的runnable对象,将这些Runnable对象作为一个内部类,将共享数据作为成员变量

# 12. 多线程的最佳实践/好习惯

  1. 给线程起个有意义的名字
  2. 避免使用锁和缩小锁的范围
  3. 多用同步辅助类(CountDownLatch、CyclicBarrier、Semaphore)少用wait、notify
  4. 多用并发集合少用同步集合

# 13. ThreadLocal的设计理念与作用

  1. 供线程内的局部变量,线程独有,不与其他线程共享
  2. 适用场景:多线程情况下某一变量不需要线程间共享,需要各个线程间相互独立

# 14. ThreadLocal原理,用的时候需要注意什么

  1. ThreadLocal通过获得Thread实例内部的ThreadLocalMap来存取数据
  2. ThreadLocal实例本身作为key值
  3. 如果使用线程池,Threadlocal可能是上一个线程的值,需要我们显示的控制
  4. ThreadLocal的key虽然采用弱引用,但是仍然可能造成内存泄漏(key为null,value还有值) 扩展:Android中的ThreadLocal实现略有不同,使用Thread实例中的是数组存值,通过ThreadLocal实例计算一个唯一的hash确定下标。

更多参照

# 15. 线程的基本状态及状态之间的关系

img

# 16. 如果同步块内的线程抛出异常会发生什么

  1. 线程内的异常可以捕获,如果没有捕获,该线程会停止运行退出
  2. 不论是正常退出还是异常退出,同步块中的锁都会释放

# 17. 什么是死锁(deadlock)

两个线程互相等待对方释放资源才能继续执行下去,这个时候就形成了死锁,谁都无法继续执行(或者多个线程循环等待)

# 18. N个线程访问N个资源,如何避免死锁

以同样的顺序加锁和释放锁

# 19. 为什么应该在循环中检查等待条件

处于等待状态的线程可能会收到错误警报和伪唤醒,如果不在循环中检查等待条件,程序就会在没有满足结束条件的情况下退出

# 20. Java中的同步集合与并发集合有什么区别

  1. 同步集合与并发集合都为多线程和并发提供了合适的线程安全的集合
  2. 并发集合性能更高

更多参考

# 21. Java中活锁和死锁有什么区别

这是上题的扩展,活锁和死锁类似,不同之处在于处于活锁的线程或进程的状态是不断改变的,活锁可以认为是一种特殊的饥饿。一个现实的活锁例子是两个 人在狭小的走廊碰到,两个人都试着避让对方好让彼此通过,但是因为避让的方向都一样导致最后谁都不能通过走廊。简单的说就是,活锁和死锁的主要区别是前者进程的状态可以改变但是却不能继续执行

# 22. 怎么检测一个线程是否拥有锁

java.lang.Thread中有一个方法叫holdsLock(),它返回true如果当且仅当当前线程拥有某个具体对象的锁

更多参考

# 23. Java中ConcurrentHashMap的并发度是什么

ConcurrentHashMap把实际map划分成若干部分来实现它的可扩展性和线程安全。这种划分是使用并发度获得的,它是 ConcurrentHashMap类构造函数的一个可选参数,默认值为16,这样在多线程情况下就能避免争用

# 24. 什么是阻塞式方法

阻塞式方法是指程序会一直等待该方法完成期间不做其他事情,ServerSocket的accept()方法就是一直等待客户端连接。这里的阻塞是 指调用结果返回之前,当前线程会被挂起,直到得到结果之后才会返回。此外,还有异步和非阻塞式方法在任务完成前就返回。

# 25. 多线程中的忙循环是什么

忙循环就是程序员用循环让一个线程等待,不像传统方法wait(), sleep() 或 yield() 它们都放弃了CPU控制,而忙循环不会放弃CPU,它就是在运行一个空循环。这么做的目的是为了保留CPU缓存,在多核系统中,一个等待线程醒来的时候可 能会在另一个内核运行,这样会重建缓存。为了避免重建缓存和减少等待重建的时间就可以使用它了。

# 26. 如何保证多线程下 i++ 结果正确

可以使用synchronized保证原子性,也可以使用AtomicInteger类 扩展:volatile只能保证可见性,不能保证原子性,因此不行

# 27. 简述Java中具有哪几种粒度的锁

Java中可以对类、对象、方法或是代码块上锁

# 同步方法和同步代码块的对比

  1. 同步代码块可以指定更小的粒度
  2. 同步代码块可以给指定实例加锁

# 28. 类锁和对象锁

类锁其实时一种特殊的对象锁,它锁的其实时类对应的class对象

更多参考

# 线程中的关键字和类

# 0. sleep和wait方法的对比

  1. 两个方法都是暂停线程,释放cpu资源给其他线程
  2. sleep是Thread的静态方法,wait是Object的方法。
  3. sleep使线程进入阻塞状态;wait使线程进入等待状态,靠其他线程notify或者notifyAll来改变状态
  4. sleep可以在任何地方使用,必须捕获异常;而wait必须在同步方法或者同步块中使用,否则会抛出运行时异常
  5. 最重要的:sleep继续持用锁,wait释放锁 扩展:yield停止当前线程,让同优先级或者优先级高的线程先执行(但不会释放锁);join方法在某一个线程的执行过程中调用另一个线程执行,等到被调用的线程执行结束后,再继续执行当前线程

# 1. 线程的sleep()方法和yield()方法有什么区别

  1. sleep方法使当前线程阻塞指定时间,随后进入就绪状态
  2. yield方法使当前线程进入就绪状态,让同优先级或者更高优先级的线程先执行
  3. sleep方法会抛出interruptedException

# 2. 为什么wait, notify 和 notifyAll这些方法不在thread类里面

JAVA提供的锁是对象级的而不是线程级的,每个对象都有锁,通 过线程获得。如果线程需要等待某些锁那么调用对象中的wait()方法就有意义了。如果wait()方法定义在Thread类中,线程正在等待的是哪个锁 就不明显了

# 3. [为什么wait和notify方法要在同步块中调用]

  1. java规定必须在同步块中,不在同步块中会抛出异常
  2. 如果不在同步块中,有可能notify在执行的时候,wait没有收到陷入死锁

更多参考

# 4. synchronized关键字的用法

synchronized 用于线程同步

  1. 可以修饰方法
  2. 可以修饰代码块
  3. 当持有的锁是类时,那么所有实例对象调用该方法或者代码块都会被锁

# 5. synchronized 在静态方法和普通方法的区别

  1. synchronized修饰静态方法时,锁是类,所有的对象实例用同一把锁
  2. 修饰普通方法时,锁是类的实例

# 6. 当一个线程进入一个对象的synchronized方法A之后,其它线程是否可进入此对象的synchronized方法B?

不能。其它线程只能访问该对象的非同步方法。第一个线程持有了对象锁,第二个线程的同步方法也需要该对象的锁才能运行,只能在锁池中等待了。

# 7. Java中的volatile 变量是什么

  1. volatile是一个修饰符,只能修饰成员变量
  2. volatile保证了变量的可见性(A线程的改变,B线程马上可以获取到)
  3. volatile禁止进行指令重排序

# 8. 写一个双检锁的单例

private static volatile Singleton instance;  

private Singleton(){}  
    
public Singleton getInstance(
if(singleton == null){
  synchronized(Singleton.class){
  if(singleton == null){
  singleton = new Singleton();
  }
  }
}
return sinlgeton;
)

# 9. 单例的DCL方式下,那个单例的私有变量要不要加volatile关键字,这个关键字有什么用

  1. 要加
  2. 两个线程同时访问双检锁,有可能指令重排序,线程1初始化一半,切换到线程2;因为初始化不是一个原子操作,此时线程2读到不为null直接使用,但是因为还没有初始化完成引起崩溃

更多参考

# 10. Synchronized 和Lock\ReadWriteLock的区别

  1. Synchronized时java关键字,Lock/ReadWriteLock接口,它们都是可重入锁
  2. Synchronized由虚拟机控制,不需要用户去手动释放锁,执行完毕后自动释放;而Lock是用户显示控制的,要用户去手动释放锁,如果没有主动释放锁,就有可能导致出现死锁现象。
  3. Lock可以用更多的方法,比如tryLock()拿到锁返回true,否则false;tryLock(long time, TimeUnit unit)方法和tryLock()方法是类似的,只不过区别在于这个方法在拿不到锁时会等待一定的时间;Lock有lockInterruptibly()方法,是可中断锁
  4. ReentrantLock可以实现公平锁(等得久的先执行)
  5. ReadWriteLock是一个接口,ReentrantReadWriteLock是它的一个实现,将对一个资源(比如文件)的访问分成了2个锁,一个读锁和一个写锁,提高了读写效率。

# 11. LockSupport

LockSupport是JDK中比较底层的类,用来创建锁和其他同步工具类的基本线程阻塞原语

park 方法获取许可。许可默认是被占用的,调用park()时获取不到许可,所以进入阻塞状态 unpark 方法颁发许可

# 12. ReadWriteLock

  1. 读写分离的锁,可以提升效率
  2. 读读能共存,读写、写写不能共存

# 13. 可重入锁(RetrantLock)实现原理

  1. RetrantLock 是通过CAS和AQS实现的
  2. CAS(Compare And Swap):三个参数,一个当前内存值V、旧的预期值A、即将更新的值B,当且仅当预期值A和内存值V相同时,将内存值修改为B并返回true,否则什么都不做,并返回false。原子性操作
  3. RetrantLock内部有一个AbstractQueuedSynchronizer实例,AbstractQueuedSynchronizer是一个抽象类,RetrantLock中有两种对他的实现,一种是公平锁,一种是非公平锁
  4. 在lock时,调用一个CAS的方法compareAndSet来将state设置为1,state是一个volitale的变量,并将当前线程和锁绑定
  5. 当compareAndSet失败时,尝试获取锁:如果和锁绑定的线程时当前线程,state+1
  6. 如果获取锁失败,将其加入到队列中等待,从而保证了并发执行的操作变成了串行
  7. 扩展:公平锁和非公平锁的区别:非公平锁无视队列,直接查看当前可不可以拿到锁;公平锁会先查看队列,队列非空的话会加入队列

更多参考

# 14. Others

synchronized 的实现原理以及锁优化?:Monitor volatile 的实现原理?:内存屏障 CAS?CAS 有什么缺陷,如何解决?CompareAndSwap,通过cpu指令实现的 AQS :AbstractQueueSynchronizer,是ReentrantLock一个内部类 如何检测死锁?怎么预防死锁?:死锁必须满足四个条件,破坏任意一个条件都可以解除死锁

Fork/Join框架

# 线程池

# 0. 什么是线程池(thread pool)

  1. 频繁的创建和销毁对象很耗费资源,所以java引入了线程池。Java 5+中的Executor接口定义一个执行线程的工具。它的子类型即线程池接口是ExecutorService。
  2. Executors 是一个工具类,可以帮我们生成一些特性的线程池
newSingleThreadExecutor:创建一个单线程化的Executor,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
newFixedThreadPool:创建一个指定工作线程数量的线程池。每当提交一个任务就创建一个工作线程,如果工作线程数量达到线程池初始的最大数,则将提交的任务存入到池队列中。
newCachedThreadPool:创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
newScheduleThreadPool:创建一个定长的线程池,而且支持定时的以及周期性的任务执行,支持定时及周期性任务执行。

  1. 我们常用的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可以接受RunnableCallable
2. submit有返回值
3. 在异常处理时,submit可以通过Future.get捕获抛出的异常


# 1. 线程池如何调优,最大数目如何确认

  1. 线程池的调优优根据具体情况具体分析,尽量使系统资源利用率最大
  2. 例如如果cpu效率明显高于IO,那么就应该创建更多线程提高cpu利用率,避免io等待(参考1参考2
  3. Android中最大数目可以是:cpu数目*2+1,但也要根据具体场景,例如picaso会根据网络状况调整最大数目

更多参考

# 2. 如果你提交给ThreadPoolExcuter任务时,线程池队列已满,这时会发生什么

1.如果还没达到最大线程数,则新建线程

2.如果已经达到最大线程数,交给RejectExecutionHandler处理。

3.如果没有设置自定义RejectExecutionHandler,则抛出RejectExecutionExcuption

# 3. 线程池的用法与优势

# 优势

实现对线程的复用,避免了反复创建及销毁线程的开销;使用线程池统一管理线程可以减少并发线程的数目,而线程数过多往往会在线程上下文切换上以及线程同步上浪费过多时间。

# 用法

我们可以调用ThreadPoolExecutor的某个构造方法来自己创建一个线程池。但通常情况下我们可以使用Executors类提供给我们的静态工厂方法来更方便的创建一个线程池对象。创建了线程池对象后,我们就可以调用submit或者excute方法提交任务到线程池中去执行了;线程池使用完毕后我们要记得调用shutdown方法来关闭它。

# 多线程中的工具类

# 0. Java并发编程:CountDownLatch、CyclicBarrier(栅栏)和Semaphore(信号量)

  1. 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();
        }
     }
}

  1. 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("所有线程写入完毕,继续处理其他任务...");
        }
    }
}

扩展(CyclicBarrierCountdownLatch的区别):1.CountdownLatch等待几个任务执行完毕,CyclicBarrier等待达到某个状态;2.CyclicBarrier可以调用reset,循环使用;3.CyclicBarrier可以有含Runnable的构造方法,当达到某一状态时执行某一任务。

  1. 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();
            }
        }
    }
}

# 1. java中的信号量(Semaphore)

  1. Semaphore可以控制当前资源被访问的线程个数,超过最大个数后线程处于阻塞等待状态
  2. 当线程个数指定为1时,可以当锁使用

更多参考

# 2. [怎么实现所有线程在等待某个事件的发生才会去执行]

所有线程需要阻塞等待,并且观察到事件状态改变满足条件时自动执行,可以用以下方法实现

  1. 闭锁CountDownLatch:闭锁是典型的等待事件发生的同步工具类,将闭锁的初始值设置1,所有线程调用await方法等待,当事件发生时调用countDown将闭锁值减为0,则所有await等待闭锁的线程得以继续执行。
  2. 阻塞队列BlockingQueue:所有等待事件的线程尝试从空的阻塞队列获取元素,将阻塞,当事件发生时,向阻塞队列中同时放入N个元素(N的值与等待的线程数相同),则所有等待的线程从阻塞队列中取出元素后得以继续执行。
  3. 信号量Semaphore:设置信号量的初始值为等待的线程数N,一开始将信号量申请完,让剩余的信号量为0,待事件发生时,同时释放N个占用的信号量,则等待信号量的所有线程将获取信号量得以继续执行。

更多参考

# 3. 生产者-消费者实现之阻塞队列

  1. 扩展:通过sychronized关键字实现
  2. 阻塞队列的特征是当取或放元素是,队列不满足条件(比如队列为空时进行取操作)可以阻塞等待,知道满足条件
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) {} }} 
}
}

# 4. ArrayBlockingQueue, CountDownLatch类的作用

  1. ArrayBlockingQueue:一个基于数组实现的阻塞队列,它在构造时需要指定容量。当试图向满队列中添加元素或者从空队列中移除元素时,当前线程会被阻塞。
  2. CountDownLatch:同步计数器,是一个线程工具类,可以让一个或几个线程等待其他线程

# 5. Condition

Condition是一个接口,有await和signal方法,和Object的wait、notify类似 Condition 通过lock获得:Condition condition = lock.newCondition(); 相对于Object的wait、notify,Condition的控制更加灵活,可以满足唤起某一线程的目的

更多参考

# 进程

# 0. 进程的三个状态

  1. 就绪状态:获得CPU调度时由 就绪状态 转换为 运行状态
  2. 运行状态:CPU时间片用完了由 运行状态 转换为 就绪状态 运行状态
  3. 阻塞状态:因等待某个事件发生而进入 阻塞状态,事件发生后由 阻塞状态 转换为 就绪状态

# 1. 进程的同步和互斥

  1. 互斥:两个进程由于不能同时使用同一临界资源,只能在一个进程使用完了,另一进程才能使用,这种现象称为进程间的互斥。
  2. 对于互斥的资源,A进程到达了该点后,若此时B进程正在对此资源进行操作,则A停下来,等待这些操作的完成再继续操作。这就是进程间的同步

# 2. 死锁产生的必要条件

  1. 互斥:一个资源一次只能被一个进程所使用,即是排它性使用
  2. 不剥夺条件:一个资源仅能被占有它的进程所释放,而不能被别的进程强占
  3. 请求与保持条件:进程已经保持了至少一个资源,但又提出了新的资源要求,而该资源又已被其它进程占有,此时请求进程阻塞,但又对已经获得的其它资源保持不放
  4. 环路等待条件:当每类资源只有一个时,在发生死锁时,必然存在一个进程—资源的环形链

更多参考

# 类加载

# 0. 描述一下JVM加载class文件的原理机制

类加载器的作用是根据指定全限定名称将class文件加载到JVM内存中,并转为Class对象。

# 加载器的种类

  1. 启动类加载器(根加载器 Bootstrap ClassLoader):由native代码实现,负责将存放在<JAVA_HOME>\lib目录或-Xbootclasspath参数指定的路径中的类库加载到内存中
  2. 扩展加载器(Extension ClassLoader):java语言实现,父加载器是Bootstrap,:负责加载<JAVA_HOME>\lib\ext目录或java.ext.dirs系统变量指定的路径中的所有类库。
  3. 应用程序类加载器(Application ClassLoader):java实现,负责加载用户类路径(classpath)上的指定类库,我们可以直接使用这个类加载器。一般情况,如果我们没有自定义类加载器默认就是用这个加载器。
  4. 自定义类加载器:有时为了安全会将类加密,或者从远程(服务器)加载类 ,这个时候就需要自定义类加载器。自定义通过继承ClassLoader类实现,loadClass方法已经实现了双亲委派模式,当父类没有加载成功时,调用当前类的findclass方法,所以我们一般重写该方法。

# 加载过程

  1. 类加载器采用双亲委派模型进行加载:每次通过先委托父类加载器加载,当父类加载器无法加载时,再自己加载。
  2. 类的生命周期可以分为七个阶段:加载 -> 连接(验证 -> 准备*(为静态变量分配内存并设置默认的初始值)* -> 解析*(将符号引用替换为直接引用)*)-> 初始化 -> 使用 -> 卸载

# 1. 类加载为什么要使用双亲委派模式,有没有什么场景是打破了这个模式

  1. 使用双亲委派模式,保证只加载一次该类
  2. 我们可以使用自定义的类加载器加载同名类,这样就阻止了系统双亲委派模式的加载

# 2. ClassLoader的隔离问题

  1. JVM 及 Dalvik 对类唯一的识别是 ClassLoader id + PackageName + ClassName
  2. 两个相同的类可能因为两个ClassLoader加载而不兼容

# 反射和范型

# 0. 反射的原理和作用

  1. 通过类的class对象类获得类的各种信息,创建对应的对象或者调用方法
  2. App的动态加载或者Android中调用其他对象private方法,都需要反射

# 1. 类对象的获取方式

  1. String.class:不执行静态块和动态构造块
  2. "hello".getClass();:执行静态块和动态构造块
  3. Class.forName("java.lang.String");:执行静态块,不执行动态构造块

# 2. 如何通过反射创建对象

  1. String.class.newInstance();
  2. String.class.getConstrutor(Stirng.class).newInstance("hello word");

# 3. 如何通过反射获取和设置对象私有字段的值

  1. 通过类对象的getDeclaredField()方法获得(Field)对象
  2. 调用Field对象的setAccessible(true)方法将其设置为可访问
  3. 通过get/set方法来获取/设置字段的值

# 4. 通过反射调用对象的方法

  1. 通过类对象的getMethod方法获得Method对象
  2. 调用对象的invoke()方法

# 5. 范型

  1. 范型可以用于类定义和方法定义
  2. 范型的实现是通过擦除实现的,也就是说编译之后范型信息会被擦出

# 6. 通配符

  1. 通配符有两种用法:?extends A 和 ? super A
  2. ?extends A 表示?的上界是A,具体什么类型并不清楚,适合于获取,获取到的一定是A类型
  3. ? super A 表示?的下界是A,具体什么类型并不清楚,适合于插入,一定可以插入A类型

更多参考

# 7. 注解(Annotation)

注解分为三种:源码级别(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

Last Updated: 3/5/2020, 6:29:15 PM