android 多线程 — Thread 类

java 的线程类型是 Thread ,多线程的学习是从 Thread 开始的

创建 Thread 的2种方式:

class MyThread extends Thread{
    private String name ;

    public MyThread(){
        name = "AA";
    }

    @Override
    public void run() {
        System.out.println(name);
    }
}

// 启动 Thread 线程对象
MyThread thread = new MyThread();
thread.start();
class MyRunnable implements Runnable{
    public MyRunnable() {
    }

    @Override
    public void run() {
        System.out.println("子线程ID:"+Thread.currentThread().getId());
    }
}

// 把 Runnable 作为参数传递给 Thread 对象
MyRunnable runnable = new MyRunnable();
Thread thread = new Thread(runnable);
thread.start();

线程调度

上面我们先回味了一下 Thread 怎么创建,怎么简单的跑起来,食髓知味,我们也要开始深入内里学习原理,夯实我们多线程的基础知识

目前来说线程调度有2种方式:

  • 协同式 - 线程之间的系统执行时间,由线程本身进行进行控制,这种线程调度方式就像接力赛,一个执行完毕后交由下一个接力,如当前线程执行完毕后,通知系统调度到其他线程执行。这玩意就是协程,我已经在 kotlion 中领略过了,线程的调度不再交给系统内核完完成,完全由 coder 自己控制,何时挂起,何时恢复
    • 优势:线程的切换是可预知的,线程之间不存在资源同步的问题,但问题是协程也不是完全脱离传统线程对象,还是会产生资源同步问题
    • 劣势:协同调度的致命缺点是当某个线程执行有问题的时候,会导致整个运行阻塞和系统崩溃
  • 抢占式 - 线程之间的系统执行时间,是由系统进行控制,而抢占式的线程调度对线程的不可预知,系统定期的中断当前正在执行的线程,将CPU执行权切换到下一个等待的线程。所以任何一个线程都不能独占CPU。正因为这种定期的线程切换导致线程之间存在不同的问题。当线程执行过程中,某个线程出现问题的时候,由线程对CPU不具有独占性。因此不会造成阻塞。我们所使用的操作系统都是是用抢占性的线程调度。如果使用协同式的线程调度情况下,如果我们再使用某个软件出现问题时候,操作系统处于阻塞状态,导致整个操作系统崩溃,我们肯定会抓狂。

我们只要知道 Java线程调度就是抢占式的就行了,线程之间通过竞争时间段来实现执行任务


Thread 线程的状态

不废话直接看图:


1
2

线程状态:

  • 创建(new)状态
    我们刚刚 创建 new 一个 Thread 对象时

  • 就绪(runnable)状态:
    Thread 对象所需要的都准备完毕,此时可以调用 start() 方法了,剩下的就是等待CPU进行调度

  • 运行(running)状态:
    执行 run() 方法让线程跑起来

  • 阻塞(blocked)状态
    暂时停止执行, 可能将资源交给其它线程使用

  • 终止(dead)状态:
    线程销毁

线程运行描述:

  • 通常当我们 new 一个线程对象来执行任务时,线程对象不会立即进入就绪状态,因为线程的运行需要一些条件(比如内存资源,在前面的JVM内存区域划分一篇博文中知道程序计数器、Java栈、本地方法栈都是线程私有的,所以需要为线程分配一定的内存空间),只有线程运行需要的所有条件满足了,才进入就绪状态。

  • 当线程进入就绪状态后,不代表立刻就能获取CPU执行时间,也许此时CPU正在执行其他的事情,因此它要等待。当得到CPU执行时间之后,线程便真正进入运行状态。

  • 线程在运行状态过程中,可能有多个原因导致当前线程不继续运行下去,比如用户主动让线程睡眠(睡眠一定的时间之后再重新执行)、用户主动让线程等待,或者被同步块给阻塞,此时就对应着多个状态:time waiting(睡眠或等待一定的事件)、waiting(等待被唤醒)、blocked(阻塞)。

  • 当由于突然中断或者任务执行完毕,线程就会被消亡。

blocked、waiting、time waiting 都可以叫阻塞,详细区别下是有必要的,涉及的方法不同

Thread 类方法解析


上面我们说了如何创建一个 Thread 线程对象并执行,获取到结果。其实 Thread 自身还有一些很重要的方法需要记住。

我简单点直接放张图


Thread 方法列表
  • currentThread()
    返回所在线程对象,这是个静态方法
System.out.println(Thread.currentThread().getName());
  • sleep()
    让当先线程睡眠指定的毫秒时间,sleep 方法会阻塞当前线程,但是不会释放当前线程所持有的对象锁
// 当前线程睡眠1秒
Thread.currentThread().sleep(1000);
  • yield()
    让当前线程交出本次 CPU 时间,并释放所持有的对象锁,竞争下次 CPU 时间。需要注意的是 yield 只是释放本次的 CPU 执行机会,下次该竞争还竞争。调用yield方法并不会让线程进入阻塞状态,而是让线程重回就绪状态,它只需要等待重新获取CPU执行时间,这一点是和sleep方法不一样的。还有这也是一个静态方法
Thread.yield();
  • start()
    线程启动方法,当调用start方法后,系统才会开启一个新的线程并分配需要的资源

  • run()方法
    run方法是不需要用户来调用的,当通过start方法启动一个线程之后,当线程获得了CPU执行时间,便进入run方法体去执行具体的任务。注意,继承Thread类必须重写run方法,在run方法中定义具体要执行的任务。

  • getId()
    获取当前线程唯一标识

Thread t= Thread.currentThread();
System.out.println(t.getName()+" "+t.getId());
  • isAlive()
    判断当前线程是否处于活动状态,不是 running 状态都不叫活跃状态
        Thread t1 = new Thread();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Log.d("AA", Thread.currentThread().getName() + "isAlive:" + t1.isAlive());
Snip20180530_2.png

可以看到就绪状态都不行,都不算是活跃状态,其他的可想而知

  • join()
    让当前线程进入阻塞,等待被 join 方法标记的线程执行完毕再执行。感觉上是不是有点像上面那个 callable 和 Future ,相当于给被 join 标记的线程添加一个返回值,我们等待这个返回值获取到数据再继续我们当前的线程。
        Thread t2 = new Thread() {
            @Override
            public void run() {
                super.run();
                try {
                    Thread.sleep(2000);
                    Log.d("AA", Thread.currentThread().getName() + "时间,time:" + System.currentTimeMillis());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };

        try {
            Log.d("AA", Thread.currentThread().getName() + "时间,time:" + System.currentTimeMillis());
            t2.start();
            t2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        Log.d("AA", Thread.currentThread().getName() + "时间,time:" + System.currentTimeMillis());
Snip20180530_3.png

可以看到 UI 线程被成功的阻塞了,和我们的预期一样,和 callable 和 Future 的效果是一样的

  • getName和setName
    用来得到或者设置线程名称。

  • getPriority和setPriority
    用来获取和设置线程优先级。

  • setDaemon和isDaemon
    用来设置线程是否成为守护线程和判断线程是否是守护线程。

守护线程和用户线程的区别在于:守护线程依赖于创建它的线程,而用户线程则不依赖。举个简单的例子:如果在main线程中创建了一个守护线程,当main方法运行完毕之后,守护线程也会随着消亡。而用户线程则不会,用户线程会一直运行直到其运行完毕。在JVM中,像垃圾收集器线程就是守护线程。

找到张图表示线程各方法的效果:


9110701-a08a6158edf64957.jpeg

停止线程


线程停止是个重要的点,有3中写法:

  • Thread.stop()
    这个方法已经被废弃不用了,
  • interrup()
    interrup 有个问题是不一定能终止线程,这点我不是很了解,建议大家再去找找资料
  • 加标记位
    大家知道,线程执行方法完成了,那么自然就会终止线程了,所以我们在 线程的 run 方法里都是加一个无限虚循环的 while(true),我们只要把这个 true 用标志位来代替,想终止的时候把这个标记改成 false 自然就能安全的退出线程了。

线程的优先级


在Java中,线程的优先级分为1~10这10个等级,如果小于1或大于10,则JDK抛出异常throw new IllegalArgumentException()。
JDK中使用3个常量来预置定义优先级的值,代码如下:

  • public final static int MIN_PRIORITY = 1;
  • public final static int NORM_PRIORITY = 5;
  • public final static int MAX_PRIORITY = 10;
Thread thread = new Thread();
t1.setPriority( Thread.NORM_PRIORITY );

Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);//-20~19,通过进程设置

这两种设置方式是相对独立的,在Android中,一般建议通过Process进程设置优先级

线程优先级特性:

  • 继承性
    比如A线程启动B线程,则B线程的优先级与A是一样的。
  • 规则性
    高优先级的线程总是大部分先执行完,但不代表高优先级线程全部先执行完。
  • 随机性
    优先级较高的线程不一定每一次都先执行完。

守护线程


在Java线程中有两种线程,一种是User Thread(用户线程),另一种是Daemon Thread(守护线程)。
Daemon的作用是为其他线程的运行提供服务,比如说GC线程。其实User Thread线程和Daemon Thread守护线程本质上来说去没啥区别的,唯一的区别之处就在虚拟机的离开:如果User Thread全部撤离,那么Daemon Thread也就没啥线程好服务的了,所以虚拟机也就退出了。

守护线程并非虚拟机内部可以提供,用户也可以自行的设定守护线程,方法:public final void setDaemon(boolean on) ;但是有几点需要注意:

thread.setDaemon(true)必须在thread.start()之前设置,否则会跑出一个IllegalThreadStateException异常。你不能把正在运行的常规线程设置为守护线程。 (备注:这点与守护进程有着明显的区别,守护进程是创建后,让进程摆脱原会话的控制+让进程摆脱原进程组的控制+让进程摆脱原控制终端的控制;所以说寄托于虚拟机的语言机制跟系统级语言有着本质上面的区别)

在Daemon线程中产生的新线程也是Daemon的。 (这一点又是有着本质的区别了:守护进程fork()出来的子进程不再是守护进程,尽管它把父进程的进程相关信息复制过去了,但是子进程的进程的父进程不是init进程,所谓的守护进程本质上说就是“父进程挂掉,init收养,然后文件0,1,2都是/dev/null,当前目录到/”)

不是所有的应用都可以分配给Daemon线程来进行服务,比如读写操作或者计算逻辑。因为在Daemon Thread还没来的及进行操作时,虚拟机可能已经退出了。

最后

我这里写的也不是很全,有没看懂的建议大家再去找资料,这里主要是给我自己看,按照的我的记忆顺序写的, 以后忘了再看方便的多。

有不全的大家见谅,有错误请下面留言我更正

参考资料:

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

推荐阅读更多精彩内容