老司机种菜


  • 首页

  • 分类

  • 关于

  • 归档

  • 标签

  • 公益404

  • 搜索

Android内存

发表于 2018-11-06 | 分类于 tips

查看内存方式

Running services

通过手机上Running services的Activity查看,可以通过Setting->Applications->Running services进。 PS.其实现在有很多查看内存管理的第三方应用了,例如手机管家

用ActivityManager的getMemoryInfo(ActivityManager.MemoryInfo outInfo)

ActivityManager.getMemoryInfo()主要是用于得到当前系统剩余内存的及判断是否处于低内存运行。

1
2
3
4
5
6
7
8
9
private void displayBriefMemory() {   
final ActivityManager activityManager = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
ActivityManager.MemoryInfo info = new ActivityManager.MemoryInfo();
activityManager.getMemoryInfo(info);
Log.i(tag,"系统剩余内存:"+(info.availMem >> 10)+"k");
Log.i(tag,"系统是否处于低内存运行:"+info.lowMemory);
Log.i(tag,"当系统剩余内存低于"+info.threshold+"时就看成低内存运行");

}

ActivityManager.getMemoryInfo()是用ActivityManager.MemoryInfo返回结果,而不是Debug.MemoryInfo,他们不一样的。 ActivityManager.MemoryInfo只有三个Field:

  • availMem:表示系统剩余内存
  • lowMemory:它是boolean值,表示系统是否处于低内存运行
  • hreshold:它表示当系统剩余内存低于好多时就看成低内存运行

在代码中使用Debug的getMemoryInfo(Debug.MemoryInfo memoryInfo)或ActivityManager的MemoryInfo[] getProcessMemoryInfo(int[] pids)

该方式得到的MemoryInfo所描述的内存使用情况比较详细.数据的单位是KB.

MemoryInfo的Field如下

  • dalvikPrivateDirty: The private dirty pages used by dalvik。
  • dalvikPss :The proportional set size for dalvik.
  • dalvikSharedDirty :The shared dirty pages used by dalvik.
  • nativePrivateDirty :The private dirty pages used by the native heap.
  • nativePss :The proportional set size for the native heap.
  • nativeSharedDirty :The shared dirty pages used by the native heap.
  • otherPrivateDirty :The private dirty pages used by everything else.
  • otherPss :The proportional set size for everything else.
  • otherSharedDirty :The shared dirty pages used by everything else.
  • Android和Linux一样有大量内存在进程之间进程共享。某个进程准确的使用好多内存实际上是很难统计的。
  • 因为有paging out to disk(换页),所以如果你把所有映射到进程的内存相加,它可能大于你的内存的实际物理大小。
  • dalvik:是指dalvik所使用的内存。
  • native:是被native堆使用的内存。应该指使用C\C++在堆上分配的内存。
  • other:是指除dalvik和native使用的内存。但是具体是指什么呢?至少包括在C\C++分配的非堆内存,比如分配在栈上的内存。puzlle!
  • private:是指私有的。非共享的。
  • share:是指共享的内存。
  • PSS:实际使用的物理内存(比例分配共享库占用的内存)
  • Pss:它是把共享内存根据一定比例分摊到共享它的各个进程来计算所得到进程使用内存。网上又说是比例分配共享库占用的内存,那么至于这里的共享是否只是库的共享,还是不清楚。
  • PrivateDirty:它是指非共享的,又不能换页出去(can not be paged to disk )的内存的大小。比如Linux为了提高分配内存速度而缓冲的小对象,即使你的进程结束,该内存也不会释放掉,它只是又重新回到缓冲中而已。
  • SharedDirty:参照PrivateDirty我认为它应该是指共享的,又不能换页出去(can not be paged to disk )的内存的大小。比如Linux为了提高分配内存速度而缓冲的小对象,即使所有共享它的进程结束,该内存也不会释放掉,它只是又重新回到缓冲中而已。

具体代码请参考实例1

  1. 注意1:MemoryInfo所描述的内存使用情况都可以通过命令adb shell “dumpsys meminfo %curProcessName%” 得到。
  2. 注意2:如果想在代码中同时得到多个进程的内存使用或非本进程的内存使用情况请使用ActivityManager的MemoryInfo[] getProcessMemoryInfo(int[] pids),否则Debug的getMemoryInfo(Debug.MemoryInfo memoryInfo)就可以了。
  3. 注意3:可以通过ActivityManager的List<ActivityManager.RunningAppProcessInfo> getRunningAppProcesses()得到当前所有运行的进程信息。ActivityManager.RunningAppProcessInfo中就有进程的id,名字以及该进程包括的所有apk包名列表等。
  4. 注意4:数据的单位是KB.

    方式4、使用Debug的getNativeHeapSize (),getNativeHeapAllocatedSize (),getNativeHeapFreeSize ()方法。

该方式只能得到Native堆的内存大概情况,数据单位为字节。

static long getNativeHeapAllocatedSize()

Returns the amount of allocated memory in the native heap.

返回的是当前进程navtive堆中已使用的内存大小 static long getNativeHeapFreeSize()

Returns the amount of free memory in the native heap.

返回的是当前进程navtive堆中已经剩余的内存大小

static long getNativeHeapSize()

Returns the size of the native heap.

返回的是当前进程navtive堆本身总的内存大小

示例代码:

1
2
3
4
5
Log.i(tag,"NativeHeapSizeTotal:"+(Debug.getNativeHeapSize()>>10));

Log.i(tag,"NativeAllocatedHeapSize:"+(Debug.getNativeHeapAllocatedSize()>>10));

Log.i(tag,"NativeAllocatedFree:"+(Debug.getNativeHeapFreeSize()>>10));

注意:DEBUG中居然没有与上面相对应的关于dalvik的函数。

使用”adb shell cat /proc/meminfo” 命令。

该方式只能得出系统整个内存的大概使用情况。

MemTotal: 395144 kB

MemFree: 184936 kB

Buffers: 880 kB

Cached: 84104 kB

SwapCached: 0 kB

……………………………………………………………………………………

MemTotal :可供系统和用户使用的总内存大小 (它比实际的物理内存要小,因为还有些内存要用于radio, DMA buffers, 等).

MemFree:剩余的可用内存大小。这里该值比较大,实际上一般Android system 的该值通常都很小,因为我们尽量让进程都保持运行,这样会耗掉大量内存。

Cached: 这个是系统用于文件缓冲等的内存. 通常systems需要20MB 以避免bad paging states;。当内存紧张时,the Android out of memory killer将杀死一些background进程,以避免他们消耗过多的cached RAM ,当然如果下次再用到他们,就需要paging. 那么是说background进程的内存包含在该项中吗?

使用“adb shell ps -x”命令

该方式主要得到的是内存信息是VSIZE 和RSS。

USER PID PPID VSIZE RSS WCHAN PC NAME

…………………….省略……………………………

app_70 3407 100 267104 22056 ffffffff afd0eb18 S com.teleca.robin.test (u:55, s:12)

app_7 3473 100 268780 21784 ffffffff afd0eb18 S com.android.providers.calendar (u:16, s:8)

radio 3487 100 267980 21140 ffffffff afd0eb18 S com.osp.app.signin (u:11, s:12)

system 3511 100 273232 22024 ffffffff afd0eb18 S com.android.settings (u:11, s:4)

app_15 3546 100 267900 20300 ffffffff afd0eb18 S com.sec.android.providers.drm (u:15, s:6)

app_59 3604 100 272028 22856 ffffffff afd0eb18 S com.wssyncmldm (u:231, s:54)

root 4528 2 0 0 c0141e4c 00000000 S flush-138:13 (u:0, s:0)

root 4701 152 676 336 c00a68c8 afd0e7cc S /system/bin/sh (u:0, s:0)

root 4702 4701 820 340 00000000 afd0d8bc R ps (u:0, s:5)

VSZIE:意义暂时不明。

dumpsys meminfo

adb shell dumpsys meminfo -a <process id>/<process name>来查看一个进程的memory。截图如下: image

Naitve Heap Size: 从mallinfo usmblks获得,代表最大总共分配空间

Native Heap Alloc: 从mallinfo uorblks获得,总共分配空间

Native Heap Free: 从mallinfo fordblks获得,代表总共剩余空间

Native Heap Size 约等于Native Heap Alloc + Native Heap Free

mallinfo是一个C库, mallinfo 函数提供了各种各样的通过C的malloc()函数分配的内存的统计信息。

Dalvik Heap Size:从Runtime totalMemory()获得,Dalvik Heap总共的内存大小。

Dalvik Heap Alloc: Runtime totalMemory()-freeMemory() ,Dalvik Heap分配的内存大小。

Dalvik Heap Free:从Runtime freeMemory()获得,Dalvik Heap剩余的内存大小。

Dalvik Heap Size 约等于Dalvik Heap Alloc + Dalvik Heap Free

OtherPss, include Cursor,Ashmem, Other Dev, .so mmap, .jar mmap, .apk mmap, .ttf mmap, .dex mmap, Other mmap, Unkown统计信息都可以在process的smap文件看到。

Objects and SQL 信息都是从Android Debug信息中获得。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
其他类型               smap 路径名称          描述

Cursor /dev/ashmem/Cursor Cursor消耗的内存(KB)

Ashmem /dev/ashmem 匿名共享内存用来提供共享内存通过分配一个多个进程

可以共享的带名称的内存块

Other dev /dev/ 内部driver占用的在 “Other dev”

.so mmap .so C 库代码占用的内存

.jar mmap .jar Java 文件代码占用的内存

.apk mmap .apk apk代码占用的内存

.ttf mmap .ttf ttf 文件代码占用的内存

.dex mmap .dex Dex 文件代码占用的内存

Other mmap 其他文件占用的内存

rxjava用法

发表于 2018-11-05 | 分类于 language

现在越来越多Android开发者使用到RxJava,在Android使用RxJava主要有如下好处: 1,轻松切换线程。以前我们切换线程主要使用Handler等手段来做。 2,轻松解决回调的嵌套问题。现在的app业务逻辑越来越复杂,多的时候3,4层回调嵌套,使得代码可维护性变得很差。RxJava链式调用使得这些调用变得扁平化。

随着RxJava的流行,越来越多的开源项目开始支持RxJava,像Retrofit、GreenDao等。这些开源项目支持RxJava使得我们解决复杂业务变得非常方便。

但是这些还不够,有的时候我们自己的封装的业务也需要支持RxJava,举个例子:查询数据、处理本地文件等操作,总而言之就是一些耗时任务。而且还要处理这些操作的成功、失败、线程切换等操作。

RX操作符之Observable的创建方式

创建方式一:just、from、repeat、repeatWhen
1.just方法

创建发送指定值的Observerble,just只是简单的原样发射,将数组或Iterable当做单个数据。如果传递的值为null,则发送的Observable的值为null。参数最多为9个

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Observable<String> myObservable = Observable.just("just1","just1","just1","just1","just1","just1","just1","just1","just1","just1");

Subscriber<String> mySubscriber = new Subscriber<String>() {
@Override
public void onNext(String s) {
System.out.println(s);
}

@Override
public void onCompleted() {
System.out.println("onCompleted.................");
}

@Override
public void onError(Throwable e) {
System.out.println("onError....................");
}
};

myObservable.subscribe(mySubscriber);

结果:

1
2
3
4
5
6
7
8
9
10
11
just1
just1
just1
just1
just1
just1
just1
just1
just1
just1
onCompleted.............
2.from方法

将数据转换成为Observables,而不是需要混合使用Observables和其它类型的数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
String[]items = {"just1","just1","just1","just1","just1","just1"};

Observable<String> myObservable = Observable.from(items);

Subscriber<String> mySubscriber = new Subscriber<String>() {
@Override
public void onNext(String s) {
System.out.println(s);
}

@Override
public void onCompleted() {
System.out.println("onCompleted.................");
}

@Override
public void onError(Throwable e) {
System.out.println("onError....................");
}
};

myObservable.subscribe(mySubscriber);

结果:

1
2
3
4
5
6
7
just1
just1
just1
just1
just1
just1
onCompleted.............
3.repeat方法

repeat()重复地执行某个操作,如果不传递参数,结果将会被无限地重复执行,默认在trampoline调度器上执行,该方法为非静态方法,不可以直接通过Observable来调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
String[]items = {"just1","just2","just3","just4","just5","just6"};

Observable<String> myObservable = Observable.from(items).repeat();


Subscriber<String> mySubscriber = new Subscriber<String>() {
@Override
public void onNext(String s) {
System.out.println(s);
}

@Override
public void onCompleted() {
System.out.println("onCompleted.................");
}

@Override
public void onError(Throwable e) {
System.out.println("onError....................");
}
};

myObservable.subscribe(mySubscriber);

repeat()如果传入数字类型的参数,则重复地执行指定次数的某个操作,默认在trampoline调度器上执行,不可以直接通过Observable来调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
String[]items = {"just1","just2","just3","just4","just5","just6"};

Observable<String> myObservable = Observable.from(items).repeat(2);


Subscriber<String> mySubscriber = new Subscriber<String>() {
@Override
public void onNext(String s) {
System.out.println(s);
}

@Override
public void onCompleted() {
System.out.println("onCompleted.................");
}

@Override
public void onError(Throwable e) {
System.out.println("onError....................");
}
};

myObservable.subscribe(mySubscriber);

repeatWhen()不是缓存和重放原始Observable的数据序列,而是有条件的重新订阅和发射原来的Observable,当Observable中的call()方法中调用了重复执行的代码时,onNext()将会被重复执行。如果该方法执行后返回void,则结束执行

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
final String[]items = {"just1","just2","just3","just4","just5","just6"};

Observable<String> myObservable = Observable.from(items).repeatWhen(new Func1<Observable<? extends Void>, Observable<?>>() {
@Override
public Observable<?> call(Observable<? extends Void> observable) {
return observable.delay(5, TimeUnit.SECONDS);
}
});


Subscriber<String> mySubscriber = new Subscriber<String>() {
@Override
public void onNext(String s) {
System.out.println("onNext.................."+s);
}

@Override
public void onCompleted() {
System.out.println("onCompleted.................");
}

@Override
public void onError(Throwable e) {
System.out.println("onError....................");
}
};

myObservable.subscribe(mySubscriber);

doWhile 属于可选包rxjava-computation-expressions,不是RxJava标准操作符的一部分。doWhile在原始序列的每次重复后检查某个条件,如果满足条件才重复发射。 whileDo 属于可选包rxjava-computation-expressions,不是RxJava标准操作符的一部分。whileDo在原始序列的每次重复前检查某个条件,如果满足条件才重复发射。

创建方式二:defer、range、interval、timer、Empty、Never、Throw
1.defer

Defer操作符会一直等待直到有观察者订阅它,然后它使用Observable工厂方法生成一个Observable。它对每个观察者都这样做,因此尽管每个订阅者都以为自己订阅的是同一个Observable,事实上每个订阅者获取的是它们自己的单独的数据序列。

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
String[]items = {"just1","just2","just3","just4","just5","just6"};

Observable<String> myObservable = Observable.from(items);

Subscriber<String> mySubscriber = new Subscriber<String>() {
@Override
public void onNext(String s) {
System.out.println("onNext................."+s);
}

@Override
public void onCompleted() {
System.out.println("onCompleted.................");
}

@Override
public void onError(Throwable e) {
System.out.println("onError....................");
}
};

Subscriber<String> mySubscriber1 = new Subscriber<String>() {
@Override
public void onNext(String s) {
System.out.println("onNext................."+s);
}

@Override
public void onCompleted() {
System.out.println("onCompleted.................");
}

@Override
public void onError(Throwable e) {
System.out.println("onError....................");
}
};

myObservable.subscribe(mySubscriber);
myObservable.subscribe(mySubscriber1);

image 尽管打印的结果一样,但是它们不是取自同一个Observable的数据

2.Range

创建一个发射特定整数序列的Observable,接收两个参数,第一个参数是范围的起始值,第二个参数是范围的数据数目,即需要多产生多少个这样的值。如果你将第二个参数设为0,将导致Observable不发射任何数据(如果设置为负数,会抛异常)。range默认不在任何特定的调度器上执行。有一个变体可以通过可选参数指定Scheduler。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
String[]items = {"just1","just2","just3","just4","just5","just6"};

Observable<Integer> myObservable = Observable.range(5,10);


Subscriber<Integer> mySubscriber = new Subscriber<Integer>() {
@Override
public void onNext(Integer s) {
System.out.println("onNext................."+s);
}

@Override
public void onCompleted() {
System.out.println("onCompleted.................");
}

@Override
public void onError(Throwable e) {
System.out.println("onError....................");
}
};

myObservable.subscribe(mySubscriber);

image

3.Interval

Interval操作符返回一个Observable,它按固定的时间间隔发射一个无限递增的整数序列。RxJava将这个操作符实现为interval方法。它接受一个表示时间间隔的参数和一个表示时间单位的参数。结果递增且不断增加

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Observable<Long> myObservable = Observable.interval(2,TimeUnit.SECONDS);
Subscriber<Long> mySubscriber = new Subscriber<Long>() {
@Override
public void onNext(Long s) {
System.out.println("onNext................."+s);
}

@Override
public void onCompleted() {
System.out.println("onCompleted.................");
}

@Override
public void onError(Throwable e) {
System.out.println("onError....................");
}
};

myObservable.subscribe(mySubscriber);

image

4.Timer

创建一个Observable,它在一个给定的延迟后发射一个特殊的值,延迟2s后发送数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Observable<Long> myObservable = Observable
.timer(2,TimeUnit.SECONDS);
Subscriber<Long> mySubscriber = new Subscriber<Long>() {
@Override
public void onNext(Long s) {
System.out.println("onNext................."+s);
}

@Override
public void onCompleted() {
System.out.println("onCompleted.................");
}

@Override
public void onError(Throwable e) {
System.out.println("onError....................");
}
};
myObservable.subscribe(mySubscriber);

结果:

1
2
onNext.................0
onCompleted.................
5.Empty

创建一个不发射任何数据但是正常终止的Observable

6.Never

创建一个不发射数据也不终止的Observable

7.Throw

创建一个不发射数据以一个错误终止的Observable

Java GC

发表于 2018-10-31 | 分类于 tips

要想深入了解Java的GC(Garbage Collection),我们应该先探寻如下三个问题:

What? – 哪些内存需要回收? When? – 什么时候回收? How? – 如何回收?

GC Definition

Definition: Program itself finds and collects memory which is useless. It is a form of automatic memory management which doesn’t need programmers release memory. Java中为什么会有GC机制呢?

  • 安全性考虑;– for security.
  • 减少内存泄露;– erase memory leak in some degree.
  • 减少程序员工作量。– Programmers don’t worry about memory releasing.

What? – 哪些内存需要回收?

我们知道,内存运行时JVM会有一个运行时数据区来管理内存。它主要包括5大部分:程序计数器(Program Counter Register)、虚拟机栈(VM Stack)、本地方法栈(Native Method Stack)、方法区(Method Area)、堆(Heap).

而其中程序计数器、虚拟机栈、本地方法栈是每个线程私有的内存空间,随线程而生,随线程而亡。例如栈中每一个栈帧中分配多少内存基本上在类结构去诶是哪个下来时就已知了,因此这3个区域的内存分配和回收都是确定的,无需考虑内存回收的问题。

但方法区和堆就不同了,一个接口的多个实现类需要的内存可能不一样,我们只有在程序运行期间才会知道会创建哪些对象,这部分内存的分配和回收都是动态的,GC主要关注的是这部分内存。

总而言之,GC主要进行回收的内存是JVM中的方法区和堆;涉及到多线程(指堆)、多个对该对象不同类型的引用(指方法区),才会涉及GC的回收。

When? – 什么时候回收?

堆

在面试中经常会碰到这样一个问题(事实上笔者也碰到过):如何判断一个对象已经死去?

很容易想到的一个答案是:对一个对象添加引用计数器。每当有地方引用它时,计数器值加1;当引用失效时,计数器值减1.而当计数器的值为0时这个对象就不会再被使用,判断为已死。是不是简单又直观。然而,很遗憾。这种做法是错误的!为什么是错的呢?事实上,用引用计数法确实在大部分情况下是一个不错的解决方案,而在实际的应用中也有不少案例,但它却无法解决对象之间的循环引用问题。比如对象A中有一个字段指向了对象B,而对象B中也有一个字段指向了对象A,而事实上他们俩都不再使用,但计数器的值永远都不可能为0,也就不会被回收,然后就发生了内存泄露。。

所以,正确的做法应该是怎样呢? 在Java,C#等语言中,比较主流的判定一个对象已死的方法是:可达性分析(Reachability Analysis). 所有生成的对象都是一个称为”GC Roots”的根的子树。从GC Roots开始向下搜索,搜索所经过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链可以到达时,就称这个对象是不可达的(不可引用的),也就是可以被GC回收了。如下图所示: image

在 Java 语言中,可作为 GC Roots 的对象包括下面几种:

  • 虚拟机栈(栈帧中的本地变量表)中引用的对象
  • 方法区中类静态属性引用的对象
  • 方法区中常量引用的对象
  • 本地方法栈中 JNI(即一般说的 Native 方法)引用的对象

无论是引用计数器还是可达性分析,判定对象是否存活都与引用有关!那么,如何定义对象的引用呢?

我们希望给出这样一类描述:当内存空间还够时,能够保存在内存中;如果进行了垃圾回收之后内存空间仍旧非常紧张,则可以抛弃这些对象。所以根据不同的需求,给出如下四种引用,根据引用类型的不同,GC回收时也会有不同的操作:

  • 强引用(Strong Reference):Object obj = new Object();只要强引用还存在,GC永远不会回收掉被引用的对象。
  • 软引用(Soft Reference):描述一些还有用但非必需的对象。在系统将会发生内存溢出之前,会把这些对象列入回收范围进行二次回收(即系统将会发生内存溢出了,才会对他们进行回收。)
  • 弱引用(Weak Reference):程度比软引用还要弱一些。这些对象只能生存到下次GC之前。当GC工作时,无论内存是否足够都会将其回收(即只要进行GC,就会对他们进行回收。)
  • 虚引用(Phantom Reference):一个对象是否存在虚引用,完全不会对其生存时间构成影响。也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用关联的 唯一目的就是能在这个对象被收集器回收时收到一个系统通知。

方法区

What部分我们已经提到,GC主要回收的是堆和方法区中的内存,而上面的How主要是针对对象的回收,他们一般位于堆内。那么,方法区中的东西该怎么回收呢?

关于方法区中需要回收的是一些废弃的常量和无用的类。

  1. 废弃的常量的回收。这里看引用计数就可以了。没有对象引用该常量就可以放心的回收了。
  2. 无用的类的回收。什么是无用的类呢?
    • 该类所有的实例都已经被回收。也就是Java堆中不存在该类的任何实例;
    • 加载该类的ClassLoader已经被回收;
    • 该类对应的java.lang.Class对象没有任何地方被引用,无法在任何地方通过反射访问该类的方法。

总而言之,对于堆中的对象,主要用可达性分析判断一个对象是否还存在引用,如果该对象没有任何引用就应该被回收。而根据我们实际对引用的不同需求,又分成了4中引用,每种引用的回收机制也是不同的。对于方法区中的常量和类,当一个常量没有任何对象引用它,它就可以被回收了。而对于类,如果可以判定它为无用类,就可以被回收了。

How? – 如何回收?

标记-清除(Mark-Sweep)算法

分为两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。 缺点:效率问题,标记和清除两个过程的效率都不高;空间问题,会产生很多碎片。

复制算法

将可用内存按容量划分为大小相等的两块,每次只用其中一块。当这一块用完了,就将还存活的对象复制到另外一块上面,然后把原始空间全部回收。高效、简单。 缺点:将内存缩小为原来的一半。

标记-整理(Mark-Compat)算法

标记过程与标记-清除算法过程一样,但后面不是简单的清除,而是让所有存活的对象都向一端移动,然后直接清除掉端边界以外的内存。

分代收集(Generational Collection)算法

新生代中,每次垃圾收集时都有大批对象死去,只有少量存活,就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集; 老年代中,其存活率较高、没有额外空间对它进行分配担保,就应该使用“标记-整理”或“标记-清理”算法进行回收。

一些收集器

Serial收集器

单线程收集器,表示在它进行垃圾收集时,必须暂停其他所有的工作线程,直到它收集结束。”Stop The World”.

ParNew收集器

实际就是Serial收集器的多线程版本。

  • 并发(Parallel):指多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态;
  • 并行(Concurrent):指用户线程与垃圾收集线程同时执行,用户程序在继续运行,而垃圾收集程序运行于另一个CPU上。

####Parallel Scavenge收集器 该收集器比较关注吞吐量(Throughout)(CPU用于用户代码的时间与CPU总消耗时间的比值),保证吞吐量在一个可控的范围内。

CMS(Concurrent Mark Sweep)收集器

CMS收集器是一种以获得最短停顿时间为目标的收集器。

G1(Garbage First)收集器

从JDK1.7 Update 14之后的HotSpot虚拟机正式提供了商用的G1收集器,与其他收集器相比,它具有如下优点:并行与并发;分代收集;空间整合;可预测的停顿等。

本部分主要分析了三种不同的垃圾回收算法:Mark-Sweep, Copy, Mark-Compact. 每种算法都有不同的优缺点,也有不同的适用范围。而JVM中对垃圾回收器并没有严格的要求,不同的收集器会结合多个算法进行垃圾回收

内存分配

Java技术体系中所提倡的自动内存管理最终可以归结为自动化的解决2个问题:给对象分配内存以及回收分配给对象的内存。

对象优先在Eden分配

大多数情况下,对象在新生代Eden区分配。当Eden区没有足够的内存时,虚拟机将发起一次Minor GC。

  • Minor GC(新生代GC):指发生在新生代的垃圾收集动作,因为Java对象大多都具备朝生夕灭的特性,所以Minor GC发生的非常频繁。
  • Full GC/Major GC(老年代GC):指发生在老年代的GC,出现了Major GC,经常会伴随至少一次的Minor GC。

大对象直接进老年代

大对象是指需要大量连续内存空间的Java对象(例如很长的字符串以及数组)。

长期存活的对象将进入老年代

JVM为每个对象定义一个对象年龄计数器。

  • 如果对象在Eden出生并经历过第一次Minor GC后仍然存活,并且能够被Survivor容纳,则应该被移动到Survivor空间中,并且年龄对象设置为1;
  • 对象在Survivor区中每熬过一次Minor GC,年龄就会增加1岁,当它的年龄增加到一定程度(默认为15岁,可通过参数-XX:MaxTenuringThreshold设置),就会被晋升到老年代中。
  • 要注意的是:JVM并不是永远的要求对象的年龄必须达到MaxTenuringThreshold才能晋升老年代,如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一般,年龄大于等于该年龄的对象就可以直接进入老年代,无需等到MaxTenuringThreshold中要求的年龄。
空间分配担保

= 在发生Minor GC之前,虚拟机会先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果这个条件成立,则进行Minor GC是安全的;

  • 如果不成立,则虚拟机会查看HandlePromotionFailure设置值是否允许担保失败。如果允许,则急促检查老年代最大可用连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,将尝试着进行一次Minor GC,尽管它是有风险的;
  • 如果小于或者HandePromotionFailure设置为不允许冒险,则这时要改为进行一次Full GC.

Java内部类详解

发表于 2018-10-31 | 分类于 tips

1.内部类基础

在Java中,可以将一个类定义在另一个类里面或者一个方法里面,这样的类称为内部类。广泛意义上的内部类一般来说包括这四种:成员内部类、局部内部类、匿名内部类和静态内部类。下面就先来了解一下这四种内部类的用法。

1.1.成员内部类

成员内部类是最普通的内部类,它的定义为位于另一个类的内部,形如下面的形式:

1
2
3
4
5
6
7
8
9
10
11
12
13
class Circle {
double radius = 0;

public Circle(double radius) {
this.radius = radius;
}

class Draw { //内部类
public void drawSahpe() {
System.out.println("drawshape");
}
}
}

这样看起来,类Draw像是类Circle的一个成员,Circle称为外部类。成员内部类可以无条件访问外部类的所有成员属性和成员方法(包括private成员和静态成员)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Circle {
private double radius = 0;
public static int count =1;
public Circle(double radius) {
this.radius = radius;
}

class Draw { //内部类
public void drawSahpe() {
System.out.println(radius); //外部类的private成员
System.out.println(count); //外部类的静态成员
}
}
}

不过要注意的是,当成员内部类拥有和外部类同名的成员变量或者方法时,会发生隐藏现象,即默认情况下访问的是成员内部类的成员。如果要访问外部类的同名成员,需要以下面的形式进行访问:

1
2
外部类.this.成员变量
外部类.this.成员方法

虽然成员内部类可以无条件地访问外部类的成员,而外部类想访问成员内部类的成员却不是这么随心所欲了。在外部类中如果要访问成员内部类的成员,必须先创建一个成员内部类的对象,再通过指向这个对象的引用来访问:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Circle {
private double radius = 0;

public Circle(double radius) {
this.radius = radius;
getDrawInstance().drawSahpe(); //必须先创建成员内部类的对象,再进行访问
}

private Draw getDrawInstance() {
return new Draw();
}

class Draw { //内部类
public void drawSahpe() {
System.out.println(radius); //外部类的private成员
}
}
}

成员内部类是依附外部类而存在的,也就是说,如果要创建成员内部类的对象,前提是必须存在一个外部类的对象。创建成员内部类对象的一般方式如下:

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
public class Test {
public static void main(String[] args) {
//第一种方式:
Outter outter = new Outter();
Outter.Inner inner = outter.new Inner(); //必须通过Outter对象来创建

//第二种方式:
Outter.Inner inner1 = outter.getInnerInstance();
}
}

class Outter {
private Inner inner = null;
public Outter() {

}

public Inner getInnerInstance() {
if(inner == null)
inner = new Inner();
return inner;
}

class Inner {
public Inner() {

}
}
}

1.2.局部内部类

局部内部类是定义在一个方法或者一个作用域里面的类,它和成员内部类的区别在于局部内部类的访问仅限于方法内或者该作用域内。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class People{
public People() {

}
}

class Man{
public Man(){

}

public People getWoman(){
class Woman extends People{ //局部内部类
int age =0;
}
return new Woman();
}
}

注意,局部内部类就像是方法里面的一个局部变量一样,是不能有public、protected、private以及static修饰符的。

1.3.匿名内部类

匿名内部类应该是平时我们编写代码时用得最多的,在编写事件监听的代码时使用匿名内部类不但方便,而且使代码更加容易维护。下面这段代码是一段Android事件监听代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
scan_bt.setOnClickListener(new OnClickListener() {

@Override
public void onClick(View v) {
// TODO Auto-generated method stub

}
});

history_bt.setOnClickListener(new OnClickListener() {

@Override
public void onClick(View v) {
// TODO Auto-generated method stub

}
});

这段代码为两个按钮设置监听器,这里面就使用了匿名内部类。这段代码中的:

1
2
3
4
5
6
7
8
new OnClickListener() {

@Override
public void onClick(View v) {
// TODO Auto-generated method stub

}
}

就是匿名内部类的使用。代码中需要给按钮设置监听器对象,使用匿名内部类能够在实现父类或者接口中的方法情况下同时产生一个相应的对象,但是前提是这个父类或者接口必须先存在才能这样使用。当然像下面这种写法也是可以的,跟上面使用匿名内部类达到效果相同。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
private void setListener()
{
scan_bt.setOnClickListener(new Listener1());
history_bt.setOnClickListener(new Listener2());
}

class Listener1 implements View.OnClickListener{
@Override
public void onClick(View v) {
// TODO Auto-generated method stub

}
}

class Listener2 implements View.OnClickListener{
@Override
public void onClick(View v) {
// TODO Auto-generated method stub

}
}

这种写法虽然能达到一样的效果,但是既冗长又难以维护,所以一般使用匿名内部类的方法来编写事件监听代码。同样的,匿名内部类也是不能有访问修饰符和static修饰符的。

匿名内部类是唯一一种没有构造器的类。正因为其没有构造器,所以匿名内部类的使用范围非常有限,大部分匿名内部类用于接口回调。匿名内部类在编译的时候由系统自动起名为Outter$1.class。一般来说,匿名内部类用于继承其他类或是实现接口,并不需要增加额外的方法,只是对继承方法的实现或是重写。

1.4.静态内部类

静态内部类也是定义在另一个类里面的类,只不过在类的前面多了一个关键字static。静态内部类是不需要依赖于外部类的,这点和类的静态成员属性有点类似,并且它不能使用外部类的非static成员变量或者方法,这点很好理解,因为在没有外部类的对象的情况下,可以创建静态内部类的对象,如果允许访问外部类的非static成员就会产生矛盾,因为外部类的非static成员必须依附于具体的对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Test {
public static void main(String[] args) {
Outter.Inner inner = new Outter.Inner();
}
}

class Outter {
public Outter() {

}

static class Inner {
public Inner() {

}
}
}

2.深入理解内部类

2.1.为什么成员内部类可以无条件访问外部类的成员?

在此之前,我们已经讨论过了成员内部类可以无条件访问外部类的成员,那具体究竟是如何实现的呢?下面通过反编译字节码文件看看究竟。事实上,编译器在进行编译的时候,会将成员内部类单独编译成一个字节码文件,下面是Outter.java的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Outter {
private Inner inner = null;
public Outter() {

}

public Inner getInnerInstance() {
if(inner == null)
inner = new Inner();
return inner;
}

protected class Inner {
public Inner() {

}
}
}

编译之后,出现了两个字节码文件:

  • Outter$Inner.class
  • Outter.class 反编译Outter$Inner.class文件得到下面信息:
    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
    E:\Workspace\Test\bin\com\cxh\test2>javap -v Outter$Inner
    Compiled from "Outter.java"
    public class com.cxh.test2.Outter$Inner extends java.lang.Object
    SourceFile: "Outter.java"
    InnerClass:
    #24= #1 of #22; //Inner=class com/cxh/test2/Outter$Inner of class com/cxh/tes
    t2/Outter
    minor version: 0
    major version: 50
    Constant pool:
    const #1 = class #2; // com/cxh/test2/Outter$Inner
    const #2 = Asciz com/cxh/test2/Outter$Inner;
    const #3 = class #4; // java/lang/Object
    const #4 = Asciz java/lang/Object;
    const #5 = Asciz this$0;
    const #6 = Asciz Lcom/cxh/test2/Outter;;
    const #7 = Asciz <init>;
    const #8 = Asciz (Lcom/cxh/test2/Outter;)V;
    const #9 = Asciz Code;
    const #10 = Field #1.#11; // com/cxh/test2/Outter$Inner.this$0:Lcom/cxh/t
    est2/Outter;
    const #11 = NameAndType #5:#6;// this$0:Lcom/cxh/test2/Outter;
    const #12 = Method #3.#13; // java/lang/Object."<init>":()V
    const #13 = NameAndType #7:#14;// "<init>":()V
    const #14 = Asciz ()V;
    const #15 = Asciz LineNumberTable;
    const #16 = Asciz LocalVariableTable;
    const #17 = Asciz this;
    const #18 = Asciz Lcom/cxh/test2/Outter$Inner;;
    const #19 = Asciz SourceFile;
    const #20 = Asciz Outter.java;
    const #21 = Asciz InnerClasses;
    const #22 = class #23; // com/cxh/test2/Outter
    const #23 = Asciz com/cxh/test2/Outter;
    const #24 = Asciz Inner;

    {
    final com.cxh.test2.Outter this$0;

    public com.cxh.test2.Outter$Inner(com.cxh.test2.Outter);
    Code:
    Stack=2, Locals=2, Args_size=2
    0: aload_0
    1: aload_1
    2: putfield #10; //Field this$0:Lcom/cxh/test2/Outter;
    5: aload_0
    6: invokespecial #12; //Method java/lang/Object."<init>":()V
    9: return
    LineNumberTable:
    line 16: 0
    line 18: 9

    LocalVariableTable:
    Start Length Slot Name Signature
    0 10 0 this Lcom/cxh/test2/Outter$Inner;


    }

第11行到35行是常量池的内容,下面逐一第38行的内容:

1
final com.cxh.test2.Outter this$0;

这行是一个指向外部类对象的指针,看到这里想必大家豁然开朗了。也就是说编译器会默认为成员内部类添加了一个指向外部类对象的引用,那么这个引用是如何赋初值的呢?下面接着看内部类的构造器:

1
public com.cxh.test2.Outter$Inner(com.cxh.test2.Outter);

从这里可以看出,虽然我们在定义的内部类的构造器是无参构造器,编译器还是会默认添加一个参数,该参数的类型为指向外部类对象的一个引用,所以成员内部类中的Outter this&0 指针便指向了外部类对象,因此可以在成员内部类中随意访问外部类的成员。从这里也间接说明了成员内部类是依赖于外部类的,如果没有创建外部类的对象,则无法对Outter this&0引用进行初始化赋值,也就无法创建成员内部类的对象了。

2.2.为什么局部内部类和匿名内部类只能访问局部final变量?

想必这个问题也曾经困扰过很多人,在讨论这个问题之前,先看下面这段代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Test {
public static void main(String[] args) {

}

public void test(final int b) {
final int a = 10;
new Thread(){
public void run() {
System.out.println(a);
System.out.println(b);
};
}.start();
}
}

这段代码会被编译成两个class文件:Test.class和Test1.class。默认情况下,编译器会为匿名内部类和局部内部类起名为Outterx.class(x为正整数)。test方法中的匿名内部类的名字被起为 Test$1。上段代码中,如果把变量a和b前面的任一个final去掉,这段代码都编译不过。我们先考虑这样一个问题:当test方法执行完毕之后,变量a的生命周期就结束了,而此时Thread对象的生命周期很可能还没有结束,那么在Thread的run方法中继续访问变量a就变成不可能了,但是又要实现这样的效果,怎么办呢?Java采用了 复制 的手段来解决这个问题。将这段代码的字节码反编译可以得到下面的内容: image 我们看到在run方法中有一条指令:

1
bipush 10

这条指令表示将操作数10压栈,表示使用的是一个本地局部变量。这个过程是在编译期间由编译器默认进行,如果这个变量的值在编译期间可以确定,则编译器默认会在匿名内部类(局部内部类)的常量池中添加一个内容相等的字面量或直接将相应的字节码嵌入到执行字节码中。这样一来,匿名内部类使用的变量是另一个局部变量,只不过值和方法中局部变量的值相等,因此和方法中的局部变量完全独立开。

下面再看一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Test {
public static void main(String[] args) {

}

public void test(final int a) {
new Thread(){
public void run() {
System.out.println(a);
};
}.start();
}
}

反编译得到 image

我们看到匿名内部类Test$1的构造器含有两个参数,一个是指向外部类对象的引用,一个是int型变量,很显然,这里是将变量test方法中的形参a以参数的形式传进来对匿名内部类中的拷贝(变量a的拷贝)进行赋值初始化。

也就说如果局部变量的值在编译期间就可以确定,则直接在匿名内部里面创建一个拷贝。如果局部变量的值无法在编译期间确定,则通过构造器传参的方式来对拷贝进行初始化赋值。

从上面可以看出,在run方法中访问的变量a根本就不是test方法中的局部变量a。这样一来就解决了前面所说的 生命周期不一致的问题。但是新的问题又来了,既然在run方法中访问的变量a和test方法中的变量a不是同一个变量,当在run方法中改变变量a的值的话,会出现什么情况?

对,会造成数据不一致性,这样就达不到原本的意图和要求。为了解决这个问题,java编译器就限定必须将变量a限制为final变量,不允许对变量a进行更改(对于引用类型的变量,是不允许指向新的对象),这样数据不一致性的问题就得以解决了。

到这里,想必大家应该清楚为何 方法中的局部变量和形参都必须用final进行限定了。

2.3.静态内部类有特殊的地方吗?

从前面可以知道,静态内部类是不依赖于外部类的,也就说可以在不创建外部类对象的情况下创建内部类的对象。另外,静态内部类是不持有指向外部类对象的引用的,这个可以尝试反编译class文件看一下就知道了,是没有Outter this&0引用的。

3.内部类的使用场景和好处

为什么在Java中需要内部类?总结一下主要有以下四点:

  1. 每个内部类都能独立的继承一个接口的实现,所以无论外部类是否已经继承了某个(接口的)实现,对于内部类都没有影响。内部类使得多继承的解决方案变得完整,
  2. 方便将存在一定逻辑关系的类组织在一起,又可以对外界隐藏。
  3. 方便编写事件驱动程序
  4. 方便编写线程代码

个人觉得第一点是最重要的原因之一,内部类的存在使得Java的多继承机制变得更加完善。

4.常见的与内部类相关的笔试面试题

4.1.根据注释填写(1),(2),(3)处的代码

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
public class Test{
public static void main(String[] args){
// 初始化Bean1
(1)
bean1.I++;
// 初始化Bean2
(2)
bean2.J++;
//初始化Bean3
(3)
bean3.k++;
}
class Bean1{
public int I = 0;
}

static class Bean2{
public int J = 0;
}
}

class Bean{
class Bean3{
public int k = 0;
}
}

从前面可知,对于成员内部类,必须先产生外部类的实例化对象,才能产生内部类的实例化对象。而静态内部类不用产生外部类的实例化对象即可产生内部类的实例化对象。

创建静态内部类对象的一般形式为: 外部类类名.内部类类名 xxx = new 外部类类名.内部类类名() 创建成员内部类对象的一般形式为: 外部类类名.内部类类名 xxx = 外部类对象名.new 内部类类名() 因此,(1),(2),(3)处的代码分别为:

1
2
Test test = new Test();    
Test.Bean1 bean1 = test.new Bean1();
1
Test.Bean2 b2 = new Test.Bean2();
1
2
Bean bean = new Bean();     
Bean.Bean3 bean3 = bean.new Bean3();

4.2.下面这段代码的输出结果是什么?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Test {
public static void main(String[] args) {
Outter outter = new Outter();
outter.new Inner().print();
}
}


class Outter
{
private int a = 1;
class Inner {
private int a = 2;
public void print() {
int a = 3;
System.out.println("局部变量:" + a);
System.out.println("内部类变量:" + this.a);
System.out.println("外部类变量:" + Outter.this.a);
}
}
}
1
2
3
3
2
1

最后补充一点知识:关于成员内部类的继承问题。一般来说,内部类是很少用来作为继承用的。但是当用来继承的话,要注意两点:

  1. 成员内部类的引用方式必须为 Outter.Inner.
  2. 构造器中必须有指向外部类对象的引用,并通过这个引用调用super()。这段代码摘自《Java编程思想》
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    class WithInner {
    class Inner{

    }
    }
    class InheritInner extends WithInner.Inner {

    // InheritInner() 是不能通过编译的,一定要加上形参
    InheritInner(WithInner wi) {
    wi.super(); //必须有这句调用
    }

    public static void main(String[] args) {
    WithInner wi = new WithInner();
    InheritInner obj = new InheritInner(wi);
    }
    }

多线程只是总结

发表于 2018-10-25 | 分类于 language

什么是线程

线程5种状态

image

  1. 新建状态(New): 当用new操作符创建一个线程时, 例如new Thread(r),线程还没有开始运行,此时线程处在新建状态。 当一个线程处于新生状态时,程序还没有开始运行线程中的代码
  2. 就绪状态(Runnable) 一个新创建的线程并不自动开始运行,要执行线程,必须调用线程的start()方法。当线程对象调用start()方法即启动了线程,start()方法创建线程运行的系统资源,并调度线程运行run()方法。当start()方法返回后,线程就处于就绪状态。 处于就绪状态的线程并不一定立即运行run()方法,线程还必须同其他线程竞争CPU时间,只有获得CPU时间才可以运行线程。因为在单CPU的计算机系统中,不可能同时运行多个线程,一个时刻仅有一个线程处于运行状态。因此此时可能有多个线程处于就绪状态。对多个处于就绪状态的线程是由Java运行时系统的线程调度程序(thread scheduler)来调度的。
  3. 运行状态(Running) 当线程获得CPU时间后,它才进入运行状态,真正开始执行run()方法.
  4. 阻塞状态(Blocked) 线程运行过程中,可能由于各种原因进入阻塞状态: 1>线程通过调用sleep方法进入睡眠状态; 2>线程调用一个在I/O上被阻塞的操作,即该操作在输入输出操作完成之前不会返回到它的调用者; 3>线程试图得到一个锁,而该锁正被其他线程持有; 4>线程在等待某个触发条件;
    所谓阻塞状态是正在运行的线程没有运行结束,暂时让出CPU,这时其他处于就绪状态的线程就可以获得CPU时间,进入运行状态。
  5. 死亡状态(Dead) 有两个原因会导致线程死亡: 1) run方法正常退出而自然死亡, 2) 一个未捕获的异常终止了run方法而使线程猝死。 为了确定线程在当前是否存活着(就是要么是可运行的,要么是被阻塞了),需要使用isAlive方法。如果是可运行或被阻塞,这个方法返回true; 如果线程仍旧是new状态且不是可运行的, 或者线程死亡了,则返回false.

线程同步的7种方式

java允许多线程并发控制,当多个线程同时操作一个可共享的资源变量时(如数据的增删改查), 将会导致数据不准确,相互之间产生冲突,因此加入同步锁以避免在该线程没有完成操作之前,被其他线程的调用,从而保证了该变量的唯一性和准确性。

方式1:同步方法

即有synchronized关键字修饰的方法。 由于java的每个对象都有一个内置锁,当用此关键字修饰方法时, 内置锁会保护整个方法。在调用该方法前,需要获得内置锁,否则就处于阻塞状态。 代码如:

1
public synchronized void save(){}

注: synchronized关键字也可以修饰静态方法,此时如果调用该静态方法,将会锁住整个类

方式2:同步代码块

即有synchronized关键字修饰的语句块。 被该关键字修饰的语句块会自动被加上内置锁,从而实现同步 代码如:

1
2
synchronized(object){ 
}

注:同步是一种高开销的操作,因此应该尽量减少同步的内容。通常没有必要同步整个方法,使用synchronized代码块同步关键代码即可。

方式3:使用特殊域变量(volatile)实现线程同步
  1. volatile关键字为域变量的访问提供了一种免锁机制,
  2. 使用volatile修饰域相当于告诉虚拟机该域可能会被其他线程更新,
  3. 因此每次使用该域就要重新计算,而不是使用寄存器中的值
  4. volatile不会提供任何原子操作,它也不能用来修饰final类型的变量
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    class Bank {
    //需要同步的变量加上volatile
    private volatile int account = 100;

    public int getAccount() {
    return account;
    }
    //这里不再需要synchronized
    public void save(int money) {
    account += money;
    }
    }

注:多线程中的非同步问题主要出现在对域的读写上,如果让域自身避免这个问题,则就不需要修改操作该域的方法。 用final域,有锁保护的域和volatile域可以避免非同步的问题。

方式4:使用重入锁实现线程同步

在JavaSE5.0中新增了一个java.util.concurrent包来支持同步。 ReentrantLock类是可重入、互斥、实现了Lock接口的锁,它与使用synchronized方法和快具有相同的基本行为和语义,并且扩展了其能力 ReenreantLock类的常用方法有:

  1. ReentrantLock() : 创建一个ReentrantLock实例
  2. lock() : 获得锁
  3. unlock() : 释放锁

注:ReentrantLock()还有一个可以创建公平锁的构造方法,但由于能大幅度降低程序运行效率,不推荐使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Bank {

private int account = 100;
//需要声明这个锁
private Lock lock = new ReentrantLock();
public int getAccount() {
return account;
}
//这里不再需要synchronized
public void save(int money) {
lock.lock();
try{
account += money;
}finally{
lock.unlock();
}

}
}

注:关于Lock对象和synchronized关键字的选择:

  1. 最好两个都不用,使用一种java.util.concurrent包提供的机制,能够帮助用户处理所有与锁相关的代码。
  2. 如果synchronized关键字能满足用户的需求,就用synchronized,因为它能简化代码
  3. 如果需要更高级的功能,就用ReentrantLock类,此时要注意及时释放锁,否则会出现死锁,通常在finally代码释放锁
方式5:使用局部变量实现线程同步

如果使用ThreadLocal管理变量,则每一个使用该变量的线程都获得该变量的副本, 副本之间相互独立,这样每一个线程都可以随意修改自己的变量副本,而不会对其他线程产生影响。

ThreadLocal 类的常用方法

  • ThreadLocal() : 创建一个线程本地变量
  • get() : 返回此线程局部变量的当前线程副本中的值
  • initialValue() : 返回此线程局部变量的当前线程的”初始值”
  • set(T value) : 将此线程局部变量的当前线程副本中的值设置为value

首先定义一个ThreadLocal对象,这里选择Boolean类型的,如下所示:

private ThreadLocalmBooleanThreadLocal = new ThreadLocal();

然后分别在主线程、子线程1和子线程2中设置和访问它的值,代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
mBooleanThreadLocal.set(true);
Log.d(TAG, "[Thread#main]mBooleanThreadLocal=" + mBooleanThreadLocal.get());

new Thread("Thread#1") {
@Override
public void run() {
mBooleanThreadLocal.set(false);
Log.d(TAG, "[Thread#1]mBooleanThreadLocal=" + mBooleanThreadLocal.get());
};
}.start();

new Thread("Thread#2") {
@Override
public void run() {
Log.d(TAG, "[Thread#2]mBooleanThreadLocal=" + mBooleanThreadLocal.get());
};
}.start();

上面的代码中,在主线程中设置mBooleanThreadLocal的值为true,在子线程1中设置mBooleanThreadLocal的值为false,在子线程2中不设置mBooleanThreadLocal的值,然后分别在3个线程中通过get方法去mBooleanThreadLocal的值,根据前面对ThreadLocal的描述,这个时候,主线程中应该是true,子线程1中应该是false,而子线程2中由于没有设置值,所以应该是null,安装并运行程序,日志如下所示:

1
2
3
4
5
D/TestActivity(8676):[Thread#main]mBooleanThreadLocal=true

D/TestActivity(8676):[Thread#1]mBooleanThreadLocal=false

D/TestActivity(8676):[Thread#2]mBooleanThreadLocal=null

注:ThreadLocal与同步机制

  1. ThreadLocal与同步机制都是为了解决多线程中相同变量的访问冲突问题。
  2. 前者采用以”空间换时间”的方法,后者采用以”时间换空间”的方式
方式6:使用阻塞队列实现线程同步

前面5种同步方式都是在底层实现的线程同步,但是我们在实际开发当中,应当尽量远离底层结构。使用javaSE5.0版本中新增的java.util.concurrent包将有助于简化开发。 这里主要是使用LinkedBlockingQueue来实现线程的同步. LinkedBlockingQueue是一个基于已连接节点的,范围任意的blocking queue。 队列是先进先出的顺序(FIFO),关于队列以后会详细讲解~ LinkedBlockingQueue 类常用方法

  • LinkedBlockingQueue() : 创建一个容量为Integer.MAX_VALUE的LinkedBlockingQueue
  • put(E e) : 在队尾添加一个元素,如果队列满则阻塞
  • size() : 返回队列中的元素个数
  • take() : 移除并返回队头元素,如果队列空则阻塞
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
package com.xhj.thread;

import java.util.Random;
import java.util.concurrent.LinkedBlockingQueue;

/**
* 用阻塞队列实现线程同步 LinkedBlockingQueue的使用
*
* @author XIEHEJUN
*
*/
public class BlockingSynchronizedThread {
/**
* 定义一个阻塞队列用来存储生产出来的商品
*/
private LinkedBlockingQueue<Integer> queue = new LinkedBlockingQueue<Integer>();
/**
* 定义生产商品个数
*/
private static final int size = 10;
/**
* 定义启动线程的标志,为0时,启动生产商品的线程;为1时,启动消费商品的线程
*/
private int flag = 0;

private class LinkBlockThread implements Runnable {
@Override
public void run() {
int new_flag = flag++;
System.out.println("启动线程 " + new_flag);
if (new_flag == 0) {
for (int i = 0; i < size; i++) {
int b = new Random().nextInt(255);
System.out.println("生产商品:" + b + "号");
try {
queue.put(b);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("仓库中还有商品:" + queue.size() + "个");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
} else {
for (int i = 0; i < size / 2; i++) {
try {
int n = queue.take();
System.out.println("消费者买去了" + n + "号商品");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("仓库中还有商品:" + queue.size() + "个");
try {
Thread.sleep(100);
} catch (Exception e) {
// TODO: handle exception
}
}
}
}
}

public static void main(String[] args) {
BlockingSynchronizedThread bst = new BlockingSynchronizedThread();
LinkBlockThread lbt = bst.new LinkBlockThread();
Thread thread1 = new Thread(lbt);
Thread thread2 = new Thread(lbt);
thread1.start();
thread2.start();

}

}

注:BlockingQueue定义了阻塞队列的常用方法,尤其是三种添加元素的方法,我们要多加注意,当队列满时:

  • add()方法会抛出异常
  • offer()方法返回false
  • put()方法会阻塞
方式7:使用原子变量实现线程同步

需要使用线程同步的根本原因在于对普通变量的操作不是原子的。

那么什么是原子操作呢?原子操作就是指将读取变量值、修改变量值、保存变量值看成一个整体来操作,即-这几种行为要么同时完成,要么都不完成。在java的util.concurrent.atomic包中提供了创建了原子类型变量的工具类,使用该类可以简化线程同步。

其中AtomicInteger 表可以用原子方式更新int的值,可用在应用程序中(如以原子方式增加的计数器), 但不能用于替换Integer;可扩展Number,允许那些处理机遇数字类的工具和实用工具进行统一访问。

AtomicInteger类常用方法:

  • AtomicInteger(int initialValue) : 创建具有给定初始值的新的AtomicInteger
  • addAddGet(int dalta) : 以原子方式将给定值与当前值相加
  • get() : 获取当前值
1
2
3
4
5
6
7
8
9
10
11
class Bank {
private AtomicInteger account = new AtomicInteger(100);

public AtomicInteger getAccount() {
return account;
}

public void save(int money) {
account.addAndGet(money);
}
}

补充–原子操作主要有: 对于引用变量和大多数原始变量(long和double除外)的读写操作; 对于所有使用volatile修饰的变量(包括long和double)的读写操作。

线程局部存储ThreadLocal

分析Java线程中断机制stop和interrupted的用法

当我们点击某个杀毒软件的取消按钮来停止查杀病毒时,当我们在控制台敲入quit命令以结束某个后台服务时……都需要通过一个线程去取消另一个线程正在执行的任务。Java没有提供一种安全直接的方法来停止某个线程,但是Java提供了中断机制。如果对Java中断没有一个全面的了解,可能会误以为被中断的线程将立马退出运行,但事实并非如此。中断机制是如何工作的?捕获或检测到中断后,是抛出InterruptedException还是重设中断状态以及在方法中吞掉中断状态会有什么后果?Thread.stop与中断相比又有哪些异同?什么情况下需要使用中断?中断的原理Java中断机制是一种协作机制,也就是说通过中断并不能直接终止另一个线程,而需要被中断的线程自己处理中断。这好比是家里的父母叮嘱在外的子女要注意身体,但子女是否注意身体,怎么注意身体则完全取决于自己。Java中断模型也是这么简单,每个线程对象里都有一个boolean类型的标识(不一定就要是Thread类的字段,实际上也的确不是,这几个方法最终都是通过native方法来完成的),代表着是否有中断请求(该请求可以来自所有线程,包括被中断的线程本身)。例如,当线程t1想中断线程t2,只需要在线程t1中将线程t2对象的中断标识置为true,然后线程2可以选择在合适的时候处理该中断请求,甚至可以不理会该请求,就像这个线程没有被中断一样。java.lang.Thread类提供了几个方法来操作这个中断状态,这些方法包括:

  • public static boolean interrupted测试当前线程是否已经中断。线程的中断状态 由该方法清除。换句话说,如果连续两次调用该方法,则第二次调用将返回false(在第一次调用已清除了其中断状态之后,且第二次调用检验完中断状态前,当前线程再次中断的情况除外)。
  • public boolean isInterrupted()测试线程是否已经中断。线程的中断状态不受该方法的影响。
  • public void interrupt()中断线程。其中,interrupt方法是唯一能将中断状态设置为true的方法。静态方法interrupted会将当前线程的中断状态清除,但这个方法的命名极不直观,很容易造成误解,需要特别注意。

上面的例子中,线程t1通过调用interrupt方法将线程t2的中断状态置为true,t2可以在合适的时候调用interrupted或isInterrupted来检测状态并做相应的处理。此外,类库中的有些类的方法也可能会调用中断,如FutureTask中的cancel方法,如果传入的参数为true,它将会在正在运行异步任务的线程上调用interrupt方法,如果正在执行的异步任务中的代码没有对中断做出响应,那么cancel方法中的参数将不会起到什么效果;又如ThreadPoolExecutor中的shutdownNow方法会遍历线程池中的工作线程并调用线程的interrupt方法来中断线程,所以如果工作线程中正在执行的任务没有对中断做出响应,任务将一直执行直到正常结束。

既然Java中断机制只是设置被中断线程的中断状态,那么被中断线程该做些什么?显然,作为一种协作机制,不会强求被中断线程一定要在某个点进行处理。实际上,被中断线程只需在合适的时候处理即可,如果没有合适的时间点,甚至可以不处理,这时候在任务处理层面,就跟没有调用中断方法一样。“合适的时候”与线程正在处理的业务逻辑紧密相关,例如,每次迭代的时候,进入一个可能阻塞且无法中断的方法之前等,但多半不会出现在某个临界区更新另一个对象状态的时候,因为这可能会导致对象处于不一致状态。 处理时机决定着程序的效率与中断响应的灵敏性。频繁的检查中断状态可能会使程序执行效率下降,相反,检查的较少可能使中断请求得不到及时响应。如果发出中断请求之后,被中断的线程继续执行一段时间不会给系统带来灾难,那么就可以将中断处理放到方便检查中断,同时又能从一定程度上保证响应灵敏度的地方。当程序的性能指标比较关键时,可能需要建立一个测试模型来分析最佳的中断检测点,以平衡性能和响应灵敏性。

一般说来,当可能阻塞的方法声明中有抛出InterruptedException则暗示该方法是可中断的,如BlockingQueue#put、BlockingQueue#take、Object#wait、Thread#sleep等,如果程序捕获到这些可中断的阻塞方法抛出的InterruptedException或检测到中断后,这些中断信息该如何处理?一般有以下两个通用原则:

  1. 如果遇到的是可中断的阻塞方法抛出InterruptedException,可以继续向方法调用栈的上层抛出该异常,如果是检测到中断,则可清除中断状态并抛出InterruptedException,使当前方法也成为一个可中断的方法。 若有时候不太方便在方法上抛出InterruptedException,比如要实现的某个接口中的方法签名上没有throws InterruptedException,这时就可以捕获可中断方法的InterruptedException并通过Thread.currentThread.interrupt()来重新设置中断状态。如果是检测并清除了中断状态,亦是如此。 一般的代码中,尤其是作为一个基础类库时,绝不应当吞掉中断,即捕获到InterruptedException后在catch里什么也不做,清除中断状态后又不重设中断状态也不抛出InterruptedException等。因为吞掉中断状态会导致方法调用栈的上层得不到这些信息。当然,凡事总有例外的时候,当你完全清楚自己的方法会被谁调用,而调用者也不会因为中断被吞掉了而遇到麻烦,就可以这么做。总得来说,就是要让方法调用栈的上层获知中断的发生。假设你写了一个类库,类库里有个方法amethod,在amethod中检测并清除了中断状态,而没有抛出InterruptedException,作为amethod的用户来说,他并不知道里面的细节,如果用户在调用amethod后也要使用中断来做些事情,那么在调用amethod之后他将永远也检测不到中断了,因为中断信息已经被amethod清除掉了。如果作为用户,遇到这样有问题的类库,又不能修改代码,那该怎么处理?只好在自己的类里设置一个自己的中断状态,在调用interrupt方法的时候,同时设置该状态,这实在是无路可走时才使用的方法。
  2. 中断的响应 程序里发现中断后该怎么响应?这就得视实际情况而定了。有些程序可能一检测到中断就立马将线程终止,有些可能是退出当前执行的任务,继续执行下一个任务……作为一种协作机制,这要与中断方协商好,当调用interrupt会发生些什么都是事先知道的,如做一些事务回滚操作,一些清理工作,一些补偿操作等。若不确定调用某个线程的interrupt后该线程会做出什么样的响应,那就不应当中断该线程。

Thread.interrupt VS Thread.stopThread.stop方法已经不推荐使用了。而在某些方面Thread.stop与中断机制有着相似之处。如当线程在等待内置锁或IO时,stop跟interrupt一样,不会中止这些操作;当catch住stop导致的异常时,程序也可以继续执行,虽然stop本意是要停止线程,这么做会让程序行为变得更加混乱。那么它们的区别在哪里?最重要的就是中断需要程序自己去检测然后做相应的处理,而Thread.stop会直接在代码执行过程中抛出ThreadDeath错误,这是一个java.lang.Error的子类。

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
package com.ticmy.interrupt;  
import java.util.Arrays;
import java.util.Random;
import java.util.concurrent.TimeUnit;
public class TestStop {
private static final int[] array = new int[80000];
private static final Thread t = new Thread() {
public void run() {
try {
System.out.println(sort(array));
} catch (Error err) {
err.printStackTrace();
}
System.out.println("in thread t");
}
};

static {
Random random = new Random();
for(int i = 0; i < array.length; i++) {
array[i] = random.nextInt(i + 1);
}
}

private static int sort(int[] array) {
for (int i = 0; i < array.length-1; i++){
for(int j = 0 ;j < array.length - i - 1; j++){
if(array[j] < array[j + 1]){
int temp = array[j];
array[j] = array[j + 1];
array[j + 1] = temp;
}
}
}
return array[0];
}

public static void main(String[] args) throws Exception {
t.start();
TimeUnit.SECONDS.sleep(1);
System.out.println("go to stop thread t");
t.stop();
System.out.println("finish main");
}
}

这个例子很简单,线程t里面做了一个非常耗时的排序操作,排序方法中,只有简单的加、减、赋值、比较等操作,一个可能的执行结果如下:

1
2
3
4
5
6
go to stop thread t  
java.lang.ThreadDeath
at java.lang.Thread.stop(Thread.java:758)
at com.ticmy.interrupt.TestStop.main(TestStop.java:44)
finish main
in thread t

这里sort方法是个非常耗时的操作,也就是说主线程休眠一秒钟后调用stop的时候,线程t还在执行sort方法。就是这样一个简单的方法,也会抛出错误!换一句话说,调用stop后,大部分Java字节码都有可能抛出错误,哪怕是简单的加法! 如果线程当前正持有锁,stop之后则会释放该锁。由于此错误可能出现在很多地方,那么这就让编程人员防不胜防,极易造成对象状态的不一致。例如,对象obj中存放着一个范围值:最小值low,最大值high,且low不得大于high,这种关系由锁lock保护,以避免并发时产生竞态条件而导致该关系失效。假设当前low值是5,high值是10,当线程t获取lock后,将low值更新为了15,此时被stop了,真是糟糕,如果没有捕获住stop导致的Error,low的值就为15,high还是10,这导致它们之间的小于关系得不到保证,也就是对象状态被破坏了!如果在给low赋值的时候catch住stop导致的Error则可能使后面high变量的赋值继续,但是谁也不知道Error会在哪条语句抛出,如果对象状态之间的关系更复杂呢?这种方式几乎是无法维护的,太复杂了!如果是中断操作,它决计不会在执行low赋值的时候抛出错误,这样程序对于对象状态一致性就是可控的。 正是因为可能导致对象状态不一致,stop才被禁用。

中断的使用通常,中断的使用场景有以下几个:

  • 点击某个桌面应用中的取消按钮时;
  • 某个操作超过了一定的执行时间限制需要中止时;
  • 多个线程做相同的事情,只要一个线程成功其它线程都可以取消时;
  • 一组线程中的一个或多个出现错误导致整组都无法继续时;
  • 当一个应用或服务需要停止时。

下面来看一个具体的例子。这个例子里,本打算采用GUI形式,但考虑到GUI代码会使程序复杂化,就使用控制台来模拟下核心的逻辑。这里新建了一个磁盘文件扫描的任务,扫描某个目录下的所有文件并将文件路径打印到控制台,扫描的过程可能会很长。若需要中止该任务,只需在控制台键入quit并回车即可。

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
package com.ticmy.interrupt;  
import java.io.BufferedReader;
import java.io.File;
import java.io.InputStreamReader;

public class FileScanner {
private static void listFile(File f) throws InterruptedException {
if(f == null) {
throw new IllegalArgumentException();
}
if(f.isFile()) {
System.out.println(f);
return;
}
File[] allFiles = f.listFiles();
if(Thread.interrupted()) {
throw new InterruptedException("文件扫描任务被中断");
}
for(File file : allFiles) {
//还可以将中断检测放到这里
listFile(file);
}
}

public static String readFromConsole() {
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
try {
return reader.readLine();
} catch (Exception e) {
e.printStackTrace();
return "";
}
}

public static void main(String[] args) throws Exception {
final Thread fileIteratorThread = new Thread() {
public void run() {
try {
listFile(new File("c:\\"));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
new Thread() {
public void run() {
while(true) {
if("quit".equalsIgnoreCase(readFromConsole())) {
if(fileIteratorThread.isAlive()) {
fileIteratorThread.interrupt();
return;
}
} else {
System.out.println("输入quit退出文件扫描");
}
}
}
}.start();
fileIteratorThread.start();
}
}

在扫描文件的过程中,对于中断的检测这里采用的策略是,如果碰到的是文件就不检测中断,是目录才检测中断,因为文件可能是非常多的,每次遇到文件都检测一次会降低程序执行效率。此外,在fileIteratorThread线程中,仅是捕获了InterruptedException,没有重设中断状态也没有继续抛出异常,因为我非常清楚它的使用环境,run方法的调用栈上层已经没有可能需要检测中断状态的方法了。 在这个程序中,输入quit完全可以执行System.exit(0)操作来退出程序,但正如前面提到的,这是个GUI程序核心逻辑的模拟,在GUI中,执行System.exit(0)会使得整个程序退出。

线程stop方法源码:

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
/**
* Forces the thread to stop executing.
* <p>
* If there is a security manager installed, its <code>checkAccess</code>
* method is called with <code>this</code>
* as its argument. This may result in a
* <code>SecurityException</code> being raised (in the current thread).
* <p>
* If this thread is different from the current thread (that is, the current
* thread is trying to stop a thread other than itself), the
* security manager's <code>checkPermission</code> method (with a
* <code>RuntimePermission("stopThread")</code> argument) is called in
* addition.
* Again, this may result in throwing a
* <code>SecurityException</code> (in the current thread).
* <p>
* The thread represented by this thread is forced to stop whatever
* it is doing abnormally and to throw a newly created
* <code>ThreadDeath</code> object as an exception.
* <p>
* It is permitted to stop a thread that has not yet been started.
* If the thread is eventually started, it immediately terminates.
* <p>
* An application should not normally try to catch
* <code>ThreadDeath</code> unless it must do some extraordinary
* cleanup operation (note that the throwing of
* <code>ThreadDeath</code> causes <code>finally</code> clauses of
* <code>try</code> statements to be executed before the thread
* officially dies). If a <code>catch</code> clause catches a
* <code>ThreadDeath</code> object, it is important to rethrow the
* object so that the thread actually dies.
* <p>
* The top-level error handler that reacts to otherwise uncaught
* exceptions does not print out a message or otherwise notify the
* application if the uncaught exception is an instance of
* <code>ThreadDeath</code>.
*
* @exception SecurityException if the current thread cannot
* modify this thread.
* @see #interrupt()
* @see #checkAccess()
* @see #run()
* @see #start()
* @see ThreadDeath
* @see ThreadGroup#uncaughtException(Thread,Throwable)
* @see SecurityManager#checkAccess(Thread)
* @see SecurityManager#checkPermission
* @deprecated This method is inherently unsafe. Stopping a thread with
* Thread.stop causes it to unlock all of the monitors that it
* has locked (as a natural consequence of the unchecked
* <code>ThreadDeath</code> exception propagating up the stack). If
* any of the objects previously protected by these monitors were in
* an inconsistent state, the damaged objects become visible to
* other threads, potentially resulting in arbitrary behavior. Many
* uses of <code>stop</code> should be replaced by code that simply
* modifies some variable to indicate that the target thread should
* stop running. The target thread should check this variable
* regularly, and return from its run method in an orderly fashion
* if the variable indicates that it is to stop running. If the
* target thread waits for long periods (on a condition variable,
* for example), the <code>interrupt</code> method should be used to
* interrupt the wait.
* For more information, see
* <a href="{@docRoot}/../technotes/guides/concurrency/threadPrimitiveDeprecation.html">Why
* are Thread.stop, Thread.suspend and Thread.resume Deprecated?</a>.
*/
@Deprecated
public final void stop() {
stop(new ThreadDeath());
}

上面注释,第9行到第16行表明,stop()方法可以停止“其他线程”。执行thread.stop()方法这条语句的线程称为当前线程,而“其他线程”则是 调用thread.stop()方法的对象thread所代表的线程。 第21行至23行表明,可以停止一个尚未started(启动)的线程。它的效果是:当该线程启动后,就立马结束了。

第48行以后的注释,则深刻表明了为什么stop()方法被弃用!为什么它是不安全的。比如说,threadA线程拥有了监视器,这些监视器负责保护某些临界资源,比如说银行的转账的金额。当正在转账过程中,main线程调用 threadA.stop()方法。结果导致监视器被释放,其保护的资源(转账金额)很可能出现不一致性。比如,A账户减少了100,而B账户却没有增加100

面试题库Android

发表于 2018-10-23 | 分类于 tips

1. View 的绘制原理

https://www.cnblogs.com/jycboy/p/6219915.html
https://www.jianshu.com/p/5a71014e7b1b
http://blog.csdn.net/fwt336/article/details/52979876

2. Android 进程间通信方式

http://blog.csdn.net/fiendvip/article/details/50954393

3. Android Binder 机制

http://blog.csdn.net/qq_23191031/article/details/60145022

4. Activity 加载过程

http://blog.csdn.net/yyh352091626/article/details/51086117

5. Activity 启动模式

https://mp.weixin.qq.com/s/GAFRItGiKnrAN76erxpnLw

6. MVP 和 MVC 和MVVM 有什么区别

http://blog.csdn.net/greathfs/article/details/52017155

7. Android 性能优化

https://mp.weixin.qq.com/s/ax6O76RF4VpZ66keEgPABw

8. Service 概况

http://blog.csdn.net/lqb3732842/article/details/54892813

9. Android 最新官方框架

https://mp.weixin.qq.com/s/qN3JvC_BBuPIHU4mNc9UqQ
https://mp.weixin.qq.com/s/4wAcKoHFv4iQH84w2sccew

10. Android 类加载机制

https://www.jianshu.com/p/3afa47e9112e
https://www.jianshu.com/p/a620e368389a

11. Android 消息处理机制(Looper、Handler、MessageQueue,Message)

https://www.jianshu.com/p/02962454adf7
http://blog.csdn.net/qian520ao/article/details/78262289?locationNum=2&fps=1#4-handler-是如何能够线程切换
  1. 说下什么是Android消息处理机制?
  2. Android消息处理机制的工作原理
  3. Looper、Handler、MessageQueue,Message作用和存在的意义?
  4. Looper 死循环为什么不会导致应用卡死,会消耗大量资源吗?
  5. 主线程的消息循环机制是什么(死循环如何处理其它事务)?
  6. ActivityThread 的动力是什么?(ActivityThread执行Looper的线程是什么)
  7. Handler 是如何能够线程切换,发送Message的?(线程间通讯)
  8. 子线程有哪些更新UI的方法。
  9. 子线程中Toast,showDialog,的方法。(和子线程不能更新UI有关吗)
  10. 如何处理Handler 使用不当导致的内存泄露?

12. Android 事件分发机制

https://www.jianshu.com/p/e99b5e8bd67b

13. Retrofit 分析

https://www.jianshu.com/p/45cb536be2f4

14. Android 动画

https://www.jianshu.com/p/420629118c10

15. Rxjava 使用介绍

https://mp.weixin.qq.com/s/2vDZ7h6SL-LR7n3FR6OMrw

16. View 和 SurfaceView 的区别

http://blog.csdn.net/shareus/article/details/51538507

17. LruCache 原理解析

https://www.jianshu.com/p/b49a111147ee

18. 多线程断点续传

http://blog.csdn.net/tianzhaoai/article/details/56673071
https://www.jianshu.com/p/1bb084d282be

19. Android APP 冷启动时间统计及优化

https://www.jianshu.com/p/c967653a9468
http://blog.csdn.net/code_dream_wq/article/details/75012353
http://blog.csdn.net/zhangyongfeiyong/article/details/61925399

20. 热修复原理

https://mp.weixin.qq.com/s/1SYmgZfVKczAGvXcydn0bA

21. 插件化原理

类的加载
    1)替换App的ClassLoader
    2)修改PathList(例如在PathList中加入我们需要加载的Dex的对应项)
资源的加载:安卓资源由AssetManager加载,应用启动时,系统会为其创建一个AssetManager实例,并由addAssetPath方 法添加资源搜索路径
    1. 插件独立使用一个资源管理器
    2. 添加所有资源路径到宿主,为避免资源冲突,需要对资源ID进行分段,不同的插件使用不同分段的资源ID
组件的动态注册

Android架构师

  1. 整体性能如何探测,有哪些方面,什么指标,怎么保证更流畅
  2. 谈谈架构。大项目,逻辑多怎么办,如何应对多App和多终端
  3. android的发展大事件和主要技术发展
  4. avtivity(service)启动流程简述
  5. 动态化的几种方案
  6. 热修复的原理
  7. 网络这块怎么优化
  8. 数据库性能怎么保证
  9. 线程安全怎么保证,异步并发这块你怎么做的

其实不一定面试时才了解这些,并且了解绝对不是重点,而是实践,绝知此事要躬行是真理,这样的问题也似乎没有“最佳答案”,但是可以发表一下自己观点和实践结论。

有些做法或观点一下子想不起来,需要具体做的时候再google一下,或者跟朋友沟通拓展一下,所以先做个简单的回答,请大家补充。

1. App性能如何探测,有哪些方面,什么指标,怎么保证更流畅?

性能可以根据帧率、内存、CPU、GPU等指标的数据和表现辅助判断,可以从/proc文件夹下读取文件获取cpu、内存等信息,也可以用dumpsys命令获取帧率等信息,也可以通过android API获取相关信息。

还有很多性能相关的分析工具很重要,辅助判断和分析,比如Heap Tool、Memory Monitor、Lint、HierarchyView、WireShark、TraceView等。

保证流畅有很多点可以去研究,比如布局、代码、缓存、网络、数据库、异步并发等方面的优化,涉及很多的知识点,可以google下,先简单说下,有时间再细述。

布局充分利用include、viewstub、merge 等标签,控制层级,避免过度渲染(绘制)。

代码上尽量使用final、局部变量、系统函数如arraycopy等、位移操作是否可以代替乘除、for循环是否可以避免size计算和new对象等等。

缓存方面,线程、位图、视图、网络数据是否可以被缓存,IO用缓冲流。 网络方面,如尽量避免轮询,控制节奏和频率,IP直连,采用SPDY方案(或HTTP2.0)来实现压缩header、多路复用、双向通信等,API数据压缩,多个请求是否可以合并,数据压缩和尝试protocol buffer相关序列化方案。

数据库方面,尝试用SQLiteStatement代替SQLiteDatabase完成操作,索引和事务的充分理解和使用,注意SQL语句语法和拼接,采用部分查询和延迟加载。

异步并发方面,全App只有一个线程池,控制核心并发数量,控制超载时排队数量和策略,合理调度任务,优化业务逻辑。

最后关于帧率,你看到的视觉卡顿,直接原因基本是“掉帧”。关于帧率,尽量保证主线程里做的事情,不会超过16毫秒(其实这挺难的),16毫秒大法好,具体可以去理解下CPU、GPU、屏幕三者如何配合完成渲染的,推荐老罗的博客。

2. 谈谈架构。大项目,逻辑多怎么办,如何应对多App和多终端

适当参考我在知乎的一个回答 怎样搭高质量的Android项目框架,框架的结构具体描述? ,结合模块化、组件化思想去做,多实践一下mvp、mvvm等策略。

3. android的发展大事件和主要技术发展

额,挺多的,朋友们补充吧。 这个问题蛮好的 Android 开发有哪些新技术出现?

4. avtivity(service)启动流程简述

可以自己阅读源代码,结合罗老师的博客,研究的非常棒:Android应用程序启动过程源代码分析

5. 动态化的几种方案

早期的H5方案,通过js和java互通增强h5的能力。 还有DexClasssLoader结合反射代理的方案。 还有React Native方案,手淘的Weex框架。 还有Lua等脚本实现动态化方案。

6. 热修复的原理

Github上读一下AndFix这个项目的源码,还有xposed、dexposed。

大致原理就是将java方法通过c/c++修改属性变为public native方法,上下文携带到native层,然后根据上下文指向另一个java方法,从而“偷梁换柱”,如果是支持ART的手机,那么策略不一样,将bug method的关键信息(classloader、theadid等)保留,将修复过的方法的各种信息赋给bug method,完成“移花接木”。

另外,其实有挺多的策略改变运行时行为的,比如: Javasisst:字节码修改类库,依赖字节码修改和DX类库,可以完成动态替换和切面编程。 AspectJ:依赖字节码编织器,需要按照其语法来编写,需要一点“编织”时间。 Xposed方案,简直是一个Bug,神器般的存在,没准以后会被Android系统修复呢,不仅可以改变自己的类行为还可以hook系统的方法,root过的机器还可以hook其他App和系统进程。

7. 网络这块怎么优化

尽量避免轮询,控制节奏和频率。

IP直连节省DNS解析时间。

尝试其他数据序列化方案比如protocol buffer等。

采用SPDY方案(或HTTP2.0)来压缩header、多路复用、双向通信等。

服务器做优化,比如分布式、缓存之类的,减少API涉及的业务操作所需要的时间嘛。

API接口数据精简,多个请求是否可以合并,增量请求啊、GIP压缩啊什么的,等等。

8. 数据库性能怎么保证

尝试使用SQLiteStatement取代SQLiteDatabase对象完成操作,避免直接使用SQLiteDatabase提供的update、inset等方法。

索引和事务的充分理解和使用,批量操作使用事务极大提升速度,这个我是做过试验的,效果惊人。

SQL语句拼接和本身的优化,仅查询部分局部数据,使用延迟加载策略。

10万条数据插入比系统SQLiteDatabase操作快一倍,推荐我的LiteOrm数据库框架 https://github.com/litesuits/android-lite-orm

9. 线程安全怎么保证,异步并发这块你怎么做的

理解并使用ReentrantLock,Synchronized保护对象或过程,final来保护不可变对象,无状态和只读对象是安全的,合理使用一些 concurrent容器,比如concurrenthashmap等,重量级耗时任务考虑是否可以释放锁,多线程下实例化或延迟加载需要保护起来,保护多线程下关键数据访问的原子性,等等还有很多的。。。

推荐研究下Doug Lea主写和设计的java concurrent包,理解CountDownLatch、CyclicBarrier、Semaphore、FutureTask等对象。

我使用自己写的SmartExecutor,直接继承ExecutorService,封装了一个公共线程池,全App保证只有一个线程池。源码在这个开源项目里:https://github.com/litesuits/android-lite-http 。

在一个 App 中 SmartExecutor 可以有多个实例,每个实例都有独立核心和等待线程数指标,每个实例都有独立的调度和满载处理策略,但它们 共享一个线程池。这种机制既满足不同木块对线程控制和任务调度的独立需求,又共享一个池资源。独立又共享,最大程度上节省资源,提升性能。

控制核心并发数,尽量和CPU核数保持一致(或者多两个)我认为吞吐量是最佳的,线程过多则调度线程消耗CPU和时间,过少则不能充分利用多核的能力。

控制排队策略和排队数量,是否考虑新任务先处理,过度超载丢掉最老的任务。

还有就是业务上,合理调度任务,优化业务逻辑,不要胡搞乱搞,不作恶。

面试题库java

发表于 2018-10-23 | 分类于 tips
  1. 数组实现队列 http://blog.csdn.net/qq_25722767/article/details/52234509

  2. GC 的流程
    https://www.cnblogs.com/little-YTMM/p/5613642.html https://www.cnblogs.com/zhguang/p/3257367.html

  3. Java 软引用和弱引用的区别
    强引用(StrongReference):只要某个对象有强引用与之关联,JVM必定不会回收这个对象,即使在内存不足的情况下,JVM宁愿抛出OutOfMemory错误也不会回收这种对象 软引用(SoftReference):只有在内存不足的时候JVM才会回收该对象 弱引用(WeakReference):当JVM进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象 虚引用(PhantomReference):垃圾回收时回收,虚引用并不会决定对象的生命周期,无法通过引用取到对象值

  4. Java 中 this 编译时的原理 this调用本类中的属性,也就是类中的成员变量; this调用本类中的其他方法; this调用本类中的其他构造方法,调用时要放在构造方法的首行。

  5. final 变量用反射修改 public static void modify(Object object, String fieldName, Object newFieldValue) throws Exception {

    Field field = object.getClass().getDeclaredField(fieldName);
    
    Field modifiersField = Field.class.getDeclaredField("modifiers");
    modifiersField.setAccessible(true); //Field 的 modifiers 是私有的
    modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
    
    if(!field.isAccessible()) {
    field.setAccessible(true);
    }
    
    field.set(object, newFieldValue);

    }

    class Person {

    public final String firstName = "Mike";
    public final String lastName = new String("Jordan"); //可被有效修改
    public final float age = 50.5f;
    public final Float height = 1.99f; //可被有效修改
    public final Address address = new Address("aaa", "bbb"); //可被有效修改
    
    public final String city; //可被有效修改
    
    public Person(String city) {
    this.city = city;
    }

    }

  6. HashMap的内部结构,给定一个key,如何找到对应的value
    https://www.cnblogs.com/shipengzhi/articles/2087505.html

  7. volatile https://www.cnblogs.com/tangyanbo/p/6538488.html

  8. Java线程池有什么作用 在程序启动的时候就创建若干线程来响应处理,它们被称为线程池,里面的线程叫工作线程 第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。 第二:提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。 第三:提高线程的可管理性。 常用线程池:ExecutorService 是主要的实现类,其中常用的有 : Executors.newSingleThreadPool() 创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。 Executors.newFixedThreadPool() 创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。 Executors.newcachedTheadPool() 创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。 Executors.newScheduledThreadPool() 创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。 https://www.jianshu.com/p/ade771d2c9c0 https://www.cnblogs.com/ruiati/p/6134131.html

  9. Java 动态代理 https://www.cnblogs.com/xiaoluo501395377/p/3383130.html

  10. 求 n 的 m 次幂 http://blog.csdn.net/derrantcm/article/details/47098373

  11. java 集合
    https://www.cnblogs.com/lwlxqlccc/p/6121307.html

  12. String 的 spit 实现 http://fangguanhong.iteye.com/blog/2069993

  13. Http和Https的区别 https://www.jianshu.com/p/37654eb66b58

  14. java线程池—ThreadPoolExecutor https://www.jianshu.com/p/ade771d2c9c0

  15. 冒泡排序 http://blog.csdn.net/u010853261/article/details/54891710

  16. 排序算法 http://blog.csdn.net/pzhtpf/article/details/7560294

  17. 抽象类和接口的区别 接口是对动作的抽象,而抽象类是对根源的抽象。 接口和抽象类都是继承树的上层,他们的共同点如下:1) 都是上层的抽象层。2) 都不能被实例化3) 都能包含抽象的方法,这些抽象的方法用于描述类具备的功能,但是不比提供具体的实现。他们的区别如下:1) 在抽象类中可以写非抽象的方法,从而避免在子类中重复书写他们,这样可以提高代码的复用性,这是抽象类的优势;接口中只能有抽象的方法。2) 一个类只能继承一个直接父类,这个父类可以是具体的类也可是抽象类;但是一个类可以实现多个接口。Java语言中类的继承是单继承原因是:当子类重写父类方法的时候,或者隐藏父类的成员变量以及静态方法的时候,JVM使用不同的绑定规则。如果一个类有多个直接的父类,那么会使绑定规则变得更复杂。为了简化软件的体系结构和绑定机制,java语言禁止多继承。接口可以多继承,是因为接口中只有抽象方法,没有静态方法和非常量的属性,只有接口的实现类才会重写接口中方法。因此一个类有多个接口也不会增加JVM的绑定机制和复杂度。

  18. java基本数据类型 8位:Byte(字节型)
    16位:short(短整型)、char(字符型)
    32位:int(整型)、float(单精度型/浮点型)
    64位:long(长整型)、double(双精度型)
    最后一个:boolean(布尔类型)

  19. Java堆的实现、排序 http://blog.csdn.net/idealemail/article/details/51382837

  20. 数组中重复数字 http://blog.csdn.net/zjkc050818/article/details/72800175

  21. 字符串中判断另一个字符串是否存在

    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
    static int indexOf(String source,
    String target,
    int fromIndex) {
    if (fromIndex >= source.count) {
    return (target.count == 0 ? source.count : -1);
    }
    if (fromIndex < 0) {
    fromIndex = 0;
    }
    if (target.count == 0) {
    return fromIndex;
    }

    char first = target.charAt(0);
    int max = (source.count - target.count);

    for (int i = fromIndex; i <= max; i++) {
    /* Look for first character. */
    if (source.charAt(i)!= first) {
    while (++i <= max && source.charAt(i) != first);
    }

    /* Found first character, now look at the rest of v2 */
    if (i <= max) {
    int j = i + 1;
    int end = j + target.count - 1;
    for (int k = 1; j < end && source.charAt(j)
    == target.charAt(k); j++, k++);

    if (j == end) {
    /* Found whole string. */
    return i;
    }
    }
    }
    return -1;
    }
  22. ThreadLocal 使用及原理
    https://www.cnblogs.com/coshaho/p/5127135.html

  23. Java 泛型 https://www.cnblogs.com/demingblog/p/5495610.html

  24. Java 内部类 http://blog.csdn.net/roamer314/article/details/48598633

  25. Java ConcurrentHashMap的原理
    https://www.cnblogs.com/chengxiao/p/6842045.html

  26. 线程的几种状态
    http://blog.csdn.net/sinat_36042530/article/details/52565296 https://www.cnblogs.com/wxd0108/p/5479442.html

  27. Java 双亲委派机制
    https://www.cnblogs.com/wxd0108/p/6681618.html

  28. Java 运算符 https://jingyan.baidu.com/article/1612d5008ff5b7e20f1eee4c.html

面试题库

发表于 2018-09-11 | 分类于 tips
1. 跨进程的远程方法的是执行在哪个进程的哪个线程?

(都是binder线程池)

2. SharedPreference实现原理,跨进程使用问题,为什么引起这个问题
3. Parcelable和Serializable区别,以及他们实现的原理
4. Binder相较于Socket实现进程通信的有点(拷贝少,安全)
5. A Activity的onCreate中启动一个新的activiy, A Activity的OnResume ,onPause与新Activity生命周期方法的执行顺序

A onCreate, onStart onResume ,onPause .. - > B onCreate, onStart, onResume

MainActivity中点击按钮启动两个Activity:

1
2
startActivity(new Intent(MainActivity.this, ActivityA.class));
startActivity(new Intent(MainActivity.this, ActivityB.class));

生命周期执行情况为:

1
2
3
4
5
6
7
8
9
2019-08-05 11:48:41.132 3557-3557/com.osn.demo I/MainActivityDDDD: onStart....
2019-08-05 11:48:41.133 3557-3557/com.osn.demo I/MainActivityDDDD: onResume....


2019-08-05 11:48:46.257 3557-3557/com.osn.demo I/MainActivityDDDD: onPause....
2019-08-05 11:48:46.271 3557-3557/com.osn.demo I/ActivityBBBB: onCreate...
2019-08-05 11:48:46.280 3557-3557/com.osn.demo I/ActivityBBBB: onStart....
2019-08-05 11:48:46.282 3557-3557/com.osn.demo I/ActivityBBBB: onResume....
2019-08-05 11:48:46.703 3557-3557/com.osn.demo I/MainActivityDDDD: onStop....

返回键关闭ActivityB,执行的生命周期方法:

1
2
3
4
5
6
2019-08-05 11:48:49.760 3557-3557/com.osn.demo I/ActivityBBBB: onPause....
2019-08-05 11:48:49.808 3557-3557/com.osn.demo I/ActivityAAAA: onCreate...
2019-08-05 11:48:49.841 3557-3557/com.osn.demo I/ActivityAAAA: onStart....
2019-08-05 11:48:49.845 3557-3557/com.osn.demo I/ActivityAAAA: onResume....
2019-08-05 11:48:50.227 3557-3557/com.osn.demo I/ActivityBBBB: onStop....
2019-08-05 11:48:50.228 3557-3557/com.osn.demo I/ActivityBBBB: onDestroy....

返回键关闭ActivityA:

1
2
3
4
5
6
2019-08-05 11:48:54.615 3557-3557/com.osn.demo I/ActivityAAAA: onPause....
2019-08-05 11:48:54.655 3557-3557/com.osn.demo I/MainActivityDDDD: onRestart....
2019-08-05 11:48:54.657 3557-3557/com.osn.demo I/MainActivityDDDD: onStart....
2019-08-05 11:48:54.658 3557-3557/com.osn.demo I/MainActivityDDDD: onResume....
2019-08-05 11:48:54.990 3557-3557/com.osn.demo I/ActivityAAAA: onStop....
2019-08-05 11:48:54.990 3557-3557/com.osn.demo I/ActivityAAAA: onDestroy....

结论: 顺序依次start两个Activity,那么会优先最后一个调用的先创建.

6. 同一台主机,udp和tcp可否使用同一个端口

(可以)

7. Android一个最简单的demo程序启动后包含哪些线程

mac环境配置之小工具

发表于 2018-09-11 | 分类于 env

NTFS读写配置

1
2
3
diskutil list查看所有分区的卷标

sudo nano /etc/fstab

LABEL=新加卷 none ntfs rw,auto,nobrowse Ctrl+x,再输入y,回车

重启电脑或者是推出硬盘重新插入

如果硬盘没有name的话可以使用UUID

1
UUID=029F9ED4-7157-49E2-9B55-852E490C8BD9 none ntfs rw,auto,nobrowse

通过diskutil info /Volumes/Untitled查看uuid

打开Finder, command+shift+G,输入框中输入/Volumes,回车

VS Code

JSON Tools

Ctrl(Cmd)+Alt+M for JSON pretty.

Alt+M for JSON minify.

VS Code mac下配置终端环境为zsh

1
"terminal.integrated.shell.osx":"/bin/zsh"

配置终端字体大小: 设置->终端->Font Size

打开文件覆盖之前tab问题

这是因为你单击文件名的缘故,这个是“预览模式”,所以再单击其他文件时,会覆盖当前打开的文件。 预览模式是现在各类编辑器的默认功能,可以关掉, 给配置settings.json里加一条:

1
"workbench.editor.enablePreview": false,

ssh-key

更新代码、SSH服务器总是提示:

1
Enter passphrase for .../id_rsa:

ssh-add命令是把专用密钥添加到ssh-agent的高速缓存中。该命令位置在/usr/bin/ssh-add ssh-add 可以使用的参数: -D:删除ssh-agent中的所有密钥. -d:从ssh-agent中的删除密钥 -e pkcs11:删除PKCS#11共享库pkcs1提供的钥匙。 -s pkcs11:添加PKCS#11共享库pkcs1提供的钥匙。 -L:显示ssh-agent中的公钥 -l:显示ssh-agent中的密钥 -t life:对加载的密钥设置超时时间,超时ssh-agent将自动卸载密钥 -X:对ssh-agent进行解锁 -x:对ssh-agent进行加锁来

执行一下命令就好了,就不用再反复输入密码了

1
ssh-add ~/.ssh/id_rsa

Host key verification failed.

IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY! Someone could be eavesdropping on you right now (man-in-the-middle attack)! It is also possible that a host key has just been changed. The fingerprint for the ECDSA key sent by the remote host is 85:82:b1:58:20:21:a5:da:be:24:e8:14:9a:12:b2:d2. Please contact your system administrator. Add correct host key in /root/.ssh/known_hosts to get rid of this message. Offending ECDSA key in /root/.ssh/known_hosts:5 ECDSA host key for 172.xxx.xxx.xxx has changed and you have requested strict checking. Host key verification failed.

ssh会把你每个你访问过计算机的公钥(public key)都记录在~/.ssh/known_hosts。当下次访问相同计算机时,OpenSSH会核对公钥。如果公钥不同,OpenSSH会发出警告,避免你受到DNS Hijack之类的攻击.SSH对主机的public_key的检查等级是根据StrictHostKeyChecking变量来配置的。默认情况下,StrictHostKeyChecking=ask。

简单说下它的三种配置值:

  • StrictHostKeyChecking=no 最不安全的级别,当然也没有那么多烦人的提示了,相对安全的内网时建议使用。如果连接server的key在本地不存在,那么就自动添加到文件中(默认是known_hosts),并且给出一个警告。

  • StrictHostKeyChecking=ask 默认的级别,就是出现刚才的提示了。如果连接和key不匹配,给出提示,并拒绝登录。

  • StrictHostKeyChecking=yes 最安全的级别,如果连接与key不匹配,就拒绝连接,不会提示详细信息。

解决方案:

  1. 除~/.ssh/known_hosts文件中对应ip的相关rsa信息, 输入命令vi ~/.ssh/known_hosts,编辑文件删除对应ip的相关rsa信息,即可。
  2. 使用 ssh-keygen -R hostname 命令, 目的是清除你当前机器里关于你的远程服务器的缓存和公钥信息,注意是大写的字母“R”。

挂载移动硬盘问题

sudo diskutil mount /dev/disk2 出现 timed out waiting to mount 接着输入

1
2
diskutil unmountDisk /dev/disk2
diskutil eject /dev/disk2

卸载是successful了但是eject 还是timed out

I was having the exact same issue where unmountDisk would work fine but eject would result in the “timed out” message. I finally found a suggestion to see if fsck was holding the disk hostage. A quick ps aux | grep fsck revealed that indeed it was hijacking the disk/volume as soon as it was plugged in. sudo pkill -f fsck (or just kill with the PID if you prefer) immediately allowed the volume to be mounted.

然后 输入 ps aux | grep fsck确实fsck在搞鬼 然后杀掉所有fsck的进程 sudo pkill -f fsck 拔掉硬盘再插进去

参考:https://apple.stackexchange.com/questions/235309/external-drive-does-not-mount-after-plug-off-without-eject

清除AndroidStudio配置

  1. 执行这些命令在命令行

    1
    2
    3
    4
    5
    6
    rm -Rf /Applications/Android\ Studio.app
    rm -Rf ~/Library/Preferences/AndroidStudio*
    rm ~/Library/Preferences/com.google.android.studio.plist
    rm -Rf ~/Library/Application\ Support/AndroidStudio*
    rm -Rf ~/Library/Logs/AndroidStudio*
    rm -Rf ~/Library/Caches/AndroidStudio*
  2. 如果你想删除全部项目

    1
    rm -Rf ~/AndroidStudioProjects
  3. 删除gradle关联文件 (caches & wrapper)

    1
    rm -Rf ~/.gradle
  4. 删除模拟器

    1
    rm -Rf ~/.android
  5. 删除android 工具

    1
    rm -Rf ~/Library/Android*

vpn(Virtual Private Network)虚拟专用网络介绍

发表于 2018-09-11 | 分类于 env

vpn(Virtual Private Network)虚拟专用网络

VPN是企业通信的常用技术,可以对网络加密,使得其安全性能提升。常用的VPN协议有PPTP、L2TP、OpenVPN

一、PPTP、L2TP、OpenVPN三种隧道协议的概念

1、PPTP(Point to Point Tunneling Protocol,点对点隧道协议)默认端口号:1723

  PPTP,即PPTF协议。该协议是在PPP协议的基础上开发的一种新的增强型安全协议,支持多协议虚拟专用网(VPN),可以通过密码身份验证协议(PAP)、可扩展身份验证协议(EAP)等方法增强安全性。可以使远程用户通过拨入ISP、通过直接连接Internet或其他网络安全地访问企业网。

  点对点隧道协议(PPTP)是一种支持多协议虚拟专用网络的网络技术,它工作在第二层。通过该协议,远程用户能够通过 Microsoft Windows NT工作站、Windows xp 、Windows 2000 和windows2003、windows7操作系统以及其它装有点对点协议的系统安全访问公司网络,并能拨号连入本地ISP,通过Internet 安全链接到公司网络。

  PPTP协议是点对点隧道协议,其将控制包与数据包分开,控制包采用TCP控制。PPTP使用TCP协议,适合在没有防火墙限制的网络中使用。

2、L2TP(Layer 2 Tunneling Protocol,第二层隧道协议)

  L2TP是一种工业标准的Internet隧道协议,功能大致和PPTP协议类似,比如同样可以对网络数据流进行加密。不过也有不同之处,比如PPTP要求网络为IP网络,L2TP要求面向数据包的点对点连接;PPTP使用单一隧道,L2TP使用多隧道;L2TP提供包头压缩、隧道验证,而PPTP不支持。

  L2TP是一个数据链路层协议,基于UDP。其报文分为数据消息和控制消息两类。数据消息用投递 PPP 帧,该帧作为L2TP报文的数据区。L2TP不保证数据消息的可靠投递,若数据报文丢失,不予重传,不支持对数据消息的流量控制和拥塞控制。控制消息用以建立、维护和终止控制连接及会话,L2TP确保其可靠投递,并支持对控制消息的流量控制和拥塞控制。

  L2TP是国际标准隧道协议,它结合了PPTP协议以及第二层转发L2F协议的优点,能以隧道方式使PPP包通过各种网络协议,包括ATM、SONET和帧中继。但是L2TP没有任何加密措施,更多是和IPSec协议结合使用,提供隧道验证。

  L2TP使用UDP协议,一般可以穿透防火墙,适合在有防火墙限制、局域网用户,如公司、网吧、学校等场合使用。

  PPTP和L2TP二个连接类型在性能上差别不大,如果使用PPTP不正常,那就更换为L2TP。

3、OpenVPN

  OpenVpn的技术核心是虚拟网卡,其次是SSL协议实现。

  虚拟网卡是使用网络底层编程技术实现的一个驱动软件,安装后在主机上多出现一个网卡,可以像其它网卡一样进行配置。服务程序可以在应用层打开虚拟网卡,如果应用软件(如IE)向虚拟网卡发送数据,则服务程序可以读取到该数据,如果服务程序写合适的数据到虚拟网卡,应用软件也可以接收得到。虚拟网卡在很多的操作系统下都有相应的实现,这也是OpenVpn能够跨平台一个很重要的理由。

  OpenVPN使用OpenSSL库加密数据与控制信息:它使用了OpenSSL的加密以及验证功能,意味着,它能够使用任何OpenSSL支持的算法。它提供了可选的数据包HMAC功能以提高连接的安全性。此外,OpenSSL的硬件加速也能提高它的性能。

  OpenVPN所有的通信都基于一个单一的IP端口,默认且推荐使用UDP协议通讯,同时TCP也被支持。

  在选择协议时候,需要注意2个加密隧道之间的网络状况,如有高延迟或者丢包较多的情况下,请选择TCP协议作为底层协议,UDP协议由于存在无连接和重传机制,导致要隧道上层的协议进行重传,效率非常低下。

  OpenVPN是一个基于SSL加密的纯应用层VPN协议,是SSL VPN的一种,支持UDP与TCP两种方式(说明:UDP和TCP是2种通讯协议,这里通常UDP的效率会比较高,速度也相对较快。所以尽量使用UDP连接方式,实在UDP没法使用的时候,再使用TCP连接方式)。

  由于其运行在纯应用层,避免了PPTP和L2TP在某些NAT设备后面不被支持的情况,并且可以绕过一些网络的封锁(通俗点讲,基本上能上网的地方就能用OpenVPN)。

  OpenVPN客户端软件可以很方便地配合路由表,实现不同线路(如国内和国外)的路由选择,实现一部分IP走VPN,另一部分IP走原网络。

二、PPTP、L2TP、OpenVPN三种隧道协议的优缺点对比

  • 易用性: PPTP > L2TP > OpenVPN
  • 速度: PPTP > OpenVPN UDP > L2TP > OpenVPN TCP
  • 安全性: OpenVPN > L2TP > PPTP
  • 稳定性: OpenVPN > L2TP > PPTP
  • 网络适用性:OpenVPN > PPTP > L2TP

三、VPN协议的选择

电脑上优先使用PPTP,无法使用可以尝试L2TP,对安全性要求高的优先使用OpenVPN。手持设备推荐使用L2TP。

  • PPTP: 最常用,设置最简单,大多数设备都支持;
  • L2TP: 支持PPTP的设备基本都支持此种方式,设置略复杂,需要选择L2TP/IPSec PSK方式,且设置预共享密钥PSK;
  • OpenVPN:最稳定,适用于各种网络环境,但需要安装第三方软件和配置文件,较复杂。

  但如今有了更加优秀的选择 IKEv2,更加轻便、快速,也是windows phone中唯一支持的VPN协议(好像最近支持了L2TP)

IKEV2介绍

是Internet Key Exchange version 2的缩写,它是用来完成认证,协商加密,哈希算法,以及对应秘钥,定义什么流量需要加密完成IKE SA,IP sec SA的建立.

1…789…19
轻口味

轻口味

190 日志
27 分类
63 标签
RSS
GitHub 微博 豆瓣 知乎
友情链接
  • SRS
© 2015 - 2019 轻口味
京ICP备17018543号
本站访客数 人次 本站总访问量 次