Thread

image-20210602170532688

对于dalvik虚拟机而言其检测到执行频率较高的函数时就会进行jit编译将其编译为本地机器码,这样下次此函数执行的时候就会直接执行编译后的机器码,编译后的机器码只存在于内存中并不会以文件的形式保存,app重启后此函数依然会以解释模式执行。在JIT编译函数生成机器码的同时还会生成配置文件profile记录热点函数信息,供AOT守护进程使用编译生成oat文件,以提速执行。

以下为JIT工作流:

img

但在我们测试中 AE 无论运行多少次启动阶段依然有JIT的运行,但TEMU在启动阶段JIT是无执行的,判断TEMU已做了AOT优化。

AOT事前编译,即在代码运行前进行编译。对于android 7.0之前的art虚拟机而言其会在apk安装的过程中利用dex2oat程序将apk中的dex文件编译为本地机器指令并保存为oat文件,这样在apk启动时直接加载此oat文件并运行,提高了程序了执行效率。但是因为他需要在apk安装的时候使用dex2oat程序进行编译,所以增加了apk在安装过程中的时间。

通过AOT优化的中端机有100ms收益

该优化需要对APP内所有工程做改造,升级AGP后,进行BaselineProfile优化

Thread

线程/进程

进程:进程是系统进行资源分配和调度的一个独立单位 (拥有独立内存空间),一个app就是一个进程,进程包含线程。

线程:是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。线程自己基本上不拥有系统资源,只拥有一些在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源。

静态的是资源和动态的是计算

  1. 进程是一个资源的容器,为进程里的所有线程提供共享资源,是对程序的一种静态描述

  2. 线程是计算机最小的调度和运行(计算)单位,是对程序的一种动态描述

Java里的线程有哪些状态?

JDK中,线程(Thread)定义了6种状态: NEW(新建)、RUNNABLE(可执行)、BLOCKED(阻塞)、WAITING(等待)、TIMED_WAITING(限时等待)、TERMINATED(结束)。

源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
/**
* A thread state. A thread can be in one of the following states:
* <ul>
* <li>{@link #NEW}

* A thread that has not yet started is in this state.
* </li>
* <li>{@link #RUNNABLE}

* A thread executing in the Java virtual machine is in this state.
* </li>
* <li>{@link #BLOCKED}

* A thread that is blocked waiting for a monitor lock
* is in this state.
* </li>
* <li>{@link #WAITING}

* A thread that is waiting indefinitely for another thread to
* perform a particular action is in this state.
* </li>
* <li>{@link #TIMED_WAITING}

* A thread that is waiting for another thread to perform an action
* for up to a specified waiting time is in this state.
* </li>
* <li>{@link #TERMINATED}

* A thread that has exited is in this state.
* </li>
* </ul>
*
* <p>
* A thread can be in only one state at a given point in time.
* These states are virtual machine states which do not reflect
* any operating system thread states.
*
* @since 1.5
* @see #getState
*/
public enum State {
/**
* Thread state for a thread which has not yet started.
*/
NEW,
/**
* Thread state for a runnable thread. A thread in the runnable
* state is executing in the Java virtual machine but it may
* be waiting for other resources from the operating system
* such as processor.
*/
RUNNABLE,
/**
* Thread state for a thread blocked waiting for a monitor lock.
* A thread in the blocked state is waiting for a monitor lock
* to enter a synchronized block/method or
* reenter a synchronized block/method after calling
* {@link Object#wait() Object.wait}.
*/
BLOCKED,
/**
* Thread state for a waiting thread.
* A thread is in the waiting state due to calling one of the
* following methods:
* <ul>
* <li>{@link Object#wait() Object.wait} with no timeout</li>
* <li>{@link #join() Thread.join} with no timeout</li>
* <li>{@link LockSupport#park() LockSupport.park}</li>
* </ul>
*
* <p>A thread in the waiting state is waiting for another thread to
* perform a particular action.
*
* For example, a thread that has called <tt>Object.wait()</tt>
* on an object is waiting for another thread to call
* <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
* that object. A thread that has called <tt>Thread.join()</tt>
* is waiting for a specified thread to terminate.
*/
WAITING,
/**
* Thread state for a waiting thread with a specified waiting time.
* A thread is in the timed waiting state due to calling one of
* the following methods with a specified positive waiting time:
* <ul>
* <li>{@link #sleep Thread.sleep}</li>
* <li>{@link Object#wait(long) Object.wait} with timeout</li>
* <li>{@link #join(long) Thread.join} with timeout</li>
* <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
* <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
* </ul>
*/
TIMED_WAITING,
/**
* Thread state for a terminated thread.
* The thread has completed execution.
*/
TERMINATED;
}

复制代码

状态说明

线程在一个给定的时间点只能处于下面其中一种状态:

这些状态是虚拟机状态,并不能反映任何操作系统的线程状态。

  • NEW:尚未启动的线程处于这个状态。Thread thread = new Thread(new Runnable(){…});处于这个状态。

  • RUNNABLE:可运行的线程处于这个状态。对应操作系统中的两种状态:ready和running,也就是说RUNNABLE状态既可以是可运行的,也可以是实际运行中的,有可能正在执行,也有可能没有正在执行。关于这个问题的理解,可以对比想一下,thread.start()调用之后线程会立刻执行吗?

  • BLOCKED:阻塞,进入synchronized修饰的方法或者代码块,等待监视器锁的线程处于这个状态。

  • WAITING:无限期等待另一个线程执行特定操作的线程处于这种状态。

  • TIMED_WAITING:正在等待另一个线程执行某个操作的线程在指定的等待时间内处于这种状态。

  • TERMINATED:已经退出的线程处于这个状态。

状态转移

NEW:线程尚未启动的线程状态。当在程序中创建一个线程的时候Thread t = new Thread(Runnable);,线程处于NEW状态。

RUNNABLE:可运行线程的线程状态。处于可运行状态的线程正在Java虚拟机中执行,但它可能正在等待操作系统中的其他资源,比如处理器。也就是说, 这个状态就是可运行也可不运行的状态。注意Runnable ≠ Running。

BLOCKED:进入synchronized修饰的方法或者代码块,等待监视器锁的阻塞线程的线程状态。比如,线程试图通过synchronized去获取监视器锁,但是其他线程已经独占了,那么当前线程就会处于阻塞状态。等到获得了监视器锁之后会再次进入RUNNABLE状态。

WAITING:调用以下方法之一,线程会处于等待状态:

  • Object.wait()注意:括号内不带参数;
  • Thread.join()注意:扩号内不带参数;
  • LockSupport.park();

其实wait()方法有多重形式,可以不带参数,可以带参数,参数表示等待时间(单位ms),如图所示:

imgimg

“BLOCKED(阻塞状态)”和“WAITING(等待状态)”的区别:阻塞状态在等待获取一个排它锁,这个事件将会在另外一个线程放弃这个锁的时候发生,然后由阻塞状态变为可执行状态;而等待状态则是在等待一段时间,或者等待唤醒动作的发生。

TIMED_WAITING:一个线程调用了以下方法之一(方法需要带具体的等待时间),会处于定时等待状态:

  • Thread.sleep(long timeout)
  • Object.wait(long timeout)
  • Thread.join(long timeout)
  • LockSupport.parkNanos()
  • LockSupport.parkUntil()

TERMINATED: 该线程已经执行完毕。执行完毕指的是线程正常执行完了run方法之后退出,也可以是遇到了未捕获的异常而退出。

img

初始(NEW)

新创建了一个线程对象,但还没有调用start()方法。

运行(RUNNABLE)

Ready

Running

Java线程中将就绪(ready)和运行中(running)两种状态笼统 的称为“运行”。线程对象创建后,其他线程(比如main线程)调用了该对象的start()方 法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取CPU的使用权,此 时处于就绪状态(ready)。就绪状态的线程在获得CPU时间片后变为运行中状态 (running)。

阻塞(BLOCKED)

表示线程阻塞于锁。或称“挂起”

阻塞或唤醒一个Java线程需要操作系统切换CPU状态来完成,这种状态转换需要耗费处理器时间

等待(WAITING)

等待状态,处于等待状态的线程是由于执行了Thread.joinObject.wait方法

处于waiting状态的线程会等待另外一个线程处理特殊的行为。 再举个例子,如果一个线程调用了一个对象的wait方法,那么这个线程就会处于waiting状态直到另外一个线程调用这个对象的notify或者notifyAll方法后才会解除这个状态

超时等待(TIMED_WAITING)

有等待时间的等待状态,比如调用了**Thread.sleep(long timeout)、Thread.join(long timeout)、Object.wait(long timeout)**,并且指定了等待时间,线程就会处于这个状态。

终止(TERMINATED)

表示该线程已经执行完毕。

对比分析Java中的各个线程相关的wait()、notify()、sleep()、interrupt()方法

线程相关方法

Thread类

sleep:暂停当前正在执行的线程;(类方法

​ 是Thread的静态方法,很显然它是让当前线程按照指定的时间休眠,其休眠时间的精度取决于处理器的计时器和调度器。需要注意的是如果当前线程获得了锁,sleep方法并不会失去锁。sleep方法经常拿来与Object.wait()方法进行比价,这也是面试经常被问的地方。

sleep() VS wait()

两者主要的区别:

1. sleep()方法是Thread的静态方法,而wait是Object实例方法
2. wait()方法必须要在同步方法或者同步块中调用,也就是必须已经获得对象锁。而sleep()方法没有这个限制可以在任何地方种使用。另外,wait()方法会释放占有的对象锁,使得该线程进入等待池中,等待下一次获取资源。而sleep()方法只是会让出CPU并不会释放掉对象锁;
3. sleep()方法在休眠时间达到后如果再次获得CPU时间片就会继续执行,而wait()方法必须等待Object.notift/Object.notifyAll通知后,才会离开等待池,并且再次获得CPU时间片才会继续执行。

yield:暂停当前正在执行的线程,并执行其他线程;(类方法

​ 是Thread的静态方法,一旦执行,它会是当前线程让出CPU,但是,需要注意的是,让出的CPU并不是代表当前线程不再运行了,如果在下一次竞争中,又获得了CPU时间片当前线程依然会继续运行。另外,让出的时间片只会分配给当前线程相同优先级的线程。

​ yield()方法使当前线程出让CPU执行时间,但并不会释放当前线程所持有的锁。执行完yield()方法后,线程从Running状态转变为Runnable状态,既然是Runnable状态,那么也很可能马上会被CPU调度再次进入Running状态。

什么是线程优先级了?下面就来具体聊一聊。

现代操作系统基本采用时分的形式调度运行的线程,操作系统会分出一个个时间片,线程会分配到若干时间片,当前时间片用完后就会发生线程调度,并等待这下次分配。线程分配到的时间多少也就决定了线程使用处理器资源的多少,而线程优先级就是决定线程需要或多或少分配一些处理器资源的线程属性。

在Java程序中,通过一个整型成员变量Priority来控制优先级,优先级的范围从1~10.在构建线程的时候可以通过**setPriority(int)**方法进行设置,默认优先级为5,优先级高的线程相较于优先级低的线程优先获得处理器时间片。需要注意的是在不同JVM以及操作系统上,线程规划存在差异,有些操作系统甚至会忽略线程优先级的设定。

另外需要注意的是,sleep()和yield()方法,同样都是当前线程会交出处理器资源,而它们不同的是,sleep()交出来的时间片其他线程都可以去竞争,也就是说都有机会获得当前线程让出的时间片。而yield()方法只允许与当前线程具有相同优先级的线程能够获得释放出来的CPU时间片。

join:等待该线程终止;

​ join()方法的作用,是等待这个线程结束,是主线程等待子线程的终止。也就是说主线程的代码块中,如果碰到了t.join()方法,此时主线程需要等待(阻塞),等待子线程结束了(Waits for this thread to die.),才能继续执行t.join()之后的代码块。

interrupt:中断该线程,

interrupt()方法的工作仅仅是改变中断状态,并不是直接中断正在运行的线程。中断的真正原理是当线程被Object.wait(),Thread.join()或sleep()方法阻塞时,调用interrupt()方法后改变中断状态,而wait/join/sleep这些方法内部会不断地检查线程的中断状态值,当发现中断状态值改变时则抛出InterruptedException异常;对于没有阻塞的线程,调用interrupt()方法是没有任何作用。

Object类

  • wait:暂停当前正在执行的线程,直到调用notify()或notifyAll()方法或超时,退出等待状态;
  • notify:唤醒在该对象上等待的一个线程;
  • notifyAll:唤醒在该对象上等待的所有线程;

对比

sleep VS wait

sleep()和wait()方法都是暂停当前正在执行的线程,出让CPU资源。

方法 所属类 方法类型 解除方法 场景 用途
sleep Thread 静态方法 不释放锁 timeout,interrupt 无限制 线程内的控制
wait Object 非静态方法 释放锁 timeout,notify,interrupt 同步语句块 线程间的通信
1
2
3
4
5
6
public static void sleep(long millis) throws InterruptedException 
public static void sleep(long millis, int nanos) throws InterruptedException

public final void wait() throws InterruptedException
public final void wait(long timeout) throws InterruptedException
public final void wait(long timeout, int nanos) throws InterruptedException

wait && notify

调用对象的wait()、notify()、notifyAll()方法的线程,必须是作为此对象监视器的所有者。常见的场景便是就是synchronized关键字的语句块内部使用这3个方法,如果直接在线程中使用wait()、notify()、notifyAll()方法,那么会抛出异常IllegalMonitorStateException,抛出的异常表明某一线程已经试图等待对象的监视器,或者试图通知其他正在等待对象的监视器而本身没有指定监视器的线程。。

调用wait()方法的线程,在调用该线程的interrupt()方法,则会重新尝试获取对象锁。只有当获取到对象锁,才开始抛出相应的异常,则执行该线程之后的程序。

怎么终止一个线程

首先,一个线程不应该由其他线程来强制中断或停止,而是应该由线程自己自行停止。

所以,Thread.stop, Thread.suspend, Thread.resume 都已经被废弃了。

interrupt:

Thread.interrupt 的作用其实也不是中断线程,而是「通知线程应该中断了」,

具体到底中断还是继续运行,应该由被通知的线程自己处理。

具体来说,当对一个线程,调用 interrupt() 时,

① 如果线程处于被阻塞状态(例如处于sleep, wait, join 等状态),那么线程将立即退出被阻塞状态,并抛出一个InterruptedException异常。仅此而已。

② 如果线程处于正常活动状态,那么会将该线程的中断标志设置为 true,仅此而已。被设置中断标志的线程将继续正常运行,不受影响。

interrupt() 并不能真正的中断线程,需要被调用的线程自己进行配合才行。

① 在正常运行任务时,经常检查本线程的中断标志位,如果被设置了中断标志就自行停止线程。

② 在调用阻塞方法时正确处理InterruptedException异常。(例如,catch异常后就结束线程。)

1
2
3
4
5
6
7
8
9
Thread thread = new Thread(() -> {
while (!Thread.interrupted()) {
// do more work.
}
});
thread.start();

// 一段时间以后
thread.interrupt();

stop():

弃用,因为在stop时会释放所有的锁,可能导致线程不同步,另一个也可能导致资源如文件文件数据库的关闭行为不被执行

  1. 调用 stop() 方法会立刻停止 run() 方法中剩余的全部工作,包括在 catch 或 finally 语句中的,并抛出ThreadDeath异常(通常情况下此异常不需要显示的捕获),因此可能会导致一些清理性的工作的得不到完成,如文件,数据库等的关闭。
  2. 调用 stop() 方法会立即释放该线程所持有的所有的锁,导致数据得不到同步,出现数据不一致的问题。

使用volatile标志位

以标志位为循环条件

Https

Http和Https

  • 其实HTTPS就是从 HTTP 加上 SSL/TLS (加密处理+认证+完整性保护)

完整的https通信过程,三次RTT:tcp握手一次RTT,TLS握手两次RTT

img

HTTPS工作原理

image

  • 一、首先客户端发起连接请求,将自身支持的加密(RSA)和哈希算法(sha256)连带发给服务器(服务器端有非对称加密的公钥和私钥)
  • 二、服务器端接受请求,选取一组加密和哈希算法后,将服务器证书(含公钥)发送给客户端
  • 三、客户端收到服务器端证书并使用根证书验证证书后,从中取出公钥,然后本地生成一段随机数,将此随机数用公钥加密后发送给服务器端
  • 四、服务器端用私钥解密出这段随机数作为对称加密的秘钥。之后双方就可以进行对称加密(DES、AES等)。

证书校验过程

校验证书的过程:

第一步是校验证书网站域名、有效期等。

第二步是校验证书本身是否可信:主要是依靠验证证书的信任链条完成。

比如根证书A->B->服务端证书C,那么首先要验证C是由B签署的,这一步的具体步骤是:

1. **用B证书的公钥解密C证书的签名信息后拿到C证书的hash值(签署时该Hash值由B的私钥加密生成),**
2. **然后再用hash算法(B证书上带的签名算法)计算B证书的<u>待签名数据</u>后得到计算hash值,**
3. **将证书解密的hash值与计算hash值比较即可。**

第三步是使用CRL(证书吊销列表)或OCSP(在线证书状态协议)确认证书是否已被吊销。

1
2
3
4
5
6
7
8
9
10
11
12
证书内容可简化为:
待签名数据:
版本: v3
序列号: 123456
签名算法: SHA256withRSA
颁发者信息: CN=A, O=A Corporation, C=US
有效期: 2022-01-01 至 2023-01-01
主题信息: CN=B, O=B Corporation, C=US
主题公钥: (公钥数据)
扩展字段: (可选)
签名:
签名: (签名数据)

image-20240703175159171

image-20211123141219076 image-20211123141130160

抓包软件怎么实现的

以我熟悉的抓包软件whistle为例,抓包软件能抓https内容的核心是:你的手机安装了whistle的自签名根证书,同时你的请求都是代理给whistle的,然后whistle会发送给客户端经自签名根证书签署过的伪造服务器证书,故而能通过客户端的证书校验,从而拿到请求原文。

手机安装了抓包软件的自签名证书后,抓包工具在作为代理进行HTTPS流量捕获时,实际上充当了中间人。抓包开启后,客户端https请求的握手对象实际上是抓包软件,服务端拿到的请求地址是 抓包软件的地址。手机的请求也实际上是与抓包软件在交互,抓包软件再与服务器交互。故而抓包软件能记录并修改客户端的请求与响应

image-20240703170642767

自签名证书:抓包软件生成一个自签名的证书(自签名证书是一种灵活且方便的SSL/TLS证书解决方案,是由证书的所有者自己签发的,适用于于开发测试环境和内部网络。然而,由于它不是由受信任的三方证书颁发机构即CA签发的,使得它无法提供可靠的身份验证,无法适用于公网)

  1. 客户端信任自签名证书
    • 当用户在设备上安装抓包软件的自签名证书后,这个证书被设备作为受信任的根证书。设备会信任由这个自签名证书签发的所有证书。
  2. 中间人代理握手
    • 抓包软件充当“中间人”,拦截客户端的HTTPS请求。客户端发出的请求首先到达抓包软件。
    • 抓包软件向客户端提供一个由它自己的自签名根证书签署的伪造服务器证书(目标服务器的替身证书)。
    • 因为客户端已经信任抓包软件的自签名证书,所以它会信任这个伪造的服务器证书,并与抓包软件进行TLS握手。

这样,手机上的浏览器或应用程序在连接时实际上是与抓包软件建立的https握手

抓包软件的主要目的是捕获、分析和调试网络流量。其工作步骤如下:

  1. 客户端通过代理发送请求
    • 抓包软件配置为代理,客户端配置通过此代理发送所有网络请求。
    • 抓包软件生成一个自签名证书,客户端将其安装为受信任的根证书。
  2. 代理解析并再发送请求
    • 客户端的请求首先由抓包软件接收。对于HTTPS请求,抓包软件会使用自签名证书解密这些请求。
    • 抓包软件记录和分析解密后的请求内容,然后重新加密并发送到目标服务器。
  3. 服务器响应经过代理返回客户端
    • 目标服务器返回响应,抓包软件解密并记录这些响应数据。
    • 抓包软件再重新加密这些数据并发送回客户端。

VPN与抓包软件的原理类似,都是通过一层中间人,将客户端的请求实际上代理到中间人层发起请求。

不同的是由于抓包软件使客户端安装的自签名证书,客户端在证书校验时遇到抓包软件自签名证书签署的伪造服务器证书会校验通过,使客户端的请求都能在中间人层解密后查看和修改,之后抓包再转发。

而VPN则是通过加密隧道(加密整个传输链路):使用协议如IPsec、OpenVPN、L2TP等),在原本就由ssl/tls加密的数据上再加密一层,之后无法感知传输内容原原本本的转发。

JvmMemoryStructure

image-20240425141403378

程序计数器不会OOM和StackOverflow

有栈的结构(栈 java stack、navtive stack)可能发生 StackOverflowError(栈过深) 和 OOM

StackOverFlowError ︰若Java 虚拟机栈的内存大小不允许动态扩展,那么当线程请求栈的深度超过当前Java 虚拟机栈的最大深度的时候,就抛出StackOverFlowError错误。
OutOfMemoryError :如果虚拟机栈可以动态扩展(当前大部分的 Java 虚拟机都可动态扩展,只不过 Java 虚拟机规范中也允许固定长度的虚拟机栈),当扩展时无法申请到足够的内存时会抛出 OutOfMemoryError 异常。

没栈的结构(堆heap、方法区Method Area)只可能发生 OOM

Read more

EventAndNestedScroll

原理&流程

一. ns child会在收到DOWN事件时,找到自己祖上中最近的能与自己匹配的ns parent,与它进行绑定并关闭它的事件拦截机制

二. 然后ns child会在接下来的MOVE事件中判定出用户触发了滑动手势,并把事件流拦截下来给自己消费

三. 消费事件流时,对于每一次MOVE事件增加的滑动距离:

  1. ns child并不是直接自己消费,而是先把它交给ns parent,让ns parent可以在ns child之前消费滑动dispatch/onNestedPreScroll()
  2. 如果ns parent没有消费或是没有消费完,ns child再自己消费剩下的滑动dispatchNestedScroll()
  3. 如果ns child自己还是没有消费完这个滑动,会再把剩下的滑动交给ns parent消费onNestedScroll()
  4. 最后如果滑动还有剩余,ns child可以做最终的处理dispatchNestedScroll()

四. 同时在ns childcomputeScroll()方法中,ns child也会把自己因为用户fling操作引发的滑动,与上一条中用户滑动屏幕触发的滑动一样,使用「parent -> child -> parent -> child」的顺序进行消费

Read more

VirtualMemory

img

简述:cpu以虚拟内存,通过MMU查询 页表, 映射 高4位(页号) 到页表中查询得到 页框号 和 有效位。

如果有效位 为1, 则直接将页框号和虚拟内存低12位(偏移量)组合返回即为物理地址

如果有效位为0(意味着该页表项不存在MMU中,即未向MMU注册或相关页未被加载如内存中),则产生缺页中断,系统处理中断,通过内存置换swap算法(LRU,OPT,FIFO),之后重走一遍以上逻辑

Read more

HttpProtocol

Http 缓存

在请求一个静态文件的时候(图片,css,js)等,这些文件的特点是文件不经常变化,将这些不经常变化的文件存储起来,对客户端来说是一个优化用户浏览体验的方法。那么这个就是客户端缓存的意义了。

简述:

  • 强制缓存是根据上次响应header中的Cache-Control:Max-age或是Expries客户端直接判断缓存是否能用该资源缓存;

  • 协商缓存需要客户端用记录下来的上次响应header中的ETag或是Last-Modified,通过与向服务器的请求request的header中赋值If-None-Match或If-Modified-Since,由服务器判断资源是否更新,结果由code和是否存在body明确是否命中缓存;

同时出现的优先级排序: 强制缓存 > 协商缓存;ETag & If-None-Match > Last-Modified & If-Modified-Since ;Cache-Control > Expries;

Read more

Concurrent

https://www.wwwbuild.net/JavaAmazing/112187.html

并发操作:原子性、可见性、有序性

1、原子性

即一个操作或者多个操作,要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。

如java.util.concurrent.atomic包下的原子类,就是用CAS(Compare And Swap)保证原子性和可见性(内存屏障)

虽然 java.util.concurrent.atomic 提供了原子操作,但这些解决方案主要针对单一变量。对于涉及多个变量时的原子性操作,仍然需要使用高级同步机制(如 synchronized 块或 ReentrantLock)。

Java 内存模型(JMM)确保在使用原子类和 CAS 操作时,数值的更新对其他线程是可见的。这是通过内存屏障来实现的。

2、可见性

可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。

如volatile能保证被修饰变量的可见性、有序性;原子类可以保证原子性和可见性;

  • 可见性volatile 关键字确保变量的更新对所有线程立即可见,避免线程读取到变量的过期值。
  • 禁止指令重排序优化:编译器和运行时不会把 volatile 变量的写操作与之前的内存操作重排序,也不会把 volatile 变量的读操作与之后的内存操作重排序。

3、有序性

即程序执行的顺序按照代码的先后顺序执行。(指令编排可能会导致多线程下执行结果不一致)

如volatile能保证被修饰变量的可见性、有序性

synchronized关键字三者都能保证。

Volatile 和synchronized的区别:

java.util.concurrent.atomic包的原子类可以保证数据的原子性、可见性;volatile关键字能保证数据的可见性、有序性,但不能保证数据的原子性。synchronized关键字三者都能保证。

1:并发特性比较:

volatile关键字能保证数据的可见性、有序性,但不能保证数据的原子性(即volatile int x; x++ 是三步操作:一取x值,二加一,三赋值回x)。synchronized关键字两者都能保证。

有序性则volatile和synchronized都能保证,volatile关键字禁止JVM编译器已及处理器对其进行重排序,

synchronized保证顺序性是串行化的结果,但同步块里的语句是会发生指令从排。

2:volatile 的原理

1). 修改volatile变量时会强制将修改后的值刷新的主内存中。

2). 修改volatile变量后会导致其他线程工作内存中对应的变量值失效。因此,再读取该变量值的时候就需要重新从读取主内存中的值。

3). 禁止指令重排序优化:编译器和运行时不会把 volatile 变量的写操作与之前的内存操作重排序,也不会把 volatile 变量的读操作与之后的内存操作重排序。

(Intel 的MESI协议:当CPU写数据时,如果发现操作的变量是共享变量,即在其他CPU中也存在该变量的副本,会发出信号通知其他CPU将该变量的高速缓存置为无效状态,因此当其他CPU需要读取这个变量时,发现自己缓存中缓存该变量的缓存行是无效的,那么它就会从内存重新读取重新加载到高速缓存。)

3:阻塞与否

多线程访问volatile关键字不会发生阻塞(2所述原理),而synchronized关键字可能会发生阻塞(重量级锁时会阻塞)

4:性能

volatile关键字是线程同步的轻量级实现,所以volatile性能肯定比synchronized关键字要好。但是volatile关键字只能用于变量而synchronized关键字可以修饰方法以及代码块。synchronized关键字在JavaSE1.6之后进行了主要包括为了减少获得锁和释放锁带来的性能消耗而引入的偏向锁和轻量级锁以及其它各种优化之后执行效率有了显著提升,实际开发中使用synchronized关键字的场景还是更多一些。

Read more

tcp

简述:

Tcp是

面向链接的
面向字节流的
可靠的(几个点)

保证可靠的手段:

  • 数据分块——握手时协商确定MSS,大于MSS的tcp数据包分段(也就是拆包);序列号;校验和;确认ack包

  • 超时重传——发送方使用一个保守估计的时间作为收到数据包的确认的超时上限RTO。如果超过这个上限仍未收到确认包,发送方将重传这个数据包。每当发送方收到确认包后,会重置这个重传定时器。

    超时重传会触发拥塞控制之重置拥塞窗口为1个MSS,阈值减为当前cwnd一半,执行慢启动每轮往返拥塞倍增

  • 滑动窗口实现的流量控制;接收方在ack包中设置rwnd控制发送方发送速度。

  • 拥塞控制算法——小于阈值之前从1开始每轮往返拥塞窗口cwnd倍增(慢启动),拥塞窗口大于阈值后步长为一的递增(拥塞避免)。接收方收到失序报文段后立即发出重复确认ack包,发送方连续三次重复确认则直接发送缺乏ack的丢包(快速重传),同时把阈值减为cwnd/2并调整拥塞窗口为新阈值而后执行拥塞避免算法(快速恢复);

Ps:

超时重传会触发拥塞控制之重置拥塞窗口为1个MSS,阈值减为当前cwnd一半,执行慢启动每轮往返拥塞倍增;

三次重复确认会执行快速重传快速恢复,阈值减为当前cwnd一半,拥塞窗口为新阈值值,执行拥塞避免,每轮往返递增;

Read more

HashMap

常见Map类

简述:HashMap是非线程安全的,如需要线程安全的哈希隐射类,应使用实现了分段锁(1.8)的ConcurrentHashMap,而不建议使用遗留类HashTable(HashTable实现线程安全是依靠用synchronized关键字修饰put/get方法,效率较低)。

如果需要保存记录插入的顺序,可使用LinkHashMap(),其内部实现了一个双向链表,即每个节点本身记录了前后节点的引用。(btw:MessageQueue是单链表)

Read more

APM

主线程卡顿监控

方案一、Looper Printer监控每次 dispatchMessage 的执行耗时:

DoKit & BlockCanary & Matrix

滴滴的哆啦A梦的卡顿检测其实就是blockCanary,和Matrix 的EvilMethodTracer和AnrTracer (当然后来Matrix还增加了native的Signal信号监听)使用的 方案也就是Looper设置Printer监听卡顿

都是根据handler原理,通过给Looper.loop() 中设置printer(无论是通过反射替换Looper的mLogging还是通过setMessageLogging设置printer),监控超过 设定阈值(matrix700ms) 的主线程消息(超过5s报为ANR),printer 中判断start和end,来获取主线程dispatch该message的开始和结束时间,并判定该时间超过阈值为主线程卡慢发生,并 打印当时堆栈 + 方法耗时(matrix/dokit)

Read more

卡顿监控

https://mp.weixin.qq.com/s/3dubi2GVW_rVFZZztCpsKg

主线程卡顿监控

方案一、Looper Printer监控每次 dispatchMessage 的执行耗时:

DoKit & BlockCanary & Matrix

滴滴的哆啦A梦的卡顿检测其实就是blockCanary,和Matrix 的EvilMethodTracer和AnrTracer (当然后来Matrix还增加了native的Signal信号监听)使用的 方案也就是Looper设置Printer监听卡顿

都是根据handler原理,通过给Looper.loop() 中设置printer(无论是通过反射替换Looper的mLogging还是通过setMessageLogging设置printer),监控超过 设定阈值(matrix700ms) 的主线程消息(超过5s报为ANR),printer 中判断start和end,来获取主线程dispatch该message的开始和结束时间,并判定该时间超过阈值为主线程卡慢发生,并 打印当时堆栈 + 方法耗时(matrix/dokit)

Read more

Gc

现代VM:”引用计数法,不行。可达性分析法,行!”
JVM:”可达分析法,很行”

Read more

EventDispatch

image-20220127160157749

Core

事件分发中有一个重要的规则:一个触控点的一个事件序列只能给一个view处理

分析:以DOWN事件为序列分发判定,ViewGroup为消费DOWN事件的View生成一个TouchTarget(这个TouchTarget就包含了该view的实例与触控id,id可以是多个以应对多指触控),后续MOVE、UP都会交给这个TouchTarget。如果TouchTarget为空则ViewGroup自己处理。如果viewGroup消费了down事件,那么子view将无法收到任何事件。

Read more

fresco

Fresco(2.5.0)

以MVC的 Fresco架构入手,层层递进分析fresco的整体思路。

img

Read more

Android Build

Apk总构建流程

简述

Aapt 会将主工程、依赖库中的资源(res、assets)和androidManifest都合并,产出R.java、资源及资源索引resources.arsc;

之后javac编译包括R.java文件、主工程的java文件、aidl产生的java文件,产出class文件;如果需要插桩的话就插桩

之后使用Proguard/R8混淆工具对.class文件脱糖、压缩、混淆等,产出新的class文件;

之后使用Dx/D8编译工具将新的class文件再转换成dex文件,

之后打包成apk,然后签名、zipalign优化。

工具:aapt/aapt2、javac、Proguard/R8、Dx/D8、ApkBuilder、zipalign

img

Read more

ScreenDraw

从onCreate到页面绘制

当手指点击了桌面的App图标时发生了什么 - ProcessOn

Android 屏幕刷新机制

主要参考 https://juejin.cn/post/6863756420380196877#heading-12

省流版:

双缓存:为了解决画面撕裂;画面撕裂来自于只有一个buffer时,正在display的那一帧数据被后一帧的数据覆盖了

Vsync:系统在收到VSync pulse(Vsync脉冲)后,将马上开始下一帧的渲染,(CPU开始计算数据)。

三缓冲:当显示器正在写入FrameBuffer同时GPU也正在写入BackBuffer时,下一次渲染开始了,此时CPU可以使用新增的GraphicBuffer进行计算。减少了Jank。(更多缓冲需要耗费更大的内存)

ChoreoGrapher机制:规定了数据计算开始(measure、layout、draw)的时机(vsync信号),使计算到渲染图像数据能有一个完整的16.6ms:更新ui(request()/invalidate())后编舞者注册vsync信号回调,在下一个vsync信号到时候立刻进行view的测量布局绘制

Read more