2021年Android面试题及答案收集(不断更新中)
Posted 小羊子说
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了2021年Android面试题及答案收集(不断更新中)相关的知识,希望对你有一定的参考价值。
前言
找工作、招人必备之良品。后期不断完善中……
如何招聘人,搜集了一些知识点。如何做好应聘准备,也收集了一些主要知识点,供你参考。
- android基础知识:基本涵盖Android所有知识体系,四大组件,Fragment,WebView,事件分发,View绘制…
- Java基础知识&高阶知识点:基础部分不谈了,高阶部分:泛型,反射,Java虚拟机…
- 算法与数据结构:链表,堆,栈,树…
- Android常用框架:异步,网络,图片加载,内存优化,依赖注入,数据库等框架
- Android前沿技术:Android组件化,热更新,插件化,消息推送,AOP面向切面编程,Flutter(谷歌的移动UI框架)…
- 源码分析:Android源码分析,启动一个app的源码分析,常用框架源码分析,Java源码分析,集合源码分析…
- 网络基础:五层网络模型,三次握手&四次挥手,请求头&响应头,Socket&WebSocket…
文章目录
- 前言
- 1.Java中的==、equals和hashCode的区别
- 2.int和integer的区别
- 3.String、StringBuffer和StringBuilder的区别
- 4.什么是内部类?内部类的作用是什么?
- 5.进程与线程的区别
- 6.final、finally、finalize的区别
- 7.Serializable和Parcelable的区别
- 8.静态属性和静态方法是否可以被继承?是否可以被重写?
- 9.成员内部类、静态内部类、局部内部类、和匿名内部类的理解
- 10.Java的垃圾回收机制及其在何时会被触发
- 11.Java中的代理是什么?静态代理和动态代理的区别是什么?
- 12.Java中实现多态的机制是什么?
- 13.Java中反射的相关理解
- 14.Java中注解的相关理解
- 15.对Java中String类的理解
- 16.对Java中字符串常量池的理解
- 17.Java中为什么String类要设计成不可变的
- 18.Java中Hash码(哈希码)的理解
- 19.Object类的equal方法和hashcode方法的重写
- 20.Java常用集合List与Set,以及Map的区别
- 21.ArrayMap和HashMap的区别
- 22.HashMap和HashTable的区别
- 23.HashMap和HashSet的区别
- 24.ArrayList和LinkedList的区别
- 25.数组和链表的区别
- 26.Java中多线程实现的三种方式
- 27.Java中创建线程的三种方式
- 28.线程和进程的区别
- 29.Java中的线程的run( )方法和start()方法的区别
- 31.Java中wait和sleep方法的不同
- 32.对Java中wait/notify关键字的理解
- 33.什么是线程阻塞?线程该如何关闭?
- 34.如何保证线程的安全
- 35.实现线程同步的方式
- 36.Java中Synchronized关键字的用法,以及对象锁、方法锁、类锁的理解
- 37.Java中锁与同步的相关知识
- 38.Synchronized和volatile关键字的区别
- 39.Java的原子性、可见性、有序性的理解
- 40.ReentrantLock、Synchronized、Volatile关键字
- 41.Java中死锁的概念,其产生的四个必要条件
- 42.Java中堆和栈的理解
- 43.理解线程间通信
- 44.线程中的join()方法的理解,及如何让多个线程按顺序执行
- 45.工作者线程(workerThread)与主线程(UI线程)的理解
- 46.AsyncTask(异步任务)的工作原理
- 47.并发和并行的区别及理解
- 48.同步和异步的区别、阻塞和非阻塞的区别的理解
- 49.Java中任务调度的理解
- 50.Java中进程的详细概念
- 51.线程的详细概念
- 52.Android中的性能优化相关问题
- 53.内存泄漏的相关原因
- 54.通过Handler在线程间通信的原理
- 55.Android中动画的类型:
- 55.理解Activity、View、Window三者之间的关系
- 56.Android中Context详解:
- 57.Java中double和float类型的区别
- 58.Android常用的数据存储方式(4种)
- 59.ANR的了解及优化
- 60.Android垃圾回收机制和程序优化System.gc( )
- 61.Android平台的优势和不足
- 62.其他讲解:
1.Java中的==、equals和hashCode的区别
(1)“==”运算符用来比较两个变量的值是否相等,即该运算符用于比较变量之间对应的内存中的地址是否相同,
要比较两个基本类型的数据或两个引用变量是否相等,只能使用“=.=“(注:编译器格式显示问题,是双等号)运算符
(2)equals是Object类提供的方法之一,每个java类都集成自Object类,即每个对象都有equals方法,equals与“==”一样,比较的都是引用,相比运 算符,equals(Object)方法的特殊之处在于其可以被覆盖,所以可以通过覆盖的方法让他比较的不是引用而是数据内容,即堆中的内容是否相等。
(3)hashCode()方法是从Object类继承过来的,他也是用来比较两个对象是否相等,Object类中的hashCode方法,返回对象在内存中地址转换成的一个Int值,所以如果未重写hashCode方法,任何对象的hashCode方法返回的值都是不相等的。
综上而言,==和equals判断的是(基本类型数据)引用是否相等,但equals可以通过覆盖方法使其比较的是数据内容,事实上,Java中很多类库中的类已经覆盖了此方法,而hashCode判断的是对象在内存中地址是否相等。
2.int和integer的区别
Integer是int提供的封装类,而int是java的基本数据类型,Integer的默认值是null,而int的默认值是0,声明Integer的变量需要实例化,而int不需要,Integer是对象,是一个引用指向这个对象,而int是基本数据类型,直接存储数据。
3.String、StringBuffer和StringBuilder的区别
他们的主要区别在于运行速度和线程安全两个方面,运行速度:StringBuilder>StringBuffer>String,String最慢的原因在于String是字符串常量,一旦创建是不可以再更改的,但后两者的对象是变量,是可以更改的,Java中对String对象的操作实际上是一个不断创建新的对象而将旧的对象回收的过程,而后两者因为是变量,所以可以直接进行更改,在线程安全上,StringBuilder是线程不安全的,而StringBuffer是线程安全的,因为在StringBuffer对象在字符串缓冲区被多个线程使用时,StringBuffer中很多方法可以带有synchronized关键字,所以可以保证线程是安全的,而StringBuilder不存在该关键字,所以在线程中并不安全。
4.什么是内部类?内部类的作用是什么?
内部类是定义在另一个类里面的类,与之相对应,包含内部类的类被称为外部类,
内部类的作用有:(1)内部类提供了更好的封装,可以把内部类隐藏在外部类之内,不允许同一包中的其他类访问,(2)内部类的方法可以直接访问外部类的所有数据,包括私有的数据,(3)内部类的种类:成员内部类、静态内部类、方法内部类、匿名内部类
5.进程与线程的区别
进程是CPU资源分配的最小单位,而线程是CPU调度的最小单位,进程之间不能共享资源,而线程共享所在进程的地址空间和其他资源,一个进程内可以拥有多个线程,进程可以开启进程、也可以开启线程,一个线程只能属于一个进程,线程可直接使用同进程的资源,线程依赖于进程而存在。
6.final、finally、finalize的区别
final是用于修饰类、成员变量和成员方法,类不可被继承,成员变量不可变,成员方法不可被重写;finally与try…catch…共同使用,确保无论是否出现异常都能被调用到;finalize是类的方法,垃圾回收前会调用此方法,子类可以重写finalize方法实现对资源的回收。
7.Serializable和Parcelable的区别
Serlizable是java序列化接口,在硬盘上读写,读写的过程中有大量临时变量产生,内部执行大量的I/O操作,效率很低;Parcelable是Android序列化接口,效率高,在内存中读写,但使用麻烦,对象不能保存到磁盘中。
8.静态属性和静态方法是否可以被继承?是否可以被重写?
可以继承,但不可以被重写,而是被隐藏,如果子类里面定义了静态方法或属性,那么这时候父类的静态方法或属性称之为隐藏。
9.成员内部类、静态内部类、局部内部类、和匿名内部类的理解
Java中内部类主要分为成员内部类、局部内部类(嵌套在方法和作用域中)、匿名内部类(无构造方法)、静态内部类(由static修饰的类、不能使用任何外围类的非static成员变量和方法、不依赖外围类),每个内部类都能独立的继承一个(接口的)实现,所以无论外围类是否已经继承了某个(接口的)实现,对于内部类均无影响,因为Java支持实现多个接口,而不支持多继承,我们可以使用内部类提供的、可以继承多个具体的或抽象的类的能力来解决使用接口难以解决的问题,接口只是解决了部分问题,而内部类使得多继承的解决方案变得更加完整。
10.Java的垃圾回收机制及其在何时会被触发
内存回收机制:就是释放掉在内存中已经没有用的对象,要判断怎样的对象是没用的,有两种方法:(1)采用标记数的方法,在给内存中的对象打上标记,对象被引用一次,计数加一,引用被释放,计数就减一,当这个计数为零时,这个对象就可以被回收,但是,此种方法,对于循环引用的对象是无法识别出来并加以回收的,(2)采用根搜索的方法,从一个根出发,搜索所有的可达对象,则剩下的对象就是可被回收的,垃圾回收是在虚拟机空闲的时候或者内存紧张的时候执行的,什么时候回收并不是由程序员控制的,可达与不可达的概念:分配对象使用new关键字,释放对象时,只需将对象的引用赋值为null,让程序不能够在访问到这个对象,则称该对象不可达。
在以下情况中垃圾回收机制会被触发:
(1)所有实例都没有活动线程访问 ;(2)没有其他任何实例访问的循环引用实例;(3)Java中有不同的引用类型。判断实例是否符合垃圾收集的条件都依赖于它的引用类型。
11.Java中的代理是什么?静态代理和动态代理的区别是什么?
代理模式:在某些情况下,一个用户不想或不能直接引用一个对象,此时可以通过一个称之为“代理”的第三者来实现间接引用,代理对象可以在客户端和目标对象之间起中介的作用,并且可以通过中介对象去掉用户不能看到的内容和服务,或者添加用户需要的额外服务。
静态代理:即在程序运行前代理类就已经存在,也就是编写代码的时候已经将代理类的代码写好。
动态代理:在程序运行时,通过反射机制动态创建代理类。
12.Java中实现多态的机制是什么?
方法的重写Overriding和重载Overloading是Java多态性的不同表现
重写Overriding 是父类与子类之间多态的一种表现;
重载Overloading是同一个类中多态性的一种表现;
13.Java中反射的相关理解
Java的反射机制是在运行状态中,对于任意一个类,都能够获取到这个类的所有属性和方法,对于任意一个对象,能够调用它的任意一个方法和属性,包括私有的方法和属性,这种动态地获取信息以及动态的调用对象的方法的功能就称之为Java的反射机制。
从对象出发,通过反射(.class类)可以获取到类的完整信息,(类名、class类型、所在包、具有的所有方法Method[]类型、某个方法的完整信息,包括修饰符、返回值类型、异常、参数类型、所有属性Field[ ]、某个属性的完整信息,构造器Constructors,调用类的属性或方法。
14.Java中注解的相关理解
Java注解,英文名为Annotation,一种代码级别的说明,是JDK1.5及以后版本引入的一个特性,与类、接口、枚举在同一个层次,他可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明、注释、作用分类;注解是Java提供的一种元程序中元素关联任何信息和任何元数据(metadata)的途径和方法,Annotion(注解)是一个接口,程序可以通过反射来获取指定程序元素的Annotion对象,然后通过Annotion对象来获取注解里面的元数据
注解的一般格式为:[修饰符]@interface[名称]元素,元素是无方法体的方法声明,可以有默认值。
注解的作用:
编写文档:通过代码里标识的元数据生成文档[生成文档doc]
代码分析:通过代码里的元数据对代码进行分析[使用反射]
编译检查:通过代码里标识的元数据让编译器能够实现基本的编译检查[Override]
15.对Java中String类的理解
通过对String类的源代码分析可知,(1)String类是final类,即意味着String类不能被继承,并且他的成员方法都默认为final方法;(2)String类在源代码中实际上是通过char[ ]数组来保存字符串的;(3)String对象一旦创建就是固定不便的,对String对象的任何change操作都会生成新的对象。
16.对Java中字符串常量池的理解
字符串的分配和其他对象分配一样,是需要消耗高昂的时间和空间成本的,而且字符串在程序中使用得非常多,JVM为了提高性能和减少内存的开销,在实例化字符串的时候会进行一些优化;每当我们创建字符串常量时,JVM首先会检查字符串常量池,如果该字符床已经存在于常量池中,那么就直接返回常量池中的实例引用,如果该字符串不存在,就会实例化该字符串,并将其放在常量池中,由于字符串的不可变性,我们可以十分肯定常量池中一定不存在两个相同的字符串,Java中的常量池实际上分为两种形态:静态常量池和运行时常量池
静态常量池:即*.class文件的常量池,.class文件的常量池不仅仅包括字符串(数字)字面量,还包含类、方法的信息,占用class文件的绝大部分空间。
运行时常量池:常量存入到内存中,并保存在方法区中,我们常说的常量池,就是指方法区中的运行时常量池。
17.Java中为什么String类要设计成不可变的
在java中将String类设计成不可变的是综合考虑到各种因素的结果,需要综合内存、同步、数据结构、以及安全等方面的考虑。
字符串常量池的需要:字符串常量池是Java堆内存中一个特殊的存储区域,当创建一个String对象时,假如此字符串值已经存在于常量池中,则不会创建一个新的对象,而是引用已经存在的对象,假若字符串对象允许改变,那么将会导致各种逻辑的错误,比如改变一个引用的字符串将会导致另一个引用出现脏数据。
允许String对象缓存HashCode:Java中String对象的哈希码被频繁的使用,比如在HashMap等容器中,字符串不变性保证了哈希码的唯一性,因此可以放心地进行缓存,这也是一种性能优化的手段,意味着不必每次都去计算新的哈希码。
安全性:String被许多Java类库用来当作参数,如:网络连接(network connection)、打开文件(opening files)等等,如果String不是不可变的,网络连接、打开文件将会被改变——这将导致一系列的安全威胁,操作的方法本以为连接上一台机器,其实不是,优于反射的参数都是字符串,同样也会引起一系列的安全问题。
18.Java中Hash码(哈希码)的理解
在Java中,哈希码代表了对象的一种特征,例如我们判断某两个字符串是否==,如果其哈希码相等,则这两个字符串是相等的,其次,哈希码是一种数据结构的算法,常见的哈希码的算法有:
Object类的HashCode,返回对象的内存地址经过处理后的结构,由于每个对象的内存地址都不一样,所以哈希码也不一样。
String类的HashCode,根据String类包含的字符串的内容,根据一种特殊的算法返回哈希码,只要字符串的内容相同,返回的哈希码也相同。
Integer类:返回的哈希码就是integer对象里所包含的那个整数的数值。例如
Integer i1=new Integer(100) i1.hashCode的值就是100,由此可见两个一样大小的Integer对象返回的哈希码也一样。
19.Object类的equal方法和hashcode方法的重写
equal和hashCode的关系是这样的:(1)如果两个对象相同(即用equal比较返回true),那么他们的hashcode值一定要相同;(2)如果两个对象的hashcode相同,他们并不一定相同(即用equal比较返回false),因为hashcode的方法是可以重载的,如果不重载,会用Java.long.Object的hashcode方法,只要是不同的对象,hashcode必然不同。
由于为了提高程序的效率才实现了hashcode方法,先进行hashcode的比较,如果不同,就没有必要再进行equal的比较了,这样就大大减少了equals比较的次数,这对需要比较数量很大的运算效率特稿是很多的。
附加:一旦new对象就会在内存中开辟空间,==比较的是对象的地址值,返回的是boolean型,equal方法默认比较的对象的地址值,但是Integer等基本类型包装类以及String类中已经重写了equal()方法,比较的是对象内存中的内容,返回值是boolean型。
20.Java常用集合List与Set,以及Map的区别
Java中的集合主要分为三种类型:Set(集)、List(列表)、Map(映射);
数组是大小固定的,并且同一个数组只能存放类型一样的数据(基本类型/引用类型),而Java集合是可以存储和操作数目不固定的一组数据,所有的Java集合都位于java.util包中,Java集合只能存放引用类型的数据,不能存放基本数据类型。
Collection是最基本的集合接口,声明了适用于Java集合(只包括Set和List)的通用方法,Set和List都继承了Collection接口。
Set是最简单的一种集合,集合中的对象不按特定的方式排序,并且没有重复对象,Set接口主要实现了两种实现类
TreeSet:TreeSet类实现了SortedSet接口,能够对集合中的对象进行排序;
HashSet:HashSet类按照哈希算法来存取集合中的对象,存取速度比较快;
Set具有与Collection完全一样的接口,因此没有任何额外的功能,实际上Set就是Collection,只是行为不同(这是继承与多态思想的典型应用,表现不同的行为),Set不保存重复的元素,Set接口不保证维护元素的次序。
(2) List列表的特征是其他元素以线性表的方式存储,集合中可以存放重复的对象,其接口主要实现类:
ArrayList( ):代表长度可以改变的数组,可以对元素进行随机的访问,向ArrayList( )中插入与删除元素的速度慢。
LinkedList( ):在实现类中采用链表数据结构,插入和删除的速度快,但访问的速度慢。
对于List的随机访问来说,就是只是随机来检索位于特定位置的元素,List的get(int index)方法返回集合中由参数index指定的索引位置的对象,索引下标从0开始。
Map映射是一种把关键字对象映射的集合,他的每一个元素都包括一堆键对象和值对象,Map没有继承Collection接口,从Map集合中检索元素时只要给出键对象,就会返回对应的值对象。
HashMap:Map基于散列表的实现,插入和查询“键值对”的开销是固定的,可以通过构造器设置容量capacity和负载因子load factor ,以调整容器的性能;
LinkedHashMap:类似于HashMap,但在迭代遍历时,取得“键值对”的顺序是其插入次序,只比HashMap慢一点,而在迭代访问时反而更快,因为它使用链表维护内部次序。
TreeMap:基于红黑树数据结构的实现,查看“键”或“键值对”时,他们会对其排序(次序由Comparabel和Comparator决定)
21.ArrayMap和HashMap的区别
ArrayMap相比传统的HashMap速度更慢,因为其查找方法是二分法,并且当删除或添加数据时,会对空间重新调整,可以说ArrayMap是牺牲了时间来换空间,ArrayMap与HashMap的区别主要在:
存储方式不同:HashMap内部有一个HashMapEntry<K,V>[ ]对象,而ArrayMap是一个<key,value>映射的数据结构,内部使用两个数组进行数据存储,一个数组记录key的hash值,另一个数组记录value值。
添加数据时扩容的处理不一样:HashMap进行了new操作,重新创建对象,开销很大,而ArrayMap用的是copy数据,效率相对高很多。
ArrayMap提供了数组收缩的功能,在clear或remove之后,会重新收缩数组,释放空间。
ArrayMap采用的是二分法查找。
22.HashMap和HashTable的区别
HashMap是基于哈希表实现的,每一个元素是一个key—value对,其内部通过单链表解决冲突的问题HashMap是非线程安全的,只适用于单线程的环境下。多线程的环境下可以采用concurrent并发包下的concurrentHashMap,HsahMap实现了serializable接口,支持序列化,实现了cloneable接口,能被克隆。HashMap内部维持了一个存储数据的Entry数组,HashMap采用链表解决冲突,HashMap中的key和value都允许为null,key为null的键值对永远都放在以table[0]为节点的链表中。
HashTable同样是基于哈希表实现的,同样每个元素是一个key-value对,其内部也是通过单链表解决冲突问题,容量不足时,同样会自动增大,HashTble是线程安全的,能用在多线程的环境下,HashTable实现了serializable接口,它支持序列化,实现了cloneable接口,能被克隆。
HashMap和HashTable之间的区别有以下几点
继承的父类不同,hashTable继承自Dictionary类,而HashMap继承自AbstractMap类,但二者都实现了Map接口。
线程安全性不同,HashTable中的方法是synchronized的,而HashMap中的方法在缺省的情况下是非ynchronized的,在多线程的环境下,可以直接使用HsahTable,不需要为他的方法实现同步,但使用HashMap时就必须自己增加同步处理。
key和value是否允许为null值:关于其中的key和value都是对象,并且不能包含重复的key,但可以包含重复的value,hashtable中,key和value都不允许出现null值,但在hashmap中,null可以作为键,这样的键只有一个,可以有多个键对应的值为null.
23.HashMap和HashSet的区别
HashMap:其实现了Map接口,HashMap存储键值对,使用put( )方法将元素放入到Map中,HashMap使用键对象来计算hashcode值,HashMap比较快,因为是使用唯一的键来获取对象。
HashSet:实现了Set接口,hashSet仅仅存储对象,使用add()方法将元素放入到set中,hashset使用成员对象来计算hashcode值,对于两个对象来说,hashcode可能相同,所以equal方法用来判断对象的相等性,如果两个对象不同的话,那么返回false,hashSet较hashMap来说较慢。
24.ArrayList和LinkedList的区别
ArrayList和LinkedList,前者是Array(动态数组)的数据结构,后者是Link(链表)的数据结构,此外他们两个都是对List接口的实现
当随机访问List时(get和set操作),ArrayList和LinkedList的效率更高,因为LinkedList是线性的数据存储方式,所以需要移动指针从前往后查找。
当对数据进行增删的操作时(add和remove),LinkedList比ArrayList的效率更高,因为ArrayList是数组,所以在其中进行增删操作时,会对操作点之后的所有数据的下标索引造成影响,需要进行数据的移动
从利用效率来看,ArrayList自由性较低,因为需要手动的设置固定大小的容量,但是他的使用比较方便,只需要创建,然后添加数据,通过调用下标进行使用,而LinkedList自由性交给,能够动态的随数据量的变化而变化,但是它不便于使用。
25.数组和链表的区别
数组:是将元素在内存中连续的存储的,因为数据是连续存储的,内存地址连续,所以在查找数据的时候效率比较高,但在存储之前,需要申请一块连续的内存空间,并且在编译的时候就必须确定好他的空间大小。在运行的时候空间的大小是无法随着需要进行增加和减少的,当数据比较大时,有可能出现越界的情况,当数据比较小时,有可能浪费内存空间,在改变数据个数时,增加、插入、删除数据效率比较低。
链表:是动态申请内存空间的,不需要像数组需要提前申请好内存的大小,链表只需在使用的时候申请就可以了,根据需要动态的申请或删除内存空间,对于数据增加和删除以及插入比数组灵活,链表中数据在内存中可以在任意的位置,通过应用来关联数据。
26.Java中多线程实现的三种方式
Java中多线程实现的方式主要有三种:继承Thread类、实现Runnable接口、使用ExecutorService、Callable、Future实现有返回结果的多线程,其中前两种方式线程执行完没有返回值,只有最后一种是带返回值的。
继承Thread类实现多线程:继承Thread类本质上也是实现Tunnable接口的一个实例,他代表一个线程的实例,并且启动线程的唯一方法是通过Thread类的start()方法,start()方法是一个native方法,他将启动一个新线程,并执行run( )方法。
实现Runnable接口方式实现多线程:实例化一个Thread对象,并传入实现的Runnable接口,当传入一个Runnable target参数给Thread后,Thraed的run()方法就会调用target.run( );
使用ExecutorService、Callable、Future实现有返回结果的多线程:可返回值的任务必须实现Callable接口,类似的无返回值的任务必须实现Runnable接口,执行Callable任务后,可以获取一个Future的对象,在该对象上调用get就可以获取到Callable任务返回的Object了,在结合线程池接口ExecutorService就可以实现有返回结果的多线程。
27.Java中创建线程的三种方式
Java中使用Thread类代表线程,所有的线程对象都必须时Thread类或其子类的实例,Java中可以用三种方式来创建线程
继承Java中的Thread类创建线程:定义Thread类的子类,并重写其run( )方法,run( )方法也称为线程执行体,创建该子类的实例,调用线程的start()方法启动线程。
实现Runnable接口创建线程:定义Runnable接口的实现类,重写run()方法,run方法是线程的执行体,创建Runnable实现类的实例,并用这个实例作为Thread的target来创建Thread对象,这个Thread对象才是真正的线程对象,调用线程对象的Start方法启动线程。
使用Callable和Future创建线程:Callable接口提供了一个call( )方法,作为线程的执行体,call( )方法可以有返回值,call( )方法可以声明抛出异常,其创建线程并启动的步骤,创建Callable接口的实现类,并实现call( )方法,创建该实现类的实例,使用FutureTask类来包装Callable对象,该FuutureTask对象封装了callable对象的call( )方法的返回值,使用FutureTask对象作为Thread对象的target创建并启动线程,调用FutureTask对象的get( )方法来获得子线程执行结束后的返回值。
28.线程和进程的区别
线程是进程的子集,一个进程可以有很多线程,每条线程并行执行不同的任务,不同的进程使用不同的内存空间,而所有的线程共享一片相同的内存空间,注意勿与栈内存混淆,每个线程都拥有单独的栈内存用来存储本地数据。
29.Java中的线程的run( )方法和start()方法的区别
start()方法被用来启动新创建的线程,而且start( )内部调用了run ( )方法,这和直接调用run( )方法的效果不同,当调用run( )方法时,只会是在原来的线程中调用,没有新的线程启动,只有start( )方法才会启动新线程。
30.如何控制某个方法允许并发访问线程的个数
在Java中常使用Semaphore(信号量)来进行并发编程,Semaphore控制的是线程并发的数量,实例化一个Semaphore对象,如Semaphore semaphore = newSemaphore(5,true) ,其创建了对象semaphore,并初始化了5个信号量,即最多允许5个线程并发访问,在执行的任务中,调用semaphore的acquire()方法请求一个信号量,这时信号量个数就减1,(一旦没有可使用的信号量,再次请求就会阻塞),来执行任务,执行完任务,调用semaphore的release()方法释放一个信号量此时信号量的个数就会加1 。
31.Java中wait和sleep方法的不同
Java程序中wait和sleep都会造成某种形式的暂停,sleep()方法属于Thread类中,而wait( )方法属于Object类中,sleep( )方法是让程序暂停执行指定的时间,释放CPU资源,但不会释放锁,线程的监控状态依然保持着,当指定的时间到了又会自动恢复运行状态,而当调用wait( )方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用notify()方法后线程才进入对象锁定池准备,获取对象锁进入运行状态。
32.对Java中wait/notify关键字的理解
wait()、notify()、notifyAll( )都不属于Thread类,而是属于Object基础类,也就是每个对象都有wait( )、notify()、notifyAll( )的功能,因为每个对象都有锁,锁是每个对象的基础。
wait():会把持有该对象线程的对象控制权交出去,然后处于等待状态。
notify():会通知某个正在等待这个对象的控制权的线程可以运行。
notifyAll( ):会通知所有等待这个对象的控制权的线程继续运行,如果有多个正在等待该对象控制权时,具体唤醒哪个线程,就由操作系统进行调度。
33.什么是线程阻塞?线程该如何关闭?
阻塞式方法是指程序会一直等待该方法完成执行,而在此期间不做其他的事情,例如ServerSocket的accept( )方法就是一直等待客户端连接,这里的阻塞是指调用结果返回之前,当前线程会被挂起,直到得到结果之后才返回你,此外还有异步和非阻塞式方法在任务完成前就返回。
线程关闭的方法有如下两种:
一种是调用线程的stop( )方法;
另一种是自己自行设计一个停止线程的标记;
34.如何保证线程的安全
使用Synchronized关键字:
调用Object类的wait很notify;
通过ThreadLocal机制实现;
35.实现线程同步的方式
Java允许线程并发控制,当多个线程同时操作一个可共享的资源变量时(如数据的增删改查),将会导致数据的不准确,相互之间产生冲突,因此在加入同步锁以避免在该线程没有完成操作之前,被其他线程调用,从而保证该变量的唯一性和准确性,同步的方法有以下几种:
Synchronized关键字修饰的方法:由于Java的每个对象都有一个内置锁,当用此关键字修饰方法时,内置锁会保护整个方法;
Synchronized关键字修饰语句块:被Synchronized关键字修饰的语句块会自动加上内置锁,从而实现同步;
使用特殊域变量(volatile)实现线程同步:当一个共享变量被volatile修饰时,他会保证修改的值立即被更新到主存中,volatile的特殊性在于,内存可见性,就是一个线程对于volatile变量的修改,对于其他线程来说是可见的,即线程每次获取volatile变量的值都是最新的。
36.Java中Synchronized关键字的用法,以及对象锁、方法锁、类锁的理解
Java的内置锁:每个Java对象都可以用作一个实现同步的锁,这些锁称为内置锁,线程进入同步代码块或方法时,会自动获得该锁,在退出同步代码块或方法时会释放该锁,获得内置锁的唯一途径就是进入这个锁的保护的同步代码块或方法。
Java的内置锁是一个互斥锁,这即意味着最多只有一个线程获得该锁,当线程A尝试去获得线程B持有的内置锁时,线程A必须等待或阻塞,直到线程B释放该锁,如果B线程不释放该锁,那么A线程将一直等待下去。
Java的对象锁与类锁,对象锁是用于实例方法的,或者一个对象实例上的,类锁是用于类的静态方法或者一个类的class对象上的,类的对象实例可以有很多个,但每个类只有一个class对象,所以不同对象的实例的对象锁是互不干扰,但是每个类只有一个类锁。
Synchronized的用法:Synchronized修饰方法和修饰代码块。
37.Java中锁与同步的相关知识
锁提供了两种主要的特性:互斥和可见性
互斥即一次只允许一个线程持有某个特定的锁,因此可使用该特性实现对共享数据的协调访问协议,这样,一次就只有一个线程能够使用该共享的数据;可见性在必须确保锁释放之前对共享对象做出的更改对于随后获得该锁的另一个线程是可见的。
在Java中,为了确保多线程读写数据时的一致性,可以采用两种方式
同步:如使用synchronized关键字,或者使用锁对象;
使用volatile关键字:使变量的值发生改变时尽快通知其他线程;
Volatile关键字详解
编译器为了加快程序的运行的速度,对一些变量的写操作会先在寄存器或者CPU缓存上进行,最后写入内存中,而在这个过程中,变量的新值对于其他线程是不可见的,当对使用volatile标记的变量进行修改时,会将其它缓存中存储的修改前的变量清除,然后重新读取。
38.Synchronized和volatile关键字的区别
Volatile在本质上是告诉JVM当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取;Synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程会被阻塞。
Volatile仅能使用在变量级别,synchronized则可以使用在变量、方法和类级别。
Volatile仅能修改变量的可见性,不能保证原子性,而synchronized则可以保证变量的修改可见性和原子性。
Volatile不会造成线程的阻塞,synchronized可能会造成线程的阻塞。
Volatile标记的变量不会被编译器优化,synchronized标记的变量可以被编译器优化。
39.Java的原子性、可见性、有序性的理解
原子性:原子是世界上最小的物质单位,具有不可分割性,比如a=0,这个操作是不可分割的,那么我们就会说这个操作是原子操作,再如a++,这个操作实际上是a=a+1,是可以分割的,所以他不是一个原子操作,非原子操作都会存在线程安全的问题,需要使用synchronized同步技术来使其变成一个原子操作,一个操作是原子操作,那么我么称它具有原子性。
可见性:是指线程之间的可见性,一个线程修改的状态对于另一个线程是可见的,比如用volatile修饰的变量就具有可见性,volatile修饰的变量不允许线程内部缓存和重排序,即会直接修改内存,所以对其他线程是可见的,但volatile只能让其被修饰的内容具有可见性,并不能保证它具有原子性,因为volatile仅能使用在变量级别,并不能对方法进行修饰,
有序性:即线程执行的顺序按代码的先后顺序执行,在Java内存模型中,允许编译器和处理器对指令进行重排序,但重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性,在Java里面可以通过volatile关键字来保证一定的“有序性”,另外还可以通过synchronized和Lock来保证有序性。
40.ReentrantLock、Synchronized、Volatile关键字
Synchronized:即互斥锁,即操作互斥,并发线程,串行得到锁,串行执行代码,就像一个房间一把钥匙,一个人进去后,下一个人必须等到第一个人出来得到钥匙才能进去;
ReetrantLock:可重入锁,和同步锁功能类似,不过需要显性的创建和销毁,其特点在于①ReentrantLock有try Lock方法,如果锁被其他线程持有,返回false,可以避免形成死锁,②创建时可自定义是可抢占的,③ReentrantReadWriteLock,用于读多写少,且不需要互斥的场景大大提高性能;
Volatile:只保证同意变量在多线程中的可见,他会强制将对缓存的修改操作立即写入主存,如果是写操作,会导致其他CPU对应的缓存无效;
41.Java中死锁的概念,其产生的四个必要条件
死锁是一种情形,多个线程被阻塞,他们中的一个或者全部都在等待某个资源被释放,由于线程被无限期的阻塞,因此程序不能正常运行,简单的说就是线程死锁时,第一个线程等待第二个线程释放资源,而同时第二个线程又在等待第一个线程释放资源;
死锁产生的四个必要条件:
互斥条件:一个资源每次只能被一个进程使用,即在一段时间内某资源仅为一个进程所占有,此时若有其他请求该资源,则请求进程只能等待;
请求与保持条件:进程已经持有了至少一个资源,但又提出了新的资源请求,而该资源已经被其他进程所占有,此时请求会被阻塞,但对自己获得的资源又保持不放;
不可剥夺条件:进程所获得的资源在未使用完毕之前,不能被其他进程强行夺走,只能由获得该资源的进程自己来释放(只能时主动释放);
循环等待条件:即若干进程形成首尾相接的循环等待资源的关系,即形成了一个进程等待环路,环路中每一个进程所占有的资源同时被另一个进程所申请,也就是前一个进程占有后一个进程所申请的资源
这四个条件是死锁产生必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之一不满足,就不会发生死锁;
死锁的避免:避免死锁的基本思想是系统对进程发出的每一个系统能够满足的资源申请进行动态检查,并根据检查结果决定是否分配资源,如果分配资源后系统可能发生死锁,则不分配,否则予以分配,这是一种保证系统不进入死锁状态的动态策略。
42.Java中堆和栈的理解
在Java中内存分为两种,一种是栈内存,另一种是堆内存
堆内存:用于存储Java中的对象和数组,当我们new一个对象或创建一个数组的时候,就会在堆内存中开辟一段空间给它,用于存放,堆内存的特点:先进先出,后今后出,②可以动态的分配内存的大小,生存期不必告诉编译器,但存取速度较慢;
栈内存:主要用来执行程序用,比如基本类型的变量和对象的引用变量,其特点:①先进后出,后进后出,②存取速度比堆快,仅次于寄存器,栈数据可以共享,但其在栈中的数据大小和生存期必须是确定的;
栈内存和堆内存都属于Java内存的一种,系统会自动去回收它,但对于堆内存开发人员一般会自动回收。
栈是一块和线程紧密相关的内存区域,每个线程都有自己的栈内存,用于存储本地变量、方法参数和栈调用一个线程中存储的变量,对于其他线程是不可见的,而堆是所有线程共享的一个公用内存区域,对象都在堆里创建,但为了提升效率,线程会从堆中拷贝一个缓存到自己的栈中,如果多个线程使用该变量,就可能引发问题,这是volatile修饰变量就可以发挥作用,他要求线程从主存中读取变量的值。
43.理解线程间通信
线程是CPU调度的最小单位(进程是CPU分配资源的最小单位),在Android中主线程是不能够做耗时的操作的,子线程是不能更新UI的,而线程间的通信方式有很多,比如广播、接口回掉等,在Android中主要使用handler,handler通过调用sendMessage方法,将保存好的消息发送到MessageQueue中,而Looper对象不断地调用loop方法,从MessageQueue中取出message,交给handler处理,从而完成线程间通信。
44.线程中的join()方法的理解,及如何让多个线程按顺序执行
Thread类的join()方法主要作用是同步,它可以使线程之间的并行执行变为串行执行,join()方法把指定的线程加入到当前线程中,可以将两个交替执行的线程合并为顺序执行的线程,比如在线程B中调用了线程A的join()方法,则会等到A线程执行完,才会继续执行线程B,join方法还可以指定时长,表示等待该线程执行指定时长后再执行其他线程,如A.join(1000),表示等待A线程执行1000毫秒后,再执行其他线程,当有多个线程、要使他们案顺序执行时,可以使用join()方法在一个线程中启动另一个线程,另外一个线程在该线程执行完之后再继续执行。
45.工作者线程(workerThread)与主线程(UI线程)的理解
Android应用的主线程(UI线程)肩负着绘制用户界面,和及时响应用户操作的重任,为避免“用户点击按钮后没有反应”的状况,就要确保主线程时刻保持着较高的响应性,把耗时的任务移除主线程,交予工作者线程(即子线程)完成,常见的工作者线程有AsyncTask(异步任务)、IntentService、HandlerThread,他们本质上都是对线程或线程池的封装。
46.AsyncTask(异步任务)的工作原理
AsyncTask是对Handler和线程池的封装,使用它可以更新用户界面,当然,这里的更新操作还是在主线程中完成的,但由于AsyncTask内部包含了一个Handler,所以可以发送消息给主线程,让他更新UI,另外AsyncTask内还包含了一个线程池,避免了不必要的创建和销毁线程的开销。
47.并发和并行的区别及理解
在单核的机器上,“多进程”并不是真正多个进程同时执行,而是通过CPU时间分片,操作系统快速在进程间切换而模拟出来的多进程,我们通常将这种情况称为并发,也就是多个进程的运行行为是“一并发生”的,但不是同时执行的,因为CPU核数的限制(CPU和通用寄存器只有有一套),严格来说,同一时刻只能存在一个进程的上下文。
但在多核CPU上,能真正实现多个进程并行执行,这种情况叫做并行,因为多个进程是真正“一并执行的”(具体多少个进程可以并行执行取决于CPU的核数),所以可知,并发是一个比并行更加宽泛的概念,在单核情况下,并发只是并发,而在多核情况下,并发就变为并行了。
48.同步和异步的区别、阻塞和非阻塞的区别的理解
同步:所谓同步是一个服务的完成需要依赖其他服务,只有等待被依赖的服务完成后,依赖的服务才能算完成,这是一种可靠的服务序列,要么都成功,要么都失败服务的状态可以保持一致;
异步:所谓异步是一个服务的完成需要依赖其他的服务时,只要通知其依赖的服务开始执行,而不需要等待被依赖的服务完成,此时该服务就算完成了,至于被依赖的服务是否真正完成并不关心,所以这是不可靠的服务序列;
阻塞:阻塞调用是指调用结果返回之前,当前线程会被挂起,一直处于等待消息通知,不能够执行其他业务,函数只有在得到结果之后才会返回;
非阻塞:和阻塞的概念相对应,指在不能立刻得到结果之前,该函数不会阻塞当前线程,而会立即返回;
阻塞调用和同步调用是不同的,对于同步调用来说,很多时候当前线程可能还是激活的,只是从逻辑上当前函数没有返回值而已,此时这个线程可能也会处理其他消息,所以如下总结:
如果这个线程在等待当前函数返回时,仍在执行其他消息处理,这种情况叫做同步非阻塞;
如果这个线程在等待当前函数返回时,没有执行其他的消息处理,而是处于挂起等待的状态,这种情况叫做同步阻塞;
如果这个线程当前的函数已经返回,并且仍在执行其他的消息处理,这种情况叫做异步非阻塞;
如果这个线程当前的函数已经返回,但没有执行其他的消息处理,而是处于被挂起的等待状态,这种情况叫做异步阻塞;
同步与异步的重点在于的等待依赖的服务是否返回结果(即使没有执行完),也就是结果通知的方式,而不管其依赖的服务是否完成,阻塞与非阻塞的重点在于当前线程等待消息返回时的行为,是否执行其他的消息处理,当前线程是否被挂起;
49.Java中任务调度的理解
大部分操作系统(如windows、Linux)的任务调度采用时间片轮转的抢占式调度方式,也就是说一个任务执行一小段时间后强制暂停去执行下一个任务,每个任务轮流执行,任务执行的一段时间叫做时间片,任务正在执行的状态叫做运行状态,任务执行一段时间后强制暂停去执行下一个任务,被暂停的任务就处于就绪状态,等待下一个属于他的时间片的到来,这样每个任务都可以得到执行,由于CPU的执行效率非常高,时间片非常短,在各个任务之间快速的切换,给人的感觉是多个任务在“同时执行”,这也即我们所说的并发;
50.Java中进程的详细概念
计算机的额核心是CPU,它承担了所有的计算任务,操作系统是计算机的管理者,它负责任务的调度、资源的分配和管理,统领整个计算机的硬件,应用程序是具有某种功能的程序,其运行在操作系统上,而进程则是一个具有一定独立功能的程序在一个数据集上一次动态执行的过程,是操作系统进行资源分配和调度的一个独立单位,是应用程序运行的载体,进程是CPU资源分配的最小单位(线程是CPU执行的最小的单元),各个进程之间内存地址相互隔离。
51.线程的详细概念
线程是程序执行中一个单一的顺序控制流程,是程序执行流的最小单元,是处理器调度和分派的基本单位,一个进程可以有多个线程,各个线程之间共享程序的内存空间(也就是所在进程的内存空间),线程是一个进程中代码的不同执行路线,线程上下文切换比进程上下文切换要快得多。
52.Android中的性能优化相关问题
由于手机硬件的限制,在Android手机中过多的使用内存,会容易导致oom(out of memory 内存溢出),过多的使用CPU资源,会导致手机卡顿,甚至导致ANR(Application Not Responding 应用程序无响应),Android主要从以下几部分对此进行优化:
布局优化,使用herarchyviewer(视图层次窗口)工具删除无用的空间和层级;
选择性能较低的viewgroup,比如在可以选择RelativeLayout也可以使用LinearLayout的情况下,优先使用LinearLayout,因为相对来说RelativeLayout功能较为复杂,会占用更多的CPU资源;
使用标签重用布局、减少层级、进行预加载(用的时候才加载);
绘制优化:指view在ondraw方法中避免大量的耗时的操作,由于onDraw方法可能会被频繁的调用;
ondraw方法中不要创建新的局部变量,ondraw方法被频繁的调用,很容易引起GC;
ondraw方法不要做耗时的操作;
线程优化:使用线程池来管理和复用线程,避免程序中出现大量的Thread,同时可以控制线 的并发数,避免相互抢占资源,而导致线程阻塞;
53.内存泄漏的相关原因
如果一个无用的对象(不需要再使用的对象)仍然被其他对象持有引用,造成该对象无法被系统回收,以致该对象在堆中所占有的内存单元无法被释放而造成内存空间浪费,这种情况就是内存泄漏,常见的内存泄露的场景有:
单例模式:因为单例的静态特性使得他的生命周期同应用的生命周期一样长,如果一个对象已经没有用处了,但是单例还持有他的引用,那么在某个应用程序的生命周期他都不能正常回收,从而导致内存泄漏;
静态变量导致内存泄漏:静态变量存储在方法区,他的生命周期从类加载开始,到整个进程结束,一旦静态变量初始化,他所持有的引用只有等到进程结束才会释放;
非静态内部类导致内存泄漏:非静态内部类(包括匿名内部类)默认就会持有外部类的引用,当非静态内部类对象的生命周期比外部对象的生命周期长时,就会导致内存泄漏,通常在Android开发中,如果要使用内部类,但又要规避内存泄漏,一般会采用静态内部类+弱引用的方式;
未取消注册或回掉导致内存泄漏:比如在Activity中注册广播,如果在Activity销毁后不取消注册,那么这个广播会一直存在于系统中,同非静态内部类一样持有Activity引用,导致内存泄漏,因此在注册广播后一定要取消注册;
54.通过Handler在线程间通信的原理
Android中主线程是不能进行耗时操作的,子线程不能进行更新UI,所以就有了Handler,其作用就是实现线程之间的通信。
Handler在整个线程通信的过程中,主要有四个对象,Handler、Message、MessageQueue、Looper等,当应用创建时,就会在主线程中创建Handler对象,将要传递的信息保存到Message中,Handler通过调用sendMessage()方法将Message发送到MessageQueue中,Looper对象不断调用Loop( )方法从MessageQueue中取出Message交给handler进行处理,从而实现线程之间的通信。
55.Android中动画的类型:
帧动画:通过指定每一帧图片和播放的时间,有序地进行播放而形成动画效果;
补间动画:通过指定的view的初始状态、变化时间、方式等,通过一系列的算法去进行图形变换从而形成动画效果,主要有Alpha、Scale、Translate、Rotate四种效果;
属性动画:在Android3.0开始支持,通过不断改变view的属性,不断的重绘而形成动画效果;
55.理解Activity、View、Window三者之间的关系
使用一个比喻形容他们的关系,Activity像一个工匠(控制单元),Window像窗户(承载模型)、View像窗花(显示视图)、LayoutInflater像剪刀、XML配置像窗花图纸:
Activity构造的时候会初始化一个Window,准确的说应该是PhoneWindow;
这个PhoneWindow有一个”ViewRoot“,这个”ViewRoot”是一个View或者说是ViewGroup,是最初的根视图;
“ViewRoot”是通过addView方法来添加一个个View的,比如TextView、Button等;
这些view的事件的监听,是由WindowManagerService来接收消息,并且回调Activity函数,比如onClickListener、onKeyDown等;
56.Android中Context详解:
Context是一个抽象基类,译为上下文,也可理解为环境,是用户操作操作系统的一个过程,这个对象描述的是一个应用程序环境的全局信息,通过它可以访问应用程序的资源和相关的权限,简单的说Context负责Activity、Service、Intent、资源、Package和权限等操作。Context层次如下图:
第一层:Context抽象类;
第二层:一个ContextImpl的实现类,里面拥有一个PackageInfo类,PackageInfo类是关于整
以上是关于2021年Android面试题及答案收集(不断更新中)的主要内容,如果未能解决你的问题,请参考以下文章
2022年Android面试题及答案汇总,每天20题持续更新中...(从面试官角度帮你审视问题)
2022年Android面试题及答案汇总,每天20题持续更新中...(从面试官角度帮你审视问题)