Android 多线程:Thread理解和使用总结

Android Thread目录.png

一、Android中的Thread

定义:线程,可以看作是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。

1.1 Thread主要函数

函数名 作用
run() 线程运行时所执行的代码
start() 启动线程
sleep()/sleep(long millis) 线程休眠,进入阻塞状态,sleep方法不会释放锁(其它线程不会进入synchronized方法体或方法块,不释放锁需要try/catch)
yield() 线程交出CPU,但是不会阻塞而是重置为就绪状态,不会释放锁
join()/join(long millis)/join(long millis,int nanoseconds) 线程插队,当该子线程执行完毕后接着执行其它
wait() 进入阻塞状态,释放锁(其它线程可以进入synchronized方法体或方法块,释放锁不需要try/catch)
interrupt() 中断线程,注意只能中断阻塞状态的线程
getId() 获取当前线程的id
getName()/setName() 获取和设置线程的name
getPriority()/setPriority() 获取和设置线程的优先级,范围1-10,默认是5
setDaemon()/isDaemo() 设置和获取是否守护线程
currentThread() 静态函数获取当前线程

1.2 Thread的几种状态

  • 新建状态(new):实例化之后进入该状态;
  • 就绪状态(Runnable):线程调用start()之后就绪状态等待cpu执行,注意这时只是表示可以运行并不代表已经运行;
  • 运行状态(Running):线程获得cpu的执行,开始执行run()方法的代码;
  • 阻塞状态(Blocked):线程由于各种原因进入阻塞状态:join()、sleep()、wait()、等待触发条件、等待由别的线程占用的锁;
  • 死亡状态(Dead):线程运行完毕或异常退出,可使用isAlive()获取状态。

二、Android中Thread的使用

这里写出三种使用方式:
1. 继承Thread,重写run()方法。
使用时直接new并且start()。(不知道什么时候run()方法变成重写了,以前可是没有 @Override)

    public class MyThread extends Thread{

        @Override
        public void run() {
            super.run();
            // do something
        }
    }
    // Thread使用
    public void goThread(){
        new MyThread().start();
    }

2. 实现Runnable,重写run()方法来执行任务。

    public class MyRunnable implements Runnable{

        @Override
        public void run() {
            // do something
        }
    }
    new Thread(new MyRunnable()).start();

另一种启动方式

    new Thread(new Runnable() {
        @Override
        public void run() {
            
        }
    }).start();

3. 通过Handler启动线程。
首先定义好Handler和Runnable :

    private int count = 0;
    private Handler mHandler = new Handler();

    private Runnable runnable = new Runnable() {
        @Override
        public void run() {
            Log.i("download",Thread.currentThread().getName()+":"+count);
            count ++;
            mHandler.postDelayed(runnable,1000); // 执行后延迟1000毫秒再次执行,count已++
        }
    };

然后使用mHandler的post()方法执行线程。当上方的Runnable执行后里面定义了mHandler.postDelayed(runnable,1000);开启延迟1000毫秒后再次执行。

  findViewById(R.id.btn_download).setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            mHandler.post(runnable); // handler运行runnable
        }
    });

三、Thread使用其它问题

3.1 如何终止线程

1. 使用boolean变量作为标记
如下,如果变量stop为true则跳出run()方法。关键字volatile表示这个变量随时都有可能发生变化,主要是表示同步,也就是同一时刻只能由一个线程来修改该变量的值。

public class ThreadStopTest extends Thread {

    public volatile boolean stop = false;
    @Override
    public void run() {
        super.run();
        while (!stop){
            // thread runing
        }
    }
    
}

2. 使用interrupt()
该方法可以用来中断进程,然后就可以终止线程,使用该方法分两种情况,线程正常运行状态阻塞状态

(1) 线程正常运行状态
要注意的是interrupt()方法只是中断线程而不是结束线程,在线程正常运行状态下使用该方法是不能结束线程的,正确的做法是判断线程是否被中断来决定是否执行代码。这种方法类似于自定义标记。看一下示例:

...
    // 定义开始和结束线程的方法,与按钮绑定
    public void goThread(){
        if(null == myThread){
            myThread = new MyThread();
        }
        myThread.start();
    }
    private void stopThread() {
        if(null != myThread && myThread.isAlive()){
            myThread.interrupt();
            myThread = null;
        }
    }

    public class MyThread extends Thread{
        @Override
        public void run() {
            super.run();
            int i = 0;
            // 判断状态,如果被打断则跳出并将线程置空
            while (!isInterrupted()){
                i++;
                Log.i("thread",Thread.currentThread().getName()+":Running()_Count:"+i);
            }
        }
    }

(2) 线程阻塞状态
先看下面的例子,重新写了一个SleepThread,开启和停止的代码省略。这个线程里每间隔1s循环增加i的值并打印。

    public class SleepThread extends Thread{
        @Override
        public void run() {
            super.run();
            int i = 0;
            while(true){
                try {
                    i++;
                    Thread.sleep(1000);
                    Log.i("thread",Thread.currentThread().getName()+":Running()_Count:"+i);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    Log.i("thread",Thread.currentThread().getName()+"异常抛出,停止线程");
                    break;
                }
            }

        }
    }

当我开始和终止该线程后Log如下:

10-14 19:46:57.562 32024-32227/com.sky.androidthreadapp I/thread: Thread-16142:Running()_Count:1
10-14 19:46:58.562 32024-32227/com.sky.androidthreadapp I/thread: Thread-16142:Running()_Count:2
...
10-14 19:47:01.564 32024-32227/com.sky.androidthreadapp I/thread: Thread-16142:Running()_Count:5
10-14 19:47:01.931 32024-32227/com.sky.androidthreadapp I/thread: Thread-16142异常抛出,停止线程

可以看到调用interrupt()方法后抛出InterruptedException异常,同时break跳出循环达到停止跑代码的作用。原理是当线程进入阻塞状态时,调用interrupt()方法会抛出异常,利用这个异常来跳出循环。
(3) 两种状态一起处理
此外,把两种状态的处理结合在一起是比较好的,这样既可以及时判断线程状态又可以捕获异常来跳出循环。

    public class SleepThread extends Thread{
        @Override
        public void run() {
            super.run();
            int i = 0;
            while(!isInterrupted()){  // 判断线程是否被打断
                try {
                    i++;
                    Thread.sleep(1000);
                    Log.i("thread",Thread.currentThread().getName()+":Running()_Count:"+i);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    Log.i("thread",Thread.currentThread().getName()+"异常抛出,停止线程");
                    break;// 抛出异常跳出循环
                }
            }

        }
    }

3. 使用stop()方法终止线程
恕我直言,这个方法就忽略吧,这个相当于强制关机,万一数据丢失得不偿失。

3.2 线程安全与线程同步

1. 什么是线程安全问题
简单地说,线程安全问题是指多个线程访问同一代码或数据,造成结果和数据的错乱或与期望的结果不同所产生的问题。
2. 如何解决线程安全问题

基本上所有的并发模式在解决线程安全问题的问题上,都采用“序列化访问临界资源”的方案,即在同一时刻只能有一个线程访问临界资源(多个线程可能同时访问的数据或资源),也称同步互斥访问。

(1) synchronized 关键字,保证同时刻只有一个线程进入该方法或者代码块,使用方式(Tips:java中有很多方式来实现线程同步,我们常用的synchronized是效率最低的...但是它方便啊):

  • 线程run()方法中要执行的代码方法添加synchronized关键字,注意要添加在方法的返回值前。
   int count = 100;
   private synchronized void count() {
        if (count > 0) {
            Log.e(TAG, Thread.currentThread().getName() + "--->" + count--);
        } else {
            isRunning = false;
        }
    }
  • 同步代码块的形式使用。
  private void count() {
        synchronized (this) {
            if (count > 0) {
                Log.e(TAG, Thread.currentThread().getName() + "--->" + count--);
            } else {
                isRunning = false;
            }
        }
    }

(2) 特殊域变量volatile修饰变量:告诉虚拟机该变量随时可能更新,因此使用时每次都会重新计算,而不是使用寄存器的值。volatile不会提供任何原子操作,它也不能用来修饰final类型的变量。(不能完全保证线程安全)

private volatile int count = 1000;

(3)使用重入锁实现线程同步。

  • ReentrantLock() : 创建一个ReentrantLock实例
  • lock() :获得锁
  • unlock() : 释放锁
    private void count() {
        lock.lock();
        if (count > 0) {
            Log.e(TAG, Thread.currentThread().getName() + "--->" + count--);
        } else {
            isRunning = false;
        }
        lock.unlock();
    }

(4)ThreadLocal管理变量。如果一个变量使用ThreadLocal进行管理,每一个使用该变量的线程都会获得该变量的副本,副本之间相互独立,所以每个线程都可以修改变量而不会对其它线程造成影响。

    private static ThreadLocal<Integer> number = new ThreadLocal<Integer>(){
        // 重写方法,设置默认值
        @Override
        protected Integer initialValue() {
            return 1;
        }
        // 自定义方法设置变量值
        public void saveNumber(int newNumber){
            number.set(number.get() + newNumber);
        }
        // 自定义方法获取变量值
        public int getNumber(){
            return number.get();
        }
    };

相关文章:
Android多线程:理解和简单使用总结

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,657评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,662评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,143评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,732评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,837评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,036评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,126评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,868评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,315评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,641评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,773评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,859评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,584评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,676评论 2 351

推荐阅读更多精彩内容