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标志位

以标志位为循环条件

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

HashMap

常见Map类

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

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

Read more

ThreadLocal

ThreadLocal:

ThreadLocal: 线程本地存储区(Thread Local Storage,简称为TLS),每个线程都有自己的私有的本地存储区域,不同线程之间彼此不能访问对方的TLS区域。

ThreadLocal类不过是为了执行set/get,确定泛型的工具人而已。真正数据是靠的每个Thread内部维护。

实际上就是 每个Thread对象中 维护了一个

叫ThreadLocal.ThreadLocalMap的

**key为 ThreadLocal对象, value为 存储的值 **

的key-vale映射结构,(实际上是一个键值对组成entry的唯一环形数组,线性探测,初始值3/4容量扩容)

当调用threadLocalInstance.set(value)时,其实时调用的CurrentThread.threadLocalMap.set(threadLocalInstance, value)

Read more