面试总结 (一)

1.jvm虚拟机模型

java 虚拟机分为 线程共享区 其中包括堆 和方法区 线程私有区 包括 程序计数器 虚拟机方法栈 本地方法栈

2.String a = new String(“a”)创建几个对象

创建了一个或者两个对象
当JVM遇见上述代码的时候 会先检查常量池中是否有a对象 如果已经存在a对象 那么就不会在常量池中创建新对象,会在堆中创建一个String()对象 然后只想常量池中的 a
如果在常量池中没有“a”对象 那么就会创建两个对象 string对象和常量池总的a
升级问法

  • String str = "abc" + "def"; 会创建几个对象
    一个对象
    在虚拟机中会对常量的+加号进行优化 把他作为一个常量对象 进行保存
  • String str = "abc" + new String("def");会创建几个对象
    5个在虚拟中会对字符串的拼接进行优化 ,会创建一个StringBuilder对象
    即这种效果
    String s = new String("def");
    new StringBuilder().append("abc").append(s).toString();
    所以最终是5个对象
    常量池中分别有“abc”和“def”,堆中对象new String("def")和“abcdef”。
    还有一个StringBuilder对象

3.死锁怎么产生?

死锁概念:在应用中许多进程需要独占是资源,当操作系统须允许读个进程并发执行的时候,可能会出现进程永远阻塞的现象,如两个进程分别等待对方所占有的资源,于是两者都处于等待状态,此现象被称为死锁

3.1 死锁产生条件
  • 互斥资源 :临界资源都是独占资源,进程互斥拍他的使用这些资源
  • 占有和等待条件: 请求资源得不到满足时不可释放资源
  • 不剥夺条件:已获得资源只能有进程自愿释放,不可被剥夺
  • 循环等待条件:每个进程都在等待链中等待下一个进程所持有的的资源
    只有同时满足这四个条件才会产生死锁
3.2产生原因
  • 进程顺序不当
  • 同类资源分配不均
  • 对某些资源的使用未加限制
3.3 解决的方法
  • 死锁防止
    在程序运行之前防止发生死锁,使他不同时满足资格条件就行
    (1)破坏互斥条件
    使资源同时访问而非互斥使用
    (2)破坏占有和等待条件
    使用静态检测的方式,程必须在执行之前就申请需要的全部资源,且直至所要的资源全部得到满足后才开始执行。 但是 这样会但是严重的减低了资源利用率。
    (3)破坏不剥夺条件
    剥夺调度能够防止死锁,但是只适用于内存和处理器资源。
    方法一:占有资源的进程若要申请新资源,必须主动释放已占有资源,若需要此资源,应该向系统重新申请。
    方法二:资源分配管理程序为进程分配新资源时,若有则分配;否则将剥夺此进程已占有的全部资源,并让进程进入等待资源状态,资源充足后再唤醒它重新申请所有所需资源。
    (4)破坏循环等待条件
    给系统的所有资源编号,规定进程请求所需资源的顺序必须按照资源的编号依次进行。
    采用层次分配策略,将系统中所有的资源排列到不同层次中
    一个进程得到某曾的一个资源后,只能申请较高一层的资源
    当需要生发某曾的资源的时候,必须先释放所占资源的较高层的资源
    当获取某层的一个资源是,如果想申请统一层的另一个资源,就必须释放此层中已占有的资源
  • 死锁避免
    各种死锁防止方法能够防止发生死锁,但必然会降低系统并发性,导致低效的资源利用率。
    安全状态


    image.png

    图 a 的第二列 Has 表示已拥有的资源数,第三列 Max 表示总共需要的资源数,Free 表示还有可以使用的资源数。从图 a 开始出发,先让 B 拥有所需的所有资源(图 b),运行结束后释放 B,此时 Free 变为 5(图 c);接着以同样的方式运行 C 和 A,使得所有进程都能成功运行,因此可以称图 a 所示的状态时安全的。

定义:如果没有死锁发生,并且即使所有进程突然请求对资源的最大需求,也仍然存在某种调度次序能够使得每一个进程运行完毕,则称该状态是安全的。

安全状态的检测与死锁的检测类似,因为安全状态必须要求不能发生死锁。下面的银行家算法与死锁检测算法非常类似,可以结合着做参考对比。
单个资源的银行家算法
一个小城镇的银行家,他向一群客户分别承诺了一定的贷款额度,算法要做的是判断对请求的满足是否会进入不安全状态,如果是,就拒绝请求;否则予以分配。


image.png

上图 c 为不安全状态,因此算法会拒绝之前的请求,从而避免进入图 c 中的状态。

  • 死锁检测和恢复
    对资源的分配加以适当限制可防止或避免死锁发生,但不利于进程对系统资源的充分共享
    不试图阻止死锁,而是当检测到死锁发生时,采取措施进行恢复。
    如果进程 - 资源分配图中无环路,此时系统没有发生死锁。
    如果进程 - 资源分配图中有环路,则可分为以下两种情况:
    每种资源类中仅有一个资源,则系统发生了死锁。此时,环路是系统发生死锁的充分必要条件,环路中的进程就是死锁进程。
    每种资源类中有多个资源,则环路的存在只是产生死锁的必要不充分条件,系统未必会发生死锁。
    每种资源类中仅有一个资源的死锁检测


    image.png

    上图为资源分配图,其中方框表示资源,圆圈表示进程。资源指向进程表示该资源已经分配给该进程,进程指向资源表示进程请求获取该资源。
    每种类型一个资源的死锁检测算法是通过检测有向图是否存在环来实现,从一个节点出发进行深度优先搜索,对访问过的节点进行标记,如果访问了已经标记的节点,就表示有向图存在环,也就是检测到死锁的发生。
    死锁恢复
    资源剥夺法
    剥夺陷于死锁的进程所占用的资源,但并不撤销此进程,直至死锁解除。
    进程回退法
    根据系统保存的检查点让所有的进程回退,直到足以解除死锁,这种措施要求系统建立保存检查点、回退及重启机制。
    进程撤销法
    撤销陷入死锁的所有进程,解除死锁,继续运行。
    逐个撤销陷入死锁的进程,回收其资源并重新分配,直至死锁解除。

4.Synchronized与ReentrantLock区别总结

相似点
它们都是加锁方式同步,而且都是阻塞式的同步,也就是说当如果一个线程获得了对象锁,进入了同步块,其他访问该同步块的线程都必须阻塞在同步块外面等待,而进行线程阻塞和唤醒的代价是比较高的(操作系统需要在用户态与内核态之间来回切换,代价很高,不过可以通过对锁优化进行改善)。

  • 功能区别
    Synchronized是java的关键字 原生语法层次的互斥,需要Jvm实现,
    ReentrantLock他是JDK1.5以后提供的API层面的互斥锁,是要使用lock与unLock()配合 需要使用 try finally来配合完成
    使用方面 :很明显Synchronized的使用比较方便简洁,并且由编译器去保证锁的加锁和释放,比较方便,而ReenTrantLock的细粒度个灵活度都比较高
  • 性能区别
    在Synchronized优化以前,synchronized的性能是比ReenTrantLock差很多的,但是自从Synchronized引入了偏向锁,轻量级锁(自旋锁)后,两者的性能就差不多了,在两种方法都可用的情况下,官方甚至建议使用synchronized,其实synchronized的优化我感觉就借鉴了ReenTrantLock中的CAS技术。都是试图在用户态就把加锁问题解决,避免进入内核态的线程阻塞。
  • synchronized
    synchronized 经过编译,会在同步代码块的前后分别形成 monitorenter和monitorexit两个字节码指令,在执行monitorenter指令是,首先要尝试
    获取对象锁。如果对象没有被锁定或者当前线程已经拥有那个锁,把锁的计数器加1,相应的在执行monitorexit指令时会将所得计算器减一,当计数器为0的时候锁就被释放,如果说去锁对象失败,那么当前线程就需要阻塞,知道对象被另一个线程释放为止
  • ReentrantLock
    ReentrantLock比synchronized会提供一些更加高级的功能
    (1)等待可中断。当持有锁的线程长期不释放的时候,正在等待的线程可以选择放弃等待,这个可以避免部分死锁
    (2)公平锁:多个线程等待统一个锁是,必须按照申请锁的时间顺序获得锁
    Synchronized是非公平锁,ReentrantLock的默认构造函数创建的是非公平锁,可以通过参数设置为公平锁,但是公平锁的性能不是很好
    (3)锁绑定多个条件,ReenTrantLock提过了一个Condition类来实现分组唤醒线程,不想synchronized要么随机唤醒一个要么唤醒所有线程
    ReenTrantLock实现的原理:
    ReenTrantLock的实现是一种自旋锁,通过循环调用CAS操作来实现加锁。它的性能比较好也是因为避免了使线程进入内核态的阻塞状态。
    什么时候需要使用ReenTrantLock
    当需要ReenTrantLock这三个特性的时候
    5.Synchronized所膨胀的过程
    synchronized锁有四种状态,无锁,偏向锁,轻量级锁,重量级锁
    锁可以升级但不能降级,但是偏向锁状态可以被重置为无锁状态
    在说一下每个锁状态的应用常见
    偏向锁:只有一个线程进入临界区;
    轻量级锁:多个线程交替进入临界区;
    重量级锁:多个线程同时进入临界区。
    一个代码块
synchronized (lockObject) {
    // do something
}

上述同步代码块中存在一个临界区,假设当前存在Thread#1和Thread#2这两个用户线程,分三种情况来讨论:
当只要Thread1进入临界区的时候,JVM会把lockObject的对象头标记为01,同事会使用CAS操作吧Thread1的线程id记录的MarkWord中,此时进入偏向锁模式,此时所谓的偏向是指,在接下来没有其他线程进入临界区,此时Thread1退出临界区 将不会执行任何操作,当碰到第二个临界区的时候会比对当前的ThreadId与lockObject对象头中ThreadId是否相同,如果相同不执行任何CAS操作,减少进入临界区和退出临界区的开销。
当Thead2尝尽进入临界区的时候,那么说明偏向锁上发生了竞争,此时会撤销偏向,Mark Word中不再存放偏向线程ID,而是存放hashCode和GC分代年龄,Thread获取lockObject的轻量锁,此时会通过CAS自旋的凡是等待
当自旋超过一定实时间,或者当一个在一个在自旋 第三个线程又来访问的时候此时 轻量级锁就会膨胀为重量级锁,重量级锁使除了拥有锁的线程以外的线程都阻塞,防止CPU空转。

6.轻量级锁 有哪些

轻量级锁就是自旋锁
简单的自旋锁

package aqs;

import java.util.concurrent.atomic.AtomicReference;

public class SpinLock {
                   //使用原子类来标识线程是否获取到了锁
       private AtomicReference lockOwner = new AtomicReference();

       public void lock() {
           Thread currentThread = Thread.currentThread();

                  // 如果锁未被占用,则设置当前线程为锁的拥有者
           while (!lockOwner.compareAndSet(null, currentThread)) {
           }
       }

       public void unlock() {
           Thread currentThread = Thread.currentThread();

                  // 只有锁的拥有者才能释放锁
           lockOwner.compareAndSet(currentThread, null);
       }
    }

通过compareAndSet保证操作的一组那字行,也可以看做C对所得获取只有在获取到锁之后才能继续后续的操作,否则就在一直获取所得状态,这种自旋锁在多线程同时竞争锁时,并不能保证其公平性。
Ticket Spinlock
Ticket就类似于现实中的排队叫号,锁拥有一个服务号,表示正在服务的线程,还有一个排队号;每个线程尝试获取锁之前先拿一个排队号,然后不断轮询锁的当前服务号是否是自己的排队号,如果是,则表示自己拥有了锁,不是则继续轮询。
简单实现

public class TicketSpinLock {

    // 排队号
    private AtomicInteger ticketNum = new AtomicInteger(0);

    // 服务号
    private AtomicInteger owner = new AtomicInteger(0);

    // 当前线程持有的票据
    private static final ThreadLocal myTicketLocal = new ThreadLocal();

    public void lock() {
        int myTicket = ticketNum.getAndIncrement();// 票据
        myTicketLocal.set(myTicket);
        while (myTicket != owner.get()) {

        }
    }

    public void unlock() {
        int myTicket = myTicketLocal.get();
        owner.compareAndSet(myTicket, myTicket + 1);
    }

    public static void main(String[] args) throws InterruptedException {
        TicketSpinLock lock = new TicketSpinLock();
        Service service = new Service(new TicketSpinLock());
        for (int i = 0; i < 100; i++) {
            new Thread(new ServiceThread(service), i + "").start();
        }
        TimeUnit.SECONDS.sleep(20);
    }

    public static class ServiceThread extends Thread {

        private Service service;

        public ServiceThread(Service service) {
            this.service = service;
        }

        @Override
        public void run() {
             service.doService2();
// service.doService();
        }
    }

    // dosomething
    public static class Service {
        private int i;

        private TicketSpinLock ticketLock;

        public Service() {
        }

        public Service(TicketSpinLock ticketLock) {
            this.ticketLock = ticketLock;
        }

        // 加锁
        public void doService() {
            ticketLock.lock();
            i = i + 1;
            System.out.println(String.format("i=%s", i));
            ticketLock.unlock();
        }

        // 不加锁
        public void doService2() {
            i = i + 1;
            System.out.println(String.format("i=%s", i));
        }

    }
}

Ticket Lock 虽然解决了公平性问题,但是在多处理器上,每个进程的线程都需要读写同一个变量serviceNum ,每次读写操作都必须早处理器的缓存之间进行缓存同步这将大大的降低性能。
CLH锁
public class CLHLock implements Lock {
AtomicReference tail = new AtomicReference(new QNode());
ThreadLocal myPred;
ThreadLocal myNode;

public CLHLock() {  
    tail = new AtomicReference(new QNode());  
    myNode = new ThreadLocal() {  
        protected QNode initialValue() {  
            return new QNode();  
        }  
    };  
    myPred = new ThreadLocal() {  
        protected QNode initialValue() {  
            return null;  
        }  
    };  
}  

@Override  
public void lock() {  
    QNode qnode = myNode.get();  
    qnode.locked = true;  
    QNode pred = tail.getAndSet(qnode);  
    myPred.set(pred);  
    while (pred.locked) {  
    }  
}  

@Override  
public void unlock() {  
    QNode qnode = myNode.get();  
    qnode.locked = false;  
    myNode.set(myPred.get());  
}  

}
CHLLOCk
上面说到TicketLock 是基于队列的,那么 CLHLock 就是基于链表设计的,CLH的发明人是:Craig,Landin and Hagersten,用它们各自的字母开头命名。CLH 是一种基于链表的可扩展,高性能,公平的自旋锁,申请线程只能在本地变量上自旋,它会不断轮询前驱的状态,如果发现前驱释放了锁就结束自旋。
MCSLock
MCS Spinlock 是一种基于链表的可扩展、高性能、公平的自旋锁,申请线程只在本地变量上自旋,直接前驱负责通知其结束自旋,从而极大地减少了不必要的处理器缓存同步的次数,降低了总线和内存的开销。MCS 来自于其发明人名字的首字母:John Mellor-Crummey和Michael Scott。

7.单例模式 两次 判断空

为何在synchronization外面的判断?
为了提高性能!如果拿掉这次的判断那么在行的时候就会直接的运行synchronization,所以这会使每个getInstance()都会得到一个静态内部锁,这样的话锁的获得以及释放的开销(包括上下文切换,内存同步等)都不可避免,降低了效率。所以在synchronization前面再加一次判断是否为空,则会大大降低synchronization块的执行次数。

为何在synchronization内部还要执行一次呢?

因为可能会有多个线程一起进入同步块外的 if,如果在同步块内不进行二次检验的话就会生成多个实例了。
使用volatile 防止指令重排 否则可能出现返回值为空的情况

8.volatile 关键字

8.1保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。(实现可见性)
8.2禁止进行指令重排序。(实现有序性)

9.二叉树层级打印

题目 请实现一个函数按照之字形顺序打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右到左的顺序打印,第三行再按照从左到右的顺序打印,其他行以此类推。

class Solution {
    public List> levelOrder(TreeNode root) {
        Queue queue = new LinkedList<>();
        List> res = new ArrayList<>();
        if(root != null) queue.add(root);
        while(!queue.isEmpty()) {
            LinkedList tmp = new LinkedList<>();
            for(int i = queue.size(); i > 0; i--) {
                TreeNode node = queue.poll();
                if(res.size() % 2 == 0) tmp.addLast(node.val); // 偶数层 -> 队列头部
                else tmp.addFirst(node.val); // 奇数层 -> 队列尾部
                if(node.left != null) queue.add(node.left);
                if(node.right != null) queue.add(node.right);
            }
            res.add(tmp);
        }
        return res;
    }
}

考察内容BFS

10.线程池

线程池的7个参数
corePoolSize:核心线程数
maximumPoolSize:最大线程数
keepAliveTime :线程存活时间
unit :存活时间单位
workQueue :工作队列 新任务被提交后,会先进入到此工作队列中,任务调度时再从队列中取出任务。
threadFactory :线程工厂 创建一个新线程时使用的工厂,可以用来设定线程名、是否为daemon线程等等
handler :拒绝策略

  • CallerRunsPolicy
    该策略下,在调用者线程中直接执行被拒绝任务的run方法,除非线程池已经shutdown,则直接抛弃任务。
  • AbortPolicy 该策略下,直接丢弃任务,并抛出RejectedExecutionException异常
  • DiscardPolicy 该策略下,直接丢弃任务,什么都不做。
  • DiscardOldestPolicy 该策略下,抛弃进入队列最早的那个任务,然后尝试把这次拒绝的任务放入队列
    线程池处理流程


    image.png

11.activity的Flag

  • FLAG_ACTIVITY_CLEAR_TASK
    本flag能造成在新活动启动前,与新活动关联的任务被清空。也就是说,新活动成为新任务的根,旧的活动都被结束了。本flag只能与FLAG_ACTIVITY_NEW_TASK联合使用。
  • FLAG_ACTIVITY_CLEAR_TOP
  1. 新活动已在当前任务中时,在新活动上面的活动会被关闭,新活动不会重新启动,只会接收new intent。
  2. 新活动已在任务最上面时:如果启动模式是"multiple" (默认的),并且没添加FLAG_ACTIVITY_SINGLE_TOP,那么活动会被销毁重新创建;如果启动模式是其他的,或者添加了FLAG_ACTIVITY_SINGLE_TOP,那么只会调用活动的onNewIntent()。
  3. 跟FLAG_ACTIVITY_NEW_TASK联合使用效果很好:如果用于启动一个任务中的根活动,会把该任务移到前面并清空至root状态。这特别有用,比如用于从notification manager中启动活动。
  • FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET
  1. 已废弃。API 21后用FLAG_ACTIVITY_NEW_DOCUMENT。
  • FLAG_ACTIVITY_MULTIPLE_TASK
  1. 用于创建一个新任务,并启动一个活动放进去;
    总是跟FLAG_ACTIVITY_NEW_DOCUMENT或者FLAG_ACTIVITY_NEW_TASK一起使用;
    单独用FLAG_ACTIVITY_NEW_DOCUMENT或者FLAG_ACTIVITY_NEW_TASK时,会在已存在的任务中寻找匹配的Intent,找不到才会创建一个新任务;
    使用了本flag不会寻找匹配的Intent,无条件创建一个新任务。
  2. 用了FLAG_ACTIVITY_NEW_TASK就不要用本flag,除非你启动的是应用的launcher。 跟FLAG_ACTIVITY_NEW_TASK联合使用能防止把已存在的任务移到前面,会为新活动创建一个新任务,无论已存在的任务中有没有新活动。
  3. 因为默认安卓系统中没有提供可视化的任务管理,所以你不应该使用本flag,除非给用户提供可以回到其他任务的方法。
  4. 单独用本flag而不用FLAG_ACTIVITY_NEW_DOCUMENT或者FLAG_ACTIVITY_NEW_TASK是无效的。
  • FLAG_ACTIVITY_NEW_DOCUMENT
  1. 本flag会给启动的活动开一个新的任务记录。使用了本flag或documentLaunchMode属性时,相同活动的多实例会在最近任务列表中产生不同的记录。
  2. 使用本flag比使用documentLaunchMode属性更好,因为documentLaunchMode属性会跟活动绑定,而flag只在需要时添加。
  3. 注意本flag的默认词义,活动销毁后最近任务列表中的入口不会移除。这跟使用FLAG_ACTIVITY_NEW_TASK不一样,后者活动销毁后入口会马上移除。你可以用FLAG_ACTIVITY_RETAIN_IN_RECENTS改变这个行为。
  4. 本flag可以跟FLAG_ACTIVITY_MULTIPLE_TASK联合使用。单独使用时跟manifest活动中定义documentLaunchMode="intoExisting"效果相同,联合使用时跟manifest活动中定义documentLaunchMode="always"效果相同。
  • FLAG_ACTIVITY_NEW_TASK
  1. 新活动会成为历史栈中的新任务(一组活动)的开始。
  2. 通常用于具有"launcher"行为的活动:让用户完成一系列事情,完全独立于之前的活动。
  3. 如果新活动已存在于一个为它运行的任务中,那么不会启动,只会把该任务移到屏幕最前。
  4. 如果新活动要返回result给启动自己的活动,就不能用这个flag。
  • FLAG_ACTIVITY_NO_ANIMATION
  1. 本flag会阻止系统展示活动的当前状态到另一个状态之间的转移动画。这并不意味着永远没有动画 -- 如果另一项活动的改变在当前展示的活动启动前发生并且没有使用本flag,那么动画还会展示。当你要进行一系列活动操作,但是用户看到的动画不应该由第一项改变来驱动,而是由下一项。
  • FLAG_ACTIVITY_NO_HISTORY
  1. 新活动不会保留在历史栈中,一旦用户切换到其他页面,新活动会马上销毁。
  2. 旧活动的onActivityResult()方法永远不会被触发。
  • FLAG_ACTIVITY_REORDER_TO_FRONT
  1. 如果新活动已在任务中,用本flag启动会将它移到任务的历史栈的前面。
  2. 如果用了FLAG_ACTIVITY_CLEAR_TOP,本flag就无效。
  • FLAG_ACTIVITY_RETAIN_IN_RECENTS
  1. 默认情况下由FLAG_ACTIVITY_NEW_DOCUMENT创建的新纪录,用户关闭时(按返回键或其他方式结束)它在最近任务中的入口会被移除。如果你想保留入口,就用本flag。
  2. 接收的活动可以用autoRemoveFromRecents属性或者调用Activity.finishAndRemoveTask()来覆盖本请求。
  • FLAG_ACTIVITY_SINGLE_TOP
    新活动已存在历史栈的顶端时就不会重新启动。
  • FLAG_ACTIVITY_FORWARD_RESULT
  1. (当前活动由源活动启动)本intent从当前活动启动新活动时,源活动的接收目标会从当前活动转移为新活动。新活动调用setResult的数据会传送给源活动。
  • FLAG_ACTIVITY_PREVIOUS_IS_TOP
  1. 本intent从当前活动启动新活动时,当前活动不会被视为顶端活动,不管是决定传intent给顶端还是启动新活动。新活动被当做顶端活动使用,假设当前活动立即销毁了。
  • FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
  1. 新活动不会保存在最近启动的活动列表中。
  • FLAG_ACTIVITY_BROUGHT_TO_FRONT
  1. 本flag一般不由应用代码设置,singleTask模式时系统会给你设置。
  • FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
  1. 新活动在新任务中启动或者被放到一个已存在任务的顶端时,会被当做任务的前门来启动。这会导致任何相关性的活动在适当状态下需要拥有这个任务(无论移动活动到它里面或者是移走),或者在需要时简单地重置任务到初始状态。
  • FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY
  1. 本flag一般不由应用代码设置,活动从历史栈中启动(长按home键)时系统会给你设置。
  • FLAG_ACTIVITY_NO_USER_ACTION
  1. 本flag会阻止当前最前面活动的onUserLeaveHint回调,在它被新启动的活动造成paused状态时。
  2. 通常,一个活动在受到用户操作而从前面移走的时候会调用上面的回调。该回调标志着活动生命周期中的一个点,在该点活动会隐藏它想要显示的”直到用户看到“的东西,比如闪烁的LED灯。
  3. 如果一个活动曾经由非用户驱动的事件比如来电或闹钟启动,应该在startActivity中添加本flag,以保证暂停时活动知道用户并没有看到通知。
  • FLAG_ACTIVITY_TASK_ON_HOME
  1. 本flag会造成新的启动任务放在当前主页活动任务(如果有的话)的顶端。也就是说,在任务中按返回键总是会回到主页,即使上一个用户看到的活动不是主页。本flag只能与FLAG_ACTIVITY_NEW_TASK联合使用。
  • FLAG_ACTIVITY_LAUNCH_ADJACENT
  1. 本flag只在分屏多窗口模式下使用。新活动会显示在旧活动旁边。本flag只能跟FLAG_ACTIVITY_NEW_TASK联合使用。并且如果你想创建一个已存在活动的新实例,那么要设置FLAG_ACTIVITY_MULTIPLE_TASK。

你可能感兴趣的:(面试总结 (一))