Android多线程的启停操作以及volatile的使用

在Android系统中,如果用户界面失去响应超过5s之后,系统会提示用户是否需要强制关闭app。因此,当需要在程序中做一些好事操作时(如网络连接,下载文件等),最好另开一个线程处理耗时操作。Android中采用Java的方法建立和使用线程。

线程的开启
1,Runnable加Thread实现
首先创建一个类实现Runnable接口,或者直接创建一个Runnable对象。然后重写Runnable中的run()方法,run()方法中的代码就是线程执行的部分。

Runnable runnable = new Runnable() {
    @Override
    public void run() {
        
    }
};

或者

class MyThread implements Runnable{

    @Override
    public void run() {

    }
}

然后通过一个Thread对象将Runnable对象作为参数传递给他,
调用Thread对象的start方法即可开启线程。

new Thread(null,new MyThread(),"myThread").start();

2,Handler加Runnable方法实现线程开启
Android不允许在子线程中更新用户界面,更新UI界面的操作这能在主线程中进行
因为如果可以在子线程中更新UI组件,就会导致用户见面显示的内容处于不确定的状态,而这种不确定的情况可能会给用户
带来困惑。
但有时我们确实需要将线程中的运行结果呈现到用户界面上,这时就需要一个Handler对象来解决上述问题。

Handler是Android给我们提供用来更新UI的一套机制,是一套消息处理机制,可以通过它来发送消息和处理消息。
每一个线程可以一个MessageQueue,而一个MessageQueue通过一个Looper来获取其中的Message,每一个线程同时只能
处理一个Message。MessageQueue是一个消息队列,用于待处理的消息。looper是一个循环提取MessageQueue中消息的机制,
当MessageQueue中有Message时就读取,没有时就阻塞。而Handler就是往MessageQueue中添加,以及处理消息的对象。
主线程在一开始时就创建好了MessageQueue和Looper。
下面一张图展现了他们几个的关系。

在这里插入图片描述

那么如何通过Handler对象来开启线程呢?就需要用到Handler的post方法
调用Handler的Post()方法或者postDelayed()方法,启动另一个线程。
创建一个工作线程,实现 Runnable 接口,实现 run 方法,处理耗时操作
创建一个 handler,通过 handler.post/postDelay,投递创建的 Runnable,在 run 方法中进行更新 UI 操作。
Handler处理Runnable任务的常用方法
1)post: 立即启动Runnable任务
2)postDelayed:延迟若干时间后启动Runnable任务
3)postAtTime: 在指定事件启动Runnable任务
4)removeCallbacks:移除指定的Runnable任务
post本质上是将一个Runnable对象封装成一二Message然后传递到主线程的MessageQueue中,一代该消息被主线程的Looper取出,那么在其中的关于更新UI界面的操作就会在主线程中运行。
样例代码:

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    handler = new Handler();
    handler.post(new Runnable() {
        @Override
        public void run() {
            //更新UI的操作
        }
    });
    
}

ps:此方法理论上同样可以将一些耗时操作post到主线程中运行,但是不推荐这样做,因为那样和直接在主线程中执行耗时操作没有任何区别。因此通常做法是当要
进行耗时操作时使用第一种方法开启线程,当需要在子线程中更新UI时将需要更新UI的部分post主线程中进行。记得我们老师说过一个准则“主线程不做耗时操作,
子线程不更新UI

线程的关闭
1,interrupt()
之前线程的关闭可以直接通过Thread的stop方法实现,但是该方法存在风险,已经被谷歌弃用。
其实只要将run中的代码执行完线程就会自动关闭,不需要人为的操作
但是有的时候我们在run中写的代码是死循环,根本执行不玩~~比如下面这个例子
开启一个线程,该线程中run是一个死循环不停的做+1,-1的操作,这个时候可以通过Thread的interrupt将线程停下。

public class MainActivity extends AppCompatActivity {
    private Thread thread;
    private Button buttonStart;
    private Button buttonStop;
    int i = 0;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        buttonStart = findViewById(R.id.button);
        buttonStop = findViewById(R.id.button2);

        buttonStart.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                thread = new Thread(runnable);
                thread.start();
                Log.d("myTag", "onClick:线程开启 ");
            }
        });

        buttonStop.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                thread.interrupt();  //将线程的中断标志位置true
                Log.d("myTag", "onClick:线程关闭 ");

            }
        });
    }

    Runnable runnable = new Runnable() {
        @Override
        public void run() {
            while (!thread.isInterrupted()){  //获取当前线程的中断标志位,如果为true则中断,如果为false则继续执行
                i++;
                i--;
            }
        }
    };

}

运行结果
在这里插入图片描述
但是interrupt方法存在局限性,就是不能遇到Thread.sleep()。interrupt方法本质上是将线程的中断标志置为true,
当线程在执行sleep方法时会不停的检测线程的中断标志位,如果为true,会抛出InterruptException。
而Java中凡是抛出InterruptedException的方法,都会在抛异常的时候,将interrupt flag重新置为false,所以原本的
interrupt就没有一点作用了。
将Runnable中的run方法改成

Runnable runnable = new Runnable() {
    @Override
    public void run() {
        while (!thread.isInterrupted()){
            i++;
            i--;
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
                Log.d("myTag", "onClick:线程关闭失败 ");
            }
        }
    }
};

此时的运行结果为
在这里插入图片描述
而此时的线程其实并未关闭,然而开发过程中,死循环套sleep时线程操作最常用的情况。
那么这种情况该如何停止呢?
获取到InterruptException之后将线程的中断标志位重新置为true

Runnable runnable = new Runnable() {
    @Override
    public void run() {
        while (!thread.isInterrupted()){
            i++;
            i--;
            Log.d("myThread","线程正在运行");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    }
};

这样就能保证线程被中断而不会继续执行了。

2,volatile关键字
通过一个volatile修饰的boolean变量作为while循环的条件,当需要将线程关闭时将boolean置为false

public class MainActivity extends AppCompatActivity {
    private Thread thread;
    private Button buttonStart;
    private Button buttonStop;
    int i = 0;
    private volatile boolean flag = true;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        buttonStart = findViewById(R.id.button);
        buttonStop = findViewById(R.id.button2);

        buttonStart.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                flag = true;
                thread = new Thread(runnable);
                thread.start();
                Log.d("myTag", "onClick:线程开启 ");
            }
        });

        buttonStop.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                flag = false;
                Log.d("myTag", "onClick:线程关闭 ");

            }
        });
    }

    Runnable runnable = new Runnable() {
        @Override
        public void run() {
            while (flag){
                i++;
                i--;
                Log.d("myThread","线程正在运行");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
        }
    };

}

如果针对这种方法,似乎使用一个普通的boolean就可以实现进程的关闭。
那么为什么要使用一个volaltile修饰呢?
这就要涉及到线程的运行机制了!

在多线程的应用中多个线程对 非volatile 变量进行操作,线程在对它们进行操作的时候为了提高性能会将变量从主存复制到 cpu 缓存中。如果你的电脑包含的 cpu 不止一个, 那么每个线程可能会运行于不同的 cpu 上。这意味着,不同线程会将变量复制到不同 cpu 的缓存里。

非volatile 变量不能保证 Java 虚拟机(JVM)何时从主存中将数据读入cpu 缓存,也不能保证何时将数据从 cpu 缓存写入到主存中。

而经过volatile修饰的变量,每次读操作都会直接从计算机的主存中读取,而不是从 cpu 缓存中读取;同样,每次对 volatile 变量的写操作都会直接写入到主存中,而不仅仅写入到 cpu 缓存里。

是想一种情况:

一个变量flag控制了一个或者好几个线程的启停,只用主线程可以对该变量进行写操作,而其他子线程只有读操作。(其实上面讲的例子也是这样的)
变量保存在主存中,因为时普通变量所以各个线程所在的cup的cache中保存者该变量的副本。
此时主线程将flag置为false,想要关闭所有的子线程,会出现一下一种情况
1,主线程的CPUcache中的flag已经置为false,但是主线程还没有将其写入主存,此时其他几个线程读取到的还是flag原先的值,所以会继续执行。
2,主线程已将cache中的数据写入内存,但是其他的线程还没有从主存中读取flag更新自己cache中的flag副本,继续读取副本导致读取到的flag还是原先的true,导致线程继续执行。
只用当主线程将数据写入内存,并且其他子线程从内存中读取后并更新了自己cache中的副本之后,才会让线程停下来,这样会导致线程错误的执行好几次。

而volatile就是保证对变量的每次读都是从主存读的,每次写都是写入到内存为止,这样保证了每次线程读到的变量都是该变量“最新的”值。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值