收集

ANR 出现的原因汇总如下:
1.主线程执行了耗时操作,比如数据库操作或网络编程
2.其他进程(就是其他程序)占用 CPU 导致本进程得不到 CPU 时间片,比如其他进程的频繁读写操作可能会导致这个问题。
细分的话,导致 ANR 的原因有如下几点:
1.耗时的网络访问
2.大量的数据读写
3.数据库操作
4.硬件操作(比如 camera)
5.调用 thread 的 join()方法、sleep()方法、wait()方法或者等待线程锁的时候
6.service binder 的数量达到上限
7.system server 中发生 WatchDog ANR
8.service 忙导致超时无响应
9.其他线程持有锁,导致主线程等待超时
10.其它线程终止或崩溃导致主线程一直等待

为什么出现ANR
1:主线程频繁进行耗时IO操作,如数据库,文件读写
2:主线程操作死锁
3:主线程被binder,对端block
4:service binder连接达到上线

synchronized可以用在如下地方
修饰实例方法,对当前实例对象this加锁
修饰静态方法,对当前类的Class对象加锁
修饰代码块,指定加锁对象,对给定对象加锁

启动优化
1:合理使用异步初始化,延迟初始化,懒加载机制
2:启动过程避免耗时操作,如数据库,I/O不要放在主线程
3:类初始化提前加载
4:合理使用IdleHandler( Looper.myQueue().addIdleHandler)
5:简化布局
6:背景图片设置,后面取消

说一下 jvm 的主要组成部分?及其作用?
类加载器(ClassLoader)
运行时数据区(Runtime Data Area)
执行引擎(Execution Engine)
本地库接口(Native Interface)
组件的作用: 首先通过类加载器(ClassLoader)会把 Java 代码转换成字节码,运行时数据区(Runtime Data Area)再把字节码加载到内存中,而字节码文件只是 JVM 的一套指令集规范,并不能直接交个底层操作系统去执行,因此需要特定的命令解析器执行引擎(Execution Engine),将字节码翻译成底层系统指令,再交由 CPU 去执行,而这个过程中需要调用其他语言的本地库接口(Native Interface)来实现整个程序的功能。

vm 运行时数据区?
程序计数器、虚拟机栈、本地方法栈、堆、方法区

说一下堆栈的区别?

  1. 栈内存存储的是局部变量而堆内存存储的是实体;
  2. 栈内存的更新速度要快于堆内存,因为局部变量的生命周期很短;
  3. 栈内存存放的变量生命周期一旦结束就会被释放,而堆内存存放的实体会被垃圾回收机制不定时的回收。

队列和栈是什么?有什么区别?
1.队列和栈都是被用来预存储数据的。
2.队列允许先进先出检索元素,但也有例外的情况,Deque 接口允许从两端检索元素。
3.栈和队列很相似,但它运行对元素进行后进先出进行检索。

说一下类加载的执行过程?
类加载分为以下 5 个步骤:
加载:根据查找路径找到相应的 class 文件然后导入;
检查:检查加载的 class 文件的正确性;
准备:给类中的静态变量分配内存空间;
解析:虚拟机将常量池中的符号引用替换成直接引用的过程。符号引用就理解为一个标示,而在直接引用直接指向内存中的地址;
初始化:对静态变量和静态代码块执行初始化工作。

Java 定义的 GC ROOTS 的对象:
虚拟机栈(栈帧中的本地变量表)中引用的对象(参考下图)
方法区的类静态属性引用的对象
方法区中的常量引用的对象
本地方法栈中 JNI 引用的对象

「可作为GCRoot的点」:
1 方法区中静态属性 和 常量 引用的对象
2 虚拟机栈 和 本地方法栈中引用的对象
3 活跃线程的成员变量

怎么判断对象是否可以被回收?
一般有两种方法来判断:
引用计数器:为每个对象创建一个引用计数,有对象引用时计数器 +1,引用被释放时计数 -1,当计数器为 0 时就可以被回收。它有一个缺点不能解决循环引用的问题;
可达性分析:从 GC Roots 开始向下搜索,搜索所走过的路径称为引用链。当一个对象到 GC Roots 没有任何引用链相连时,则证明此对象是可以被回收的。

事件分发来源步骤
IMS从系统底层接收到事件之后,会从WMS中获取window信息,并将事件信息发送给对应的viewRootImpl
viewRootImpl接收到事件信息,封装成motionEvent对象后,发送给管理的view
view会根据自身的类型,对事件进行分发还是自己处理
顶层viewGroup一般是DecorView,DecorView会根据自身callBack的情况,选择调用callBack或者调用父类ViewGroup的方法
而不管顶层viewGroup的类型如何,最终都会到达ViewGroup对事件进行分发。

内存回收大多数情况下都是被动回收的,我们就来分析下被动回收的流程:
1 LargeObj large = new LargeObj();这一行代码会创建两个对象,一个是large这个引用,放在方法栈里面的;一个是new LargeObj()这个对象,放在堆里面的,此时发现堆里面放不下LargeObj()这个对象,就会触发gc;
2 此时开始进行回收,gc会检测堆里面的所有对象,我们可以把堆理解为一个表格,gc会从上到下,从左到右,一行一行检测当前对象是否可被回收,如果是无效对象,或者是弱引用,则直接回收;如果是软引用,则仅标记;
3 经过第2步后,gc回收了一些无效对象和弱引用,然后看此时内存够用吗,够用就直接跳转到6步,不够则向下执行第4步;
4 此时发现回收过后内存还不够,于是就回收第2步标记的软引用;
5 经过第4步,回收了软引用后,内存够用吗,够用则跳转到第6步,不够则直接抛出OOM异常;
6 直接在堆空间给LargeObj()分配内存,并且将large放入方法栈中,指向LargeObj()这个对象;

我们先来看对象的内存分配分配流程(现在假设新生代和老年代都是空的):
1 创建一个对象User user = new User();
2 新创建的new User()对象首先会分配在新生代,如果新生代能放得下的话。
3 如果新生代放不下,则尝试对新生代进行一次gc(),称为MinorGC,此次gc()过后,所有存活的对象的年龄都+1,如果+1后年龄达到了15,则这个对象会直接移到老年代,我们可以简称为"年龄达标"。
4 如果此次gc()后,新生代还是放不下new User()对象,则直接放入老年代,我们可以简称为"体积达标"。
5 如果老年代也放不下的话,则会对老年代进行一次gc(),称为MajorGC。
6 如果gc()后,还放不下,则进行二次gc(),也就是回收软引用的过程。
7 如果还是放不下,则抛出OOM异常。

Glide和Fresco的区别
Glide:
多种图片格式的缓存,适用于更多的内容表现形式(如Gif、WebP、缩略图、Video)
生命周期集成(根据Activity或者Fragment的生命周期管理图片加载请求)
高效处理Bitmap(bitmap的复用和主动回收,减少系统回收压力)
高效的缓存策略,灵活(Picasso只会缓存原始尺寸的图片,Glide缓存的是多种规格),加载速度快且内存开销小(默认Bitmap格式的不同,使得内存开销是Picasso的一半)
Fresco:
最大的优势在于5.0以下(最低2.3)的bitmap加载。在5.0以下系统,Fresco将图片放到一个特别的内存区域(Ashmem区)
大大减少OOM(在更底层的Native层对OOM进行处理,图片将不再占用App的内存)
适用于需要高性能加载大量图片的场景
对于一般App来说,Glide完全够用,而对于图片需求比较大的App,为了防止加载大量图片导致OOM,Fresco 会更合适一些。并不是说用Glide会导致OOM,Glide默认用的内存缓存是LruCache,内存不会一直往上涨。

requestLayout和invalidate和postInvalidate的区别
1.requestLayout方法会重新走三大流程,所以比较耗性能,所以尽可能的不要频繁的调用requestLayout方法。
2.在layout阶段调用requestLayout方法不会立即执行,而是要等当前的layout阶段执行完毕才会执行。这就解决了requestLayout死递归的问题。
3.invalidate方法和postInvalidate方法的区别在于, invalidate方法只能在UI线程里面调用,而postInvalidate方法是无所谓线程。
4.requestLayout方法、invalidate方法和postInvalidate任务的驱动,最后依靠一个叫Choreographer类实现的。我们只是往Choreographer的任务
队列post一个Callback对象,就能保证下次屏幕刷新时能执行。

volatile和synchronized区别
volatile本质是在告诉JVM当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取;synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。
volatile仅能使用在变量级别;synchronized则可以使用在变量、方法和类级别的;
volatile仅能实现变量的修改可见性,不能保证原子性;而synchronized则可以保证变量的修改可见性和原子性;
volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞。
volatile标记的变量不会被编译器优化;synchronized标记的变量可以被编译器优化。

疑难为题
1:LiveData 数据倒灌 不多次绑定,分发的时候,只分发一次
2:应用安装完成后打开应用出现初始化两次 解决oncreate里面判断处理
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

    //防止重复创建的问题,第一次安装完成启动,和home键退出点击launcher icon启动会重复
    if (!isTaskRoot()
            && getIntent().hasCategory(Intent.CATEGORY_LAUNCHER)
            && getIntent().getAction() != null
            && getIntent().getAction().equals(Intent.ACTION_MAIN)) {

        finish();
        return;
    }
    }

3:华为手机获取拍照权限后拍照,返回值为空
问题起源:
开发中遇到了需要拍照和从图库中选择图片展示并上传的功能,其他手机测试没问题,华为手机获取拍照权限后拍照,返回值为空。
问题分析:
原来是华为在7.0以后的系统中,对于拍照后返回的图片也做了权限处理。所以说,华为7.0在拍照的时候,不仅要拿到拍照 CAMERA 的权限,还要拿到读写文件的权限 WRITE_EXTERNAL_STORAGE。
4:华为机型
Toast不显示 (荣耀手机)
也是偶然发现在荣耀8机型上后台应用的toast不会被显示出来,只能当前在前台的应用TOAST提示。
解决方案:替换toast显示,改用其他方式,如dialog或者弹出activity界面。
5.屏幕手势Touch事件
最近在做监听Touch事件时候,在onInterceptTouchEvent拦截事件时候,拦截Move事件,开发时在华为手机上没有什么问题,而在vivo和魅族手机上,却发现点击屏幕时,手势down之后,不移动在这两个手机上却发出了move事件,并且打印的getY值是一样的,这导致了出现了拦截这次点击事件,但是点击事件收不到。而回头到华为手机上时,只有down事件,一旦移动才会有move事件发生,这才是正确的事件分发。
解决办法:故为了兼容,才在move中判断下move下移动距离必须大于0才拦截
6:状态栏内容字体颜色 每个手机背景颜色可能不一样

内存泄露总结
当某些对象不再被程序所使用,但是这些对象仍然被某些对象所引用着,进而导致垃圾收集器不能及时释放它们。

造成内存泄露的原因有:
1.静态集合类引起的内存泄露
像HashMap、Vector等的使用最容易出现内存泄露,这些静态变量的生命周期和应用程序一致,他们所引用的所有的对象Object也不能被释放,因为他们也将一直被Vector等引用着。
2.当集合里面的对象属性被修改后,再调用remove()方法时不起作用。
3.释放对象的时候没有删除监听器
4.没有关闭连接,例如数据库连接(dataSourse.getConnection()),网络连接(socket)和io连接
5.内部类和外部模块的引用
6.单例模式,不正确使用单例模式是引起内存泄漏的一个常见问题,单例对象在初始化后将在JVM的整个生命周期中存在(以静态变量的方式),如果单例对象持有外部的引用,那么这个对象将不能被JVM正常回收,导致内存泄漏。
7.Handler 造成的内存泄漏(由于Handler是非静态内部类所以其持有当前Activity的隐式引用,如果Handler没有被释放,其所持有的外部引用也就是Activity也不可能被释放)

内存溢出(OOM)
避免OOM有以下几种方向:
防止内存泄露
避免OOM就是要剩余足够堆内存供应用使用,要想内存足够呢,首先就需要避免应用存在内存泄漏的情况,内存泄漏后,可使用的内存空间减少,自然就会更容易产生OOM。
减小对象的内存占用
1.使用更加轻量的数据结构,例如,我们可以考虑使用ArrayMap/SparseArray而不是HashMap等传统数据结构
2.减小Bitmap对象的内存占用
内存对象的重复利用
1.复用系统自带的资源
2.注意在ListView/GridView等出现大量重复子组件的视图里对ConvertView的复用
3.Bitmap对象的复用
4.代码中会需要使用到大量的字符串拼接的操作,这种时候有必要考虑使用StringBuilder来替代频繁的“+

ANR异常总结:
1、ANR错误一般有三种类型
1.KeyDispatchTimeout(5 seconds) --主要是类型按键或触摸事件在特定时间内(5s)无响应
2.BroadcastTimeout(10 seconds) --BroadcastReceiver在特定时间内(10s)无法处理完成
3.ServiceTimeout(20 secends) --小概率事件 Service在特定的时间内无法处理完成
2、哪些操作会导致ANR 在主线程执行以下操作:
1.高耗时的操作,如图像变换
2.磁盘读写,数据库读写操作
3.大量的创建新对象
3、如何避免
1.UI线程尽量只做跟UI相关的工作
2.耗时的操作(比如数据库操作,I/O,连接网络或者别的有可能阻塞UI线程的操作)把它放在单独的线程处理
3.尽量用Handler来处理UIThread和别的Thread之间的交互

Android的几种进程
1.前台进程:即与用户正在交互的Activity或者Activity用到的Service等,如果系统内存不足时前台进程是最后被杀死的
2.可见进程:可以是处于暂停状态(onPause)的Activity或者绑定在其上的Service,即被用户可见,但由于失去了焦点而不能与用户交互
3.服务进程:其中运行着使用startService方法启动的Service,虽然不被用户可见,但是却是用户关心的,例如用户正在非音乐界面听的音乐或者正在非下载页面自己下载的文件等;当系统要空间运行前两者进程时才会被终止
4.后台进程:其中运行着执行onStop方法而停止的程序,但是却不是用户当前关心的,例如后台挂着的QQ,这样的进程系统一旦没了有内存就首先被杀死
5.空进程:不包含任何应用程序的程序组件的进程,这样的进程系统是一般不会让他存在的
如何避免后台进程被杀死:
1.调用startForegound,让你的Service所在的线程成为前台进程
2.Service的onStartCommond返回START_STICKY或START_REDELIVER_INTENT
3.Service的onDestroy里面重新启动自己

java线程池

线程池中的几种重要的参数
corePoolSize就是线程池中的核心线程数量,这几个核心线程,只是在没有用的时候,也不会被回收
maximumPoolSize就是线程池中可以容纳的最大线程的数量
keepAliveTime,就是线程池中除了核心线程之外的其他的最长可以保留的时间,因为在线程池中,除了核心线程即使在无任务的情况下也不能被清除,其余的都是有存活时间的,意思就是非核心线程可以保留的最长的空闲时间,
util,就是计算这个时间的一个单位。
workQueue,就是等待队列,任务可以储存在任务队列中等待被执行,执行的是FIFIO原则(先进先出)。
threadFactory,就是创建线程的线程工厂。
handler,是一种拒绝策略,我们可以在任务满了之后,拒绝执行某些任务。

说说线程池的拒绝策略
AbortPolicy策略:该策略会直接抛出异常,阻止系统正常工作。
CallerRunsPolicy 策略:只要线程池未关闭,该策略直接在调用者线程中,运行当前的被丢弃的任务。
DiscardOleddestPolicy策略: 该策略将丢弃最老的一个请求,也就是即将被执行的任务,并尝试再次提交当前任务。
DiscardPolicy策略:该策略默默的丢弃无法处理的任务,不予任何处理。

execute和submit的区别?
execute适用于不需要关注返回值的场景,只需要将线程丢到线程池中去执行就可以了。
submit方法适用于需要关注返回值的场景

线程池的关闭
关闭线程池可以调用shutdownNow和shutdown两个方法来实现
shutdownNow:对正在执行的任务全部发出interrupt(),停止执行,对还未开始执行的任务全部取消,并且返回还没开始的任务列表。
shutdown:当我们调用shutdown后,线程池将不再接受新的任务,但也不会去强制终止已经提交或者正在执行中的任务。

线程池都有哪几种工作队列
1、ArrayBlockingQueue
是一个基于数组结构的有界阻塞队列,此队列按 FIFO(先进先出)原则对元素进行排序。
2、LinkedBlockingQueue
一个基于链表结构的阻塞队列,此队列按FIFO (先进先出) 排序元素,吞吐量通常要高于ArrayBlockingQueue。静态工厂方法Executors.newFixedThreadPool()使用了这个队列
3、SynchronousQueue
一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQueue,静态工厂方法Executors.newCachedThreadPool使用了这个队列。
4、PriorityBlockingQueue
一个具有优先级的无限阻塞队列。

断点下载
conn.setRequestProperty("Range", "bytes="+startIndex + "-" + endIndex);
//若请求头加上Range这个参数,则返回状态码为206,而不是200
需要RandomAccessFile 这个对象,可以插入数据到指定位置

Webview 优化

  • WebView在Application中提前初始化
  • 实现WebView复用
  • 另开WebView进程
  • DNS解析优化(接口与网页主域名一致)
  • 线上资源压缩、CDN加速
  • 静态直出,直接下发首屏html
  • 离线预推,下发离线包,并增量更新
  • WebView创建与网络请求并行
  • 网页按节点局部刷新
  • 自定义实现图片资源缓存
  • 重新定义图片加载格式,shareP
  • 本地资源拦截替换

ReentrantLock常常对比着synchronized来分析,我们先对比着来看然后再一点一点分析。
(1)synchronized是独占锁,加锁和解锁的过程自动进行,易于操作,但不够灵活。ReentrantLock也是独占锁,加锁和解锁的过程需要手动进行,不易操作,但非常灵活。
(2)synchronized可重入,因为加锁和解锁自动进行,不必担心最后是否释放锁;ReentrantLock也可重入,但加锁和解锁需要手动进行,且次数需一样,否则其他线程无法获得锁。
(3)synchronized不可响应中断,一个线程获取不到锁就一直等着;ReentrantLock可以相应中断。
ReentrantLock好像比synchronized关键字没好太多,我们再去看看synchronized所没有的,一个最主要的就是ReentrantLock还可以实现公平锁机制。什么叫公平锁呢?也就是在锁上等待时间最长的线程将获得锁的使用权。通俗的理解就是谁排队时间最长谁先执行获取锁。

Android的Activity管理和关系
AMS初始化时会创建一个ActivityStackSupervisor对象,ActivityStackSupervisor创建和管理Android系统中所有应用的ActivityStack(应用的Activity栈),一个ActivityStack对应和包含一个应用所启动的所有activity,一个ActivityStack包含一或多个TaskRecord(Task栈、任务栈,Activity启动模式中所说的栈,将一系列功能关联的Activity通过Task的概念
来统一存放和标识),一个TaskRecord包含一到多个ActivityRecord(包含Activity所有信息)。

onNewIntent
onNewIntent()非常好用,Activity第一启动的时候执行onCreate()---->onStart()---->onResume()等后续生命周期函数,也就时说第一次启动Activity并不会执行到onNewIntent(). 而后面如果再有想启动Activity的时候,那就是执行onNewIntent()---->onResart()------>onStart()----->onResume(). 如果android系统由于内存不足把已存在Activity释放掉了,那么再次调用的时候会重新启动Activity即执行onCreate()---->onStart()---->onResume()等。
当调用到onNewIntent(intent)的时候,需要在onNewIntent() 中使用setIntent(intent)赋值给Activity的Intent.否则,后续的getIntent()都是得到老的Intent。

多线程实际踩坑
 1.注意成员变量与局部变量
2.现在多数个人电脑为4核8线程,服务器为8核16线程.为更优的用好多线程,避免无关的线程阻塞和资源浪费,需在使用线程池时预设最大线程数
3.线程池回收线程时,已设置的名字的线程,回收线程名后不会改变

Java的线程生命周期有六种状态:
New(初始化状态)Runnable(可运行/运行状态)Blocked(阻塞状态)Waiting(无时间限制的等待状态)Timed_Waiting(有时
间限制的等待状态)Terminated(终止状态)1.New(初始化状态):指的是在高级语言,比如Java。在Java层面的线程被创
建了,而在操作系统中的线程其实是还没被创建的,所以这个时候是不可能分配CPU执行这个线程的!所以这个状态是
高级语言独有的,操作系统的线程没这个状态。我们New了一个线程,那时候它就是这个状态。
2.Runnable(可运行/运行状态):这个状态下是可以分配CPU执行的,在New状态时候我们调用start()方法后线程就处于
这个状态。
3.Blocked(阻塞状态):这个状态下是不能分配CPU执行的,只有一种情况会导致线程阻塞,就是synchronized!我们知
道被synchronized修饰的方法或者代码块同一时刻只能有一个线程执行,而其他竞争锁的线程就从Runnable到了
Blocked状态!当某个线程竞争到锁了它就变成了Runnable状态。注意并发包中的Lock,是会让线程属于等待状态而不
是阻塞,只有synchronized是阻塞。(感觉是历史遗留问题,没必要多一个阻塞状态和等待没差啊)。
4.Waiting(无时间限制的等待状态):这个状态下也是不能分配CPU执行的。有三种情况会使得Runnable状态到waiting
状态
调用无参的Object.wait()方法。等到notifyAll()或者notify()唤醒就会回到Runnable状态。调用无参的Thread.join()方
法。也就是比如你在主线程里面建立了一个线程A,调用A.join(),那么你的主线程是得等A执行完了才会继续执行,这
是你的主线程就是等待状态。调用LockSupport.park()方法。LockSupport是Java6引入的一个工具类Java并发包中的锁
都是基于它实现的,再调用LocakSupport.unpark(Thread thread),就会回到Runnable状态。5.Timed_Waiting(有时
间限制的等待状态):其实这个状态和Waiting就是有没有超时时间的差别,这个状态下也是不能分配CPU执行的。有五
种情况会使得Runnable状态到waiting状态
Object.wait(long timeout)。Thread.join(long millis)。Thread.sleep(long millis)。注意 Thread.sleep(long millis, int
nanos) 内部调用的其实也是Thread.sleep(long millis)。LockSupport.parkNanos(Object blocked,long deadline)。
LockSupport.parkUntil(long deadline)。6.Terminated(终止状态):在我们的线程正常run结束之后或者run一半异常了
就是终止状态!注意有个方法Thread.stop()是让线程终止的,但是这个方法已经被废弃了,不推荐使用,因为比如你这
个线程得到了锁,你stop了之后这个锁也随着没了,其它线程就都拿不到这个锁了!这不玩完了么!所以推荐使用
interrupt()方法。
interrupt()会使得线程Waiting和Timed_Waiting状态的线程抛出 interruptedException异常,使得Runnabled状态的线
程如果是在I/O操作会抛出其它异常。
如果Runnabled状态的线程没有阻塞在I/O状态的话,那只能主动检测自己是不是被中断了,使用isInterrupted()。

1.9.4 线程死锁的原因 & 举个例子 & 如何避免死锁。(校招&实习)
线程死锁
线程死锁的现象
两个或两个以上的线程在执行过程中,因争夺资源而造成的互相等待的现象,在无外力作用的情况下,这些线程会一直
互相等待而无法继续运行下去.
线程死锁的四个条件
互斥条件
资源只能被一个线程占用,如果其它线程请求获取该资源,则请求者只能等待,直到占用资源的线程释放该资源.
请求并持有条件
指一个线程已经持有了至少一个资源,但又提出了新的资源请求,而新的资源已被其他线程占用,所以当前线程会被阻
塞,但阻塞的同时不释放自己获取的资源.
不可剥夺条件
获取到的资源在自己使用完之前不能被其他线程抢占,只能在使用完之后释放.
环路等待条件
发生死锁的时候必然存在一个线程-资源的环形链,即线程集合{T0,T1,T2,…Tn}中的T0正在等待一个T1占用的资源,T1
正在等待T2占用的资源,…Tn正在等待T1占用的资源.

Synchronized放在静态方法和非静态方法上的锁对象分别是什么?(校招&实习)
Synchronized 修饰非静态方法的锁,其实是属于当前对象的锁,属于对象锁
Synchronized 修饰静态方法的锁,其实是属于当前类,属于类锁 ,类锁是一个抽象概念,为了区别对象锁
静态方法的锁属于类, 一个类中所有加锁的静态方法共用该锁
非静态方法的锁属于对象, 一个对象中所有加锁的非静态方法共用, 和静态方法的锁不同而互不相干
加锁的方法的执行不会影响同一个类/对象中未加锁的方法的执行

image.png

2.1 线程池的优势
①:线程复用,减少线程创建、销毁的开销,提高性能 ②:提高响应速度,当任务到达时,无需等待线程创建就能立即
执行。 ③:提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资 源,还会降低系统的稳定
性,使用线程池可以进行统一的分配,调优和监控。

什么时候用线程池?
①:单个任务处理时间比较短 ②:需要处理的任务数量很大

线程池
①:execute(Runnable command):执行行Ruannable类型的任务 ②:submit(task):可用来提交Callable或
Runnable任务,并返回代表此任务的Future对象 ③:shutdown():温柔的关闭线程池,停止接受新任务,并执行
完未完成的任务。 ④:shutdownNow():强制关闭线程池,未完成的任务会以列表形式返回! ⑤:
isTerminated():返回所有任务是否执行完毕。当调用shutdown()方法后,并且所有提交的任务完成后返回为true;
当调用shutdownNow()方法后,成功停止后返回为true; ⑥:isShutdown():返回线程池是否关闭,当调用
shutdown()或shutdownNow()方法后返回为true。

Thread.yield方法的作用
和 sleep 一样都是 Thread 类的方法,都是暂停当前正在执行的线程对象,不会释放资源锁,和 sleep 不同的是 yield
方法并不会让线程进入阻塞状态,而是让线程重回就绪状态,它只需要等待重新获取CPU执行时间,所以执行yield()的
线程有可能在进入到可执行状态后马上又被执行。还有一点和 sleep 不同的是 yield 方法只能使同优先级或更高优先级
的线程有执行的机会

image.png

3.1 线程池七大参数
1.corePoolSize核心线程数。当提交一个任务时,线程池会创建一个新线程执行任务,此时线程不会复用。如果当前线
程数为corePoolSize,继续提交的任务被保存到阻塞队列中,等待被执行。此时如果核心线程有空闲,回去阻塞队列中
领取任务,此时核心线程复用。
2.maximumPoolSize最大线程数。线程池中允许的最大线程数。如果当前阻塞队列满了,且继续提交任务,则创建新
的线 程执行任务,前提是当前线程数小于maximumPoolSize;
3.keepAliveTime超时时间。当线程池中的线程数量大于corePoolSize的时 候,如果这时没有新的任务提交,核心线程
外的线程不会立即销毁,而是会等待,直到等待的时间超过了keepAliveTime才会被销毁,最终会收缩到corePoolSize
的大小。
4.unit超时时间单位。 keepAliveTime的时间单位
5.workQueue阻塞队列。用来保存等待被执行的任务的阻塞队列,且任务必须实现Runable接口,在JDK中提供了如下
阻塞队列:
①:ArrayBlockingQueue:基于数组结构的有界阻塞队列,按FIFO排序任务; ②:LinkedBlockingQuene:基于链表
结构的阻塞队列,按FIFO排序任务,吞吐量通常要高于ArrayBlockingQuene; ③:SynchronousQuene:一个不存储
元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高
于 LinkedBlockingQuene; ④:priorityBlockingQuene:具有优先级的无界阻塞队列;
6.threadFactory线程工厂。它是ThreadFactory类型的变量,用来创建新线程。默认使用
Executors.defaultThreadFactory() 来创建线程。
7.handler拒绝策略。线程池的饱和策略,当阻塞队列满了,且没有空闲的工作线程,如果继续提交任务,必须采取一
种策略处理该任务,线程池提供了4种策略:
①:AbortPolicy:直接抛出异常,默认策略; ②:CallerRunsPolicy:用调用者所在的线程来执行任务; ③:
DiscardOldestPolicy:丢弃阻塞队列中靠最前的任务,并执行当前任务; ④:DiscardPolicy:直接丢弃任务;

3.3 线程池工作原理
①、在创建了线程池后,开始等待请求。 ②、当调用execute()方法添加一个请求任务时,线程池会做出如下判断:

  1. 如果正在运行的线程数量小于corePoolSize,那么马上创建线程运行这个任务;
  2. 如果正在运行的线程数量大于或等于corePoolSize,那么将这个任务放入队列;
  3. 如果这个时候队列满了且正在运行的线程数量还小于maximumPoolSize,那么还是要创建非核心线程立刻运行这
    个任务;
  4. 如果队列满了且正在运行的线程数量大于或等于maximumPoolSize,那么线程池会启动饱和拒绝策略来执行。
    ③、当一个线程完成任务时,它会从队列中取下一个任务来执行。 ④、当一个线程无事可做超过一定的时间
    (keepAliveTime)时,线程会判断:
  5. 如果当前运行的线程数大于corePoolSize,那么这个线程就被停掉。
  6. 所以线程池的所有任务完成后,它最终会收缩到corePoolSize的大小。

线程池抛异常会怎样?
如果线程池中执行时,其中一个线程出现异常,会影响其他线程执行吗? 不会。在线程池内部如果其中一个线程出现异
常,后边的线程仍然可以执行。

image.png

给我谈谈你是如何保证线程数据安全问题的?
不可变(final)
在java语言中,不可变的对象一定是线程安全的,无论是对象的方法实现还是方法的调用者,都不需要再采取任何的线
程安全保障措施。final关键字修饰的类或数据不可修改,可靠性最高。如 String类,Integer类。
线程封闭
把对象封装到一个线程里,只有一个线程能看到这个对象,那么这个对象就算不是线程安全的,也不会出现任何线程安
全方面的问题。线程封闭有三种:
Ad-hoc 线程封闭
Ad-hoc线程封闭是指维护线程封闭性的职责完全由程序实现来承担。Ad-hoc线程封闭是非常脆弱的,因为没
有任何一种语言特性,例如可见性修饰符或局部变量,能将对象封闭到目标线程上。事实上,对线程封闭对
象(例如,GUI应用程序中的可视化组件或数据模型等)的引用通常保存在公有变量中。
ThreadLocal
线程封闭
它是一个特别好的封闭方法,其实ThreadLocal内部维护了一个map,map的key是每个线程的名称,而map
的value就是我们要封闭的对象。ThreadLocal提供了get、set、remove方法,每个操作都是基于当前线程
的,所以它是线程安全的。
堆栈封闭
堆栈封闭其实就是方法中定义局部变量。不存在并发问题。多个线程访问一个方法的时候,方法中的局部变
量都会被拷贝一份到线程的栈中(Java内存模型),所以局部变量是不会被多个线程所共享的。
同步
悲观锁
非阻塞同步(乐观锁)
锁优化(过度优化)
悲观锁
同步的最常用的方法是使用锁(Lock),它是一种非强制机制,每个线程在访问数据或资源之前首先试图获取锁,并在访
问结束之后释放锁;在锁已经被占用的时候试图获取锁时,线程会等待,直到锁重新可用。
synchronized synchronized关键字,就是用来控制线程同步的,保证我们的线程在多线程环境下,不被多个线程
同时执行,确保我们数据的完整性,。可重入,修饰(class、obj、代码块)。 可重入:一个函数被重入,表示这
个函数没有执行完成,但由于外部因素或内部因素,又一次进入该函数执行。一个函数称为可重入的,表明该函数
被重入之后不会产生任何不良后果。可重入是并发安全的强力保障,一个可重入的函数可以在多线程环境下放心使
用。 注意:虽然加synchronized关键字,可以让我们的线程变得安全,但是我们在用的时候,也要注意缩小
synchronized的使用范围,如果随意使用时很影响程序的性能,别的对象想拿到锁,结果你没用锁还一直把锁占
用,这样就有点浪费资源。
Lock.lock()/Lock.unLock()
ReentrantReadWriteLock是Lock的另一种实现方式,我们已经知道了ReentrantLock是一个排他锁,同一时间只
允许一个线程访问,而ReentrantReadWriteLock允许多个读线程同时访问,但不允许写线程和读线程、写线程和
写线程同时访问。相对于排他锁,提高了并发性。
ReentrantLock也是通过互斥来实现同步。在基本用法上,ReentrantLock与synchronized很相似,他们都具备一
样的线程重入特性。

什么是公平锁&非公平锁&区别?
公平锁:多个线程按照申请锁的顺序去获得锁,线程会直接进入队列去排队,永远都是队列的第一位才能得到锁。 优 点:所有的线程都能得到资源,不会饿死在队列中。 缺点:吞吐量会下降很多,队列里面除了第一个线程,其他的线程
都会阻塞,cpu唤醒阻塞线程的开销会很大。
非公平锁:多个线程去获取锁的时候,会直接去尝试获取,获取不到,再去进入等待队列,如果能获取到,就直接获取
到锁。 优点:可以减少CPU唤醒线程的开销,整体的吞吐效率会高点,CPU也不必取唤醒所有线程,会减少唤起线程的
数量。 缺点:你们可能也发现了,这样可能导致队列中间的线程一直获取不到锁或者长时间获取不到锁,导致饿死。

给我讲讲线程间通信
1:共享进程的变量
2:TheadLocal
3:锁

谈谈你对notify的理解?
该方法也要在同步方法或同步块中调用,即在调用前,线程也必须要获得该对象的对象级别锁,如果调用notify()
时没有持有适当的锁,也会抛出IllegalMonitorStateException。

image.png

transient关键字的用法 & 作用 & 原理。
transient的作用及使用方法
我们都知道一个对象只要实现了Serilizable接口,这个对象就可以被序列化,java的这种序列化模式为开发者提供了很
多便利,我们可以不必关系具体序列化的过程,只要这个类实现了Serilizable接口,这个类的所有属性和方法都会自动
序列化。
然而在实际开发过程中,我们常常会遇到这样的问题,这个类的有些属性需要序列化,而其他属性不需要被序列化,打
个比方,如果一个用户有一些敏感信息(如密码,银行卡号等),为了安全起见,不希望在网络操作(主要涉及到序列
化操作,本地序列化缓存也适用)中被传输,这些信息对应的变量就可以加上transient关键字。换句话说,这个字段的
生命周期仅存于调用者的内存中而不会写到磁盘里持久化。
总之,java 的transient关键字为我们提供了便利,你只需要实现Serilizable接口,将不需要序列化的属性前添加关键字
transient,序列化对象的时候,这个属性就不会序列化到指定的目的地中。

transient使用小结
1)一旦变量被transient修饰,变量将不再是对象持久化的一部分,该变量内容在序列化后无法获得访问。
2)transient关键字只能修饰变量,而不能修饰方法和类。注意,本地变量是不能被transient关键字修饰的。变量如果
是用户自定义类变量,则该类需要实现Serializable接口。
3)被transient关键字修饰的变量不再能被序列化,一个静态变量不管是否被transient修饰,均不能被序列化。
第三点可能有些人很迷惑,因为发现在User类中的username字段前加上static关键字后,程序运行结果依然不变,即
static类型的username也读出来为“Alexia”了,这不与第三点说的矛盾吗?实际上是这样的:第三点确实没错(一个静
态变量不管是否被transient修饰,均不能被序列化),反序列化后类中static型变量username的值为当前JVM中对应
static变量的值,这个值是JVM中的不是反序列化得出的

interrupted()是静态方法:内部实现是调用的当前线程的isInterrupted(),并且会重置当前线程的中断状态
isInterrupted()是实例方法,是调用该方法的对象所表示的那个线程的isInterrupted(),不会重置当前线程的中断状态

什么是TCP,什么是UDP,二者之间区别如何?(校招&实习) TCP(Transmission Control Protocol 传输控制协议):是一种面向连接的、可靠的、基于字节流的传输层通信协议,使
用三次握手协议建立连接、四次挥手断开连接。面向连接意味着两个使用TCP的应用(通常是一个客户端和一个服务器)在
彼此交换数据包之前必须先建立一个TCP连接。在一个TCP连接中,仅有两方进行彼此通信,广播和多播不能用TCP。 TCP 协议的作用是,保证数据通信的完整性和可靠性,防止丢包。TCP把连接作为最基本的对象,每一条TCP连接都有
两个端点,这种端点我们叫作套接字(socket),端口号拼接到IP地址即构成了套接字。
UDP(User Datagram Protocol 用户数据报协议):是OSI(Open System Interconnection 开放式系统互联)参考模型中
一种无连接的传输层协议,提供面向事务的简单不可靠信息传送服务。UDP协议的主要作用是将网络数据流量压缩成数
据包的形式。
区别: 1.TCP提供的是面向连接的、可靠的数据流传输;UDP提供的是非面向连接的、不可靠的数据流传输。 2.TCP提
供可靠的服务,通过TCP连接传送的数据,无差错、不丢失、不重复,按序到达;UDP尽最大努力交付,即不保证可靠
交付。 3.TCP面向字节流;UDP面向报文。 4.TCP连接只能是点到点的;UDP支持一对一、一对多、多对一和多对多的
交互通信。 5.UDP具有较好的实时性,工作效率比TCP高,适用于对高速传输和实时性有较高的通信或广播通信。
6.TCP对系统资源要求较多,UDP对系统资源要求较少。TCP首部有20字节;UDP的首部只有8个字节。 7.TCP的逻辑通
信信道是全双工的可靠信道;UDP的逻辑通信信道是不可靠信道。

你知道对象什么时候会回调finalize方法吗?
在java中,垃圾回收不需要我们自己执行,而是由系统自动执行。 这一点给了我们发开发人员省了不少心,但是
finalize()方法一直是个困惑。 虚拟机在执行垃圾回收之前都会执行对象的finalize(),那么finalize()到底有什么用呢? 其实
finalize()主要是给其他调用而创建的空间在对象回收时页同步回收而设置的。 比如,你调用了C语言,使用了mallow()
开辟了一段内存空间。在你释放java对象的时候,虚拟机只能释放java对象占用的空间,而不能释放C开辟的内存空间,
所你你在释放此对象之前要先回收你在C语言中开辟的空间。 这各时候才用到finalize()方法。 平时的时候不要随便使用
finalize()方法。

onSaveInstanceState() 什么时候调用
(1)、当用户按下HOME键时。
这是显而易见的,系统不知道你按下HOME后要运行多少其他的程序,自然也不知道activity A是否会被销毁,因此
系统会调用onSaveInstanceState(),让用户有机会保存某些非永久性的数据。以下几种情况的分析都遵循该原则
(2)、长按HOME键,选择运行其他的程序时。
(3)、按下电源按键(关闭屏幕显示)时。
(4)、从activity A中启动一个新的activity时。
(5)、屏幕方向切换时,例如从竖屏切换到横屏时。
(6)、引发activity销毁和重建的其它情况,除了系统处于内存不足的原因会摧毁activity之外, 某些系统设置的改变也会导
致activity的摧毁和重建. 例如改变屏幕方向, 改变设备语言设定, 键盘弹出等。

onRestoreInstanceState()在onStart() 和 onResume()之间调用。

你可能感兴趣的:(收集)