Zhujiaqqq Blog


  • 首页

  • 关于

  • 标签

  • 分类

ConcurrentHashMap如何支持并发访问

发表于 2019-08-20 | 分类于 Java
  • CHM分段锁的Hash优化JDK6(之前分段不均匀)
  • CHM分段锁懒加载JDK7
  • CHM摒弃分段锁JDK8(通过hash(key)访问table,对单个table元素加锁)

CHM如何计数

  • JDK5-7基于段元素个数求和,二次不同就加锁
  • JDK8引入CounterCell,本质上也是分段计数

CHM是弱一致性都

  • 添加元素后不一定马上能读到
  • 清空之后可能仍然会有元素
  • 遍历之前的段元素的变化会读到
  • 遍历之后的段元素变化读不到
  • 遍历时元素发生变化不抛异常

HashTable的问题

  • 大锁:对HashTable对象加锁
  • 长锁:直接对方法加锁
  • 读写锁共用:只有一把锁,从头锁到尾

针对HashTable的优化,CHM的解法

  • 小锁:分段锁(5-7)、桶节点锁(8)
  • 短锁:先尝试获取,失败再加锁
  • 分离读写锁:读失败再加锁(5-7),volatile读CAS写(7-8)

如何进行锁优化

  • 长锁不如短锁
  • 大锁不如小锁
  • 公锁不如私锁
  • 嵌套锁不如扁平锁
  • 分离读写锁
  • 粗化高频锁
  • 消除无用锁

如何做到线程安全

发表于 2019-08-18 | 分类于 Android

什么是线程安全

可变资源(内存)线程间共享

如何实现线程安全

  • 不共享资源(ThreadLocal)
  • 共享不可变资源(可重入函数)
  • 对于共享可变资源,需要:1、 可见性;2、操作原子性;3、禁止重排序

ThreadLocal

ThreadLocal本质上是一个绑定到Thread中的Map(ThreadLocalMap),Map的key为ThreadLocal对象本身,value为需要存入的值。

ThreadLocal的使用建议:

  • 声明为全局静态final成员
  • 避免存储大量数据
  • 用完后及时移除对象

禁止重排序

  • 在重排序的情况下,final的成员变量会在构造方法中赋值,而非finial的成员变量可能会在构造方法外才被赋值(所以说final有禁止重排序的功能)。

  • Volatile 让对象保证线程可见,禁止重排序。

保证线程可见性

  • final
  • volatile
  • 加锁,当锁释放时会强制将缓存刷新到主内存

保证原子性

  • 加锁,保证操作的互斥性
  • 使用原子数值类型(AtomicInteger, …)
  • 使用原子属性更新器(AtomicRefrenceFieldUpdater)

如何停止一个线程

发表于 2019-08-17 | 分类于 Android

为什么不能用Thread.stop()方法停止线程?

Thread.stop()是不安全的,被stop的线程立即销毁,此线程访问的内存和文件没有进行保护处理,之后访问的线程会拿到错误的数据。

正确停止线程的方法

协作的任务执行模式

  • 通过目标线程自行结束,而不是强制停止
  • 目标线程应当具备处理中断的能力
  • 中断方式
    • Interrupt
    • volatile boolean 标志位

interrupted()与isInterrupted()区别

  • interrupted()是静态方法,获取当前线程的中断状态,并清空
    • 当前运行的线程
    • 中断状态调用后清空,重复调用后继续返回false
  • isInterrupted()是非静态方法,获取该线程的中断状态,不清空
    • 调用的线程对象对应的线程
    • 可以重复调用,中断清空前一直返回true

interrupt与boolean标志位对比

interrupt boolean标志位
对系统方法(sleep)有效 是 否
使用JNI 是 否
加锁 是 否
触发方式 抛异常 布尔值判断,也可抛异常
  • 需要支持系统方法(sleep)时用中断
  • 其他情况用boolean标志位(jni有性能开销,所以标志位性能更好)

CPU架构适配需要注意哪些问题

发表于 2019-08-17

CPU架构的指令兼容性

  • mips(已被废弃)

    • mips64
  • armeabi

    • armeabi-v7a
      • arm64-v8a
    • x86
      • X86_64

兼容模式运行的一些问题

  • 兼容模式运行的Native库无法获得最优性能

  • 兼容模式容易出现一些难以排查的内存问题

  • 系统优先加载对应架构目录下的so库

为App提供不同架构的Native库

例如:

  • libs

    • Armeabi-v7a

      • libmath.so

      • libmath_v8a.so

      • libui.so

线上监控问题,针对性提供Native库

非启动加载的库可云端加载

优化so体积

  • 默认隐藏所有符号,只公开必要的

    • fvisibility=hidden
  • 禁用C++Exception & RTTI

    • -fno-exceptions -fno-rtti
  • 不要使用iostream,优先使用Android Log
  • 使用gc-sections去除无用代码
    • LOCAL_CFLAGS +=-ffuntion-sections -fdata-sections
    • LOCAL_LDFLAGS += -Wl, –gc-sections

构建时分包

1
2
3
4
5
6
7
8
splits{
abi{
enable true
reset()
include 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
universalApk true
}
}

通过CPU架构分发安装包

开发者需要注意

  • 尽量不在Native层开发,降低问题跟踪维护成本
  • 尽量优化Native库体积,降低开发者的使用成本
  • 必须提供完整的CPU架构依赖

并发和并行的区别

发表于 2019-06-29 | 分类于 基础

并发(Concurrent):单个处理器同一时段内处理多个任务。

并行(Parallel):多个处理器或多核处理器同时处理多个任务。

借用一张图:

Handler发送消息的delay可靠吗

发表于 2019-06-27 | 分类于 Android

MessageQueue如何处理消息

Handler的这些方法

Handler的这些方法最终执行的都是MessageQueue.enqueueMessage()方法

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
boolean enqueueMessage(Message msg, long when) {
synchronized (this) {
msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
// Inserted within the middle of the queue. Usually we don't have to wake
// up the event queue unless there is a barrier at the head of the queue
// and the message is the earliest asynchronous message in the queue.
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}

// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}

enqueueMessage()方法就是将handler发出的message插入messageQueue中。方法中走if条件的话就是当前queue中没有要处理的message,则当前插入的msg为message链表的头;如果走else方法,那么说明当前message链表中有message,插入的msg按照when的先后插入链表。

处理message链表中的消息是在Looper.loop()方法方法中进行的。loop()方法是个for(;;)循环,在每次循环中都会调用messageQueue.next()方法进行消息的获取,然后再分发处理。

messageQueue.next()

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
100
101
102
103
104
105
Message next() {
// Return here if the message loop has already quit and been disposed.
// This can happen if the application tries to restart a looper after quit
// which is not supported.
final long ptr = mPtr;
if (ptr == 0) {
return null;
}

int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}

nativePollOnce(ptr, nextPollTimeoutMillis);

synchronized (this) {
// Try to retrieve the next message. Return if found.
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {
// Stalled by a barrier. Find the next asynchronous message in the queue.
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) {
// Next message is not ready. Set a timeout to wake up when it is ready.
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// Got a message.
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse();
return msg;
}
} else {
// No more messages.
nextPollTimeoutMillis = -1;
}

// Process the quit message now that all pending messages have been handled.
if (mQuitting) {
dispose();
return null;
}

// If first time idle, then get the number of idlers to run.
// Idle handles only run if the queue is empty or if the first message
// in the queue (possibly a barrier) is due to be handled in the future.
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
if (pendingIdleHandlerCount <= 0) {
// No idle handlers to run. Loop and wait some more.
mBlocked = true;
continue;
}

if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}

// Run the idle handlers.
// We only ever reach this code block during the first iteration.
for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = null; // release the reference to the handler

boolean keep = false;
try {
keep = idler.queueIdle();
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}

if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}

// Reset the idle handler count to 0 so we do not run them again.
pendingIdleHandlerCount = 0;

// While calling an idle handler, a new message could have been delivered
// so go back and look again for a pending message without waiting.
nextPollTimeoutMillis = 0;
}
}

在next()方法的for循环里面

nativePollOnce(ptr, nextPollTimeoutMillis);这句话nextPollTimeoutMillis=-1时会在native层阻塞,即message链表为空的情况(消息队列空闲)。当有消息立即执行或有消息将要执行都不会阻塞。

当阻塞在native层的时候,只有消息队列中插入了新的消息(messageQueue.enqueueMessage()),才能唤醒nativePollOnce方法,再往下执行next方法。

消息队列优化

  • 过滤重复消息

    在handler.sendMessage()前,将之前发送过的相同what\object的message都remove掉。

  • 取消互斥消息

  • 复用消息

    复用消息可以避免重复GC

消息空闲IdleHandler

在消息队列空闲的时候会回调IdleHandler的queueIdle()方法。

idleHandler要在messageQueue中进行添加才能接收到回调。

Looper.myQueue().addIdleHandler(idleHandler)

使用独享的Looper——HandlerThread

在有Looper的线程就可以直接创建Handler,或者创建Handler的时候传入一个looper也可以。

delay大于Handler Looper的周期时,delay时间基本可靠

Looper负载越高,任务越容易积压,导致UI卡顿

在对时间要求高的时候,不要用Handler的delay作为计时的依据

ThreadPoolExecutor

发表于 2019-06-27
1
2
3
4
5
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}

RxJava

发表于 2019-06-25

Flowable / Onservable

  • Flowable实现了Publisher接口(需要实现subscribe(Subscriber<? Super T> s)方法)

  • Observable实现了ObservableSource接口(需要实现subcribe(Observer<? Super T> observer)方法)

Flowable和Observable都需要实现subscribe()方法,但是入参不同。

几个类的区分:

  • ObservableSource 可观察源
  • Observer 观察者

报刊(ObservableSource)、报刊订阅者(Observer)、订阅报刊的行为(subscribe)

  • Publisher 发布者
  • Subscriber 订阅者

报刊发布人(Publisher)、报刊订阅者(Subscriber)、订阅报刊的行为(subscribe)

  • subscribe() 订阅

Single\Completable\Maybe

Single、Completable、Maybe是简化的Observable,只是具有少部分功能

  • Single:只能发射一条单一数据或者一条异常通知,不能发射完成通知,数据与通知只能发射一个,二选一。
  • Completable:只能发射一条完成通知或者一条异常通知,不能发射数据,要么发射完成通知要么发射异常通,二选一。
  • Maybe:只能发射一条单一数据,和发射一条完成通知,或者一条异常通知,完成通知和异常通知二选一,只能在发射完成通知或异常通知之前发射数据,否则发射数据无效

五种观察者模式与被观察者关系:

被观察者 Observable Flowable Single Completable Maybe
观察者 Observer Subscriber SingleObserver CompletableObserver MaybeObserver

Hot Observable 和 Cold Observable

View

发表于 2019-06-24

StictMode

发表于 2019-06-19
12345

Zhujiaqqq

Java Android Python Algorithm and Machine learning

43 日志
6 分类
55 标签
© 2019 Zhujiaqqq
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.4