1.线程间通信
1.1.thread.interrupt()
我们理解的可能就是线程和线程之间对话。
最简单的线程间通信就是一个线程启动另一个线程:
问题1:
如何做到一个线程终结另一个线程呢?
解答1:
我们最早使用的stop()方法,直接结束线程。但是方法过时了,不建议使用
问题2:
这么好用为什么弃用了呢?
解答2:
因为他会立刻终止线程,它的结果是不可预期的。比如我要改修改5条数据,改了3条就给我切断了
public class ThreadInteractionDemo implements TestDemo{
@Override
public void runTest() {
Thread thread = new Thread(){
@Override
public void run() {
for (int i = 0; i < 1_000_000; i++) {
System.out.println("number:"+i);
}
}
};
thread.start(); //启动线程
thread.stop(); //把这个线程立即掐断,立刻就结束了。结果什么都不打印
}
}
问题3:
那我们有什么好的方法呢?
解答3:
我们可以使用thread.interrupt();,进行处理,他的意思是中断,但是他只是一个标记,只是将当前线程标记为中断状态,或者说把他的中断状态设置为true,但是他是需要目标(也就是设置interrupt()的线程)线程进行配合,相当于我发了一个通知我需要你结束,目标线程接到通知后进行终止线程。
public class ThreadInteractionDemo implements TestDemo{
@Override
public void runTest() {
Thread thread = new Thread(){
@Override
public void run() {
for (int i = 0; i < 1_000_000; i++) {
//需要配合,进行判断,
//Thread.interrupted()和isInterrupted()都可以
if (Thread.interrupted()){
//线程马上结束了,进行收尾工作
return;
}
System.out.println("number:"+i);
}
}
};
thread.start();
/**
* thread.interrupt();
* 他只是一个标记。其实只是将这个线程标记为中断状态。或者说把它的中断状态设置为true,他需要你的目标线程配合这件事情,例如给个通知,我需要你结束了,是一个温和版本的,暴力版本的stop被弃用了。
* 1.他不是立刻的
* 2.他不是强制的
* 3.你想终止就终止,我不管你,我只是一个通知。需要自己来支持的,通过isInterrupt()或者Thread.interrupted()来判断,Thread.interrupted()调用后会重置状态,我们需要自己做收尾工作,知道线程马上结束了。
*/
thread.interrupt();
}
}
Thread.interrupted()和isInterrupted()的区别
1.Thread.interrupted()调用后会将interrupt(中断状态设置为false),注意当执行InterruptedException e的时候也会将状态重置。
2.isInterrupted()始终都是对应的值,不会重置状态
public class ThreadInteractionDemo implements TestDemo{
@Override
public void runTest() {
Thread thread = new Thread(){
@Override
public void run() {
for (int i = 0; i < 1_000_000; i++) {
if (Thread.interrupted()){
return;
}
try {
//打断这里睡1000毫秒(醒醒别睡了,我要中断你了。此时就会抛出异常)
Thread.sleep(1000);
} catch (InterruptedException e) {
//当抛出异常时,会将interrupt的状态重置,重新置为false。
e.printStackTrace();
}
System.out.println("number:"+i);
}
}
};
thread.start();
try {
//睡100毫秒
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread.interrupt();
}
}
打印结果:
结论:
通过打印结果发现,抛出异常后,还是会执行打印,每隔1秒打印一次。为什么我们都中断了还会打印呢,就是因为InterruptedException抛出异常后,会将打断(interrupt)置为false,之后for循环里Thread.interrupted()始终都是false,就像是没有人调用过一样。所以就会每隔一秒执行一次。我们只需要在抛出异常地方也做收尾工作就可以了。
public class ThreadInteractionDemo implements TestDemo{
@Override
public void runTest() {
Thread thread = new Thread(){
@Override
public void run() {
for (int i = 0; i < 1_000_000; i++) {
if (Thread.interrupted()){
return;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
//当抛出异常时,会将interrupt的状态重置,重新置为false。
e.printStackTrace();
//收尾工作
return;
}
System.out.println("number:"+i);
}
}
};
thread.start();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread.interrupt();
}
}
抛出异常地方做了收尾工作后打印结果:
结论:
通过打印结果发现,抛出异常后,就不会执行打印,我们只需要在抛出异常地方也做收尾工作就可以了。
Thread.sleep和SystemClock.sleep的区别
如果只是想睡眠:我们只需要使用SystemClock.sleep(1000);
如果想打断线程:我们可以使用Thread.sleep(1000),这个需要和thread.interrupt配合。
1.2.wait()和notify()/notifyAll()
在我们日常使用中可能很少使用到。调用wait,他会先释放调自己的moniter(监视器也可以认为是锁 synchronized),之后进入等待区。等待其他操作完成后需要唤醒它
例如:
public class WaitDemo implements TestDemo {
private String sharedString;
//初始化
private synchronized void initString(){
sharedString = "changhui";
}
//打印字符串
private synchronized void printString(){
System.out.println("String :"+sharedString);
}
/**
* 开启两个线程,一个睡500毫秒,一个睡1000毫秒
*/
@Override
public void runTest() {
Thread thread = new Thread(){
@Override
public void run() {
try {
Thread.sleep(500);
printString(); //先去打印
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
thread.start();
Thread thread1 = new Thread(){
@Override
public void run() {
try {
Thread.sleep(1000);
initString(); //去初始化
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
thread1.start();
}
}
打印结果:
通过打印结果可以看到是null,因为我们还没进行初始化。很正常
问题:
那我们在开发中不知道那个先执行,哪个后执行,我们怎么办呢?
解答:
1.我们可以把打印的线程睡眠时间改长一点。(这个是理想状态)
2.我们可以在打印的方法中进行判断,不等于空的时候在打印。(确定好使吗?下面我们来试试)
//注意我们方法都是加了synchronized关键字的
public class WaitDemo implements TestDemo {
private String sharedString;
//初始化
private synchronized void initString(){
sharedString = "changhui";
}
//打印字符串
private synchronized void printString(){
//进行判断
while (sharedString == null){
}
System.out.println("String :"+sharedString);
}
/**
* 开启两个线程,一个睡500毫秒,一个睡1000毫秒
*/
@Override
public void runTest() {
Thread thread = new Thread(){
@Override
public void run() {
try {
Thread.sleep(500);
printString(); //先去打印
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
thread.start();
Thread thread1 = new Thread(){
@Override
public void run() {
try {
Thread.sleep(1000);
initString(); //去初始化
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
thread1.start();
}
}
打印结果:
通过打印结果可以看到什么都没有打印,线程一直被执行的状态,不会被结束。
问题1:
为什么会一直被阻塞呢?
解答1:
由代码可知,我们看当执行打印方法的时候,因为有synchronized关键字,会拿到一个moniter(监视器),里边是一个死循环,始终不会结束,此时我们调用初始化操作的时候,方法也有synchronized关键字,此时因为打印方法没有释放锁,初始化操作只能进行等待,这样其实就是一个死锁。
问题2:
那我们如何解决呢,我们可以使用 wait() 进行暂时释放。(确定好使吗?下面我们来试试)
//注意我们方法都是加了synchronized关键字的
public class WaitDemo implements TestDemo {
private String sharedString;
//初始化
private synchronized void initString(){
sharedString = "changhui";
}
//打印字符串
private synchronized void printString(){
//进行判断
while (sharedString == null){
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("String :"+sharedString);
}
/**
* 开启两个线程,一个睡500毫秒,一个睡1000毫秒
*/
@Override
public void runTest() {
Thread thread = new Thread(){
@Override
public void run() {
try {
Thread.sleep(500);
printString(); //先去打印
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
thread.start();
Thread thread1 = new Thread(){
@Override
public void run() {
try {
Thread.sleep(1000);
initString(); //去初始化
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
thread1.start();
}
}
打印结果:
通过打印结果可以看到什么都没有打印,线程还是一直被执行的状态,不会被结束。
问题3:
为什么呢,不是加了wait就把锁释放了吗?
解答3:我们看下下面这幅图,就能明白了
通过代码测试:
public class WaitDemo implements TestDemo {
private String sharedString;
//初始化
private synchronized void initString(){
sharedString = "changhui";
notifyAll();
}
//打印字符串
private synchronized void printString(){
while (sharedString == null){
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("String :"+sharedString);
}
/**
* 开启两个线程,同时睡眠一个500,一个1000
*/
@Override
public void runTest() {
Thread thread = new Thread(){
@Override
public void run() {
try {
Thread.sleep(500);
printString();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
thread.start();
Thread thread1 = new Thread(){
@Override
public void run() {
try {
Thread.sleep(1000);
initString();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
thread1.start();
}
}
打印结果:
通过打印结果可以看到打印了changhui,线程也就都结束了。
注意:
wait() 和 notifyAll()或者notify()也是配合使用的。
wait() 和 notifyAll()首先需要是一个共享资源,所以都需要被synchronized关键字给包起来。如果没有使用是没有意义的,因为你等待的就是别人通知你,你要的资源已经准备就绪了。或者说我们共享的资源我已经修改过了,你可以看一看是不是你想要的样子。
1.3.thead.join()和thread.yeild()
thead.join()原理:
相当于让调用join()的线程插入到当前执行该方法的线程前边,当插入的线程完全执行完成后我才继续执行。相当于吧自己wait,通过插入的线程发送通知后,我在执行。
@Override
public void runTest() {
Thread thread = new Thread(){
@Override
public void run() {
try {
Thread.sleep(500);
printString();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
thread.start();
Thread thread1 = new Thread(){
@Override
public void run() {
try {
//相当于让调用join的线程插入到当前执行的线程前边,当插入的线程完全执行完成后我才继续执行。相当于吧自己wait,通过插入的线程通知。
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
Thread.sleep(1000);
initString();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
thread1.start();
}
Thead.yield()原理:
相当于我暂时让出我的时间片,把执行暂时分配给同优先级的其他线程,相当于自己暂时卡住了。等优先级高的线程执行完成后我在执行。
2.Handler消息机制
这篇文章我之前有说过,你们可以看一下。Handler消息机制的原理及源码分析
说一说内存泄漏
1.都说Handler,AsyncTask会导致内存泄漏。如何导致呢。
内存泄漏:如何看内存泄漏呢,通过看GCRoot(garbage collector Root)引用数,没有被 GC Root 直接或间接持有引⽤的对象,会被回收。
GCRoot分类;
1.运行中的Thread,AsyncTask内存泄漏,不是因为他是内部类,就是因为他是一个运行中的线程GCRoot,导致的内存泄漏。(AsyncThask为Activity的内部类时,可能就是AsyncThask运行的过程中,Activity销毁了,导致的内存泄漏。)
2.static静态对象,所有的static不会被回收,他们所引用的也不会被回收
3.AsyncTask,HandlerThread,IntentService,Service,Executors如何选择
1.Executors:能用就用,任务放在后台不考虑拉回来一般就用他就行
2.如果是后台任务推到前台,考虑用AsyncTask或者Handler
3.HandlerThread:就是一个不断在后台执行的单线程。直接使用Executors就行
Executors和Handler区别
Executors已经添加进来的任务,就算没执行也取消不掉。
Handler已经添加进来的任务,就算没执行是可以取消掉的
Service和IntentService不是线程
Service他是后台任务的活动空间,比如你有一个音乐播放器。
IntentService他是执行一遍任务就挂调的Service,他只是额外的有上下文,如果需要上下文的时候,我们可以使用,比如设置一个闹钟等,否则也没必要使用。
网友评论