java多线程(一)快速认识线程

背景

一般来讲,被问到线程的时候我们都能简单的来几句。

问:实现一个线程有几种方式?

答:继承Thread类,实现Runnable接口,为了展示自己学识的渊博,还可能还会说实现Callable接口通过FutureTask包装器来创建Thread线程,或则说通过线程池来运行一个线程。

问: 继承Thread类和实现Runnable接口有什么区别呢?

答: ...............

一般而言如果平时不太关注这一块的东西,基本上也就停留在这一块了,但是一旦使用到该方面的知识,要用的时候,临时恶补,少不了的是对整个并发体系的理解不够透彻,最终在设计,代码编写的时候少不了的要走不少弯路。

已经有各种书籍博客对Java多线程进行教学及总结了,本系列更多的是帮助博主本人总结,记录,提高,如果在该过程中还能帮助到其他小伙伴,那就真是太开心了。

概念

线程:进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。

进程:一个进程包括由操作系统分配的内存空间,包含一个或多个线程。一个线程不能独立的存在,它必须是进程的一部分。一个进程一直运行,直到所有的非守护线程都结束运行后才能结束。

线程生命周期

232002051747387.jpg
新建状态(New):当线程对象对创建后,即进入了新建状态,如:Thread t = new MyThread();

就绪状态(Runnable):当调用线程对象的start()方法(t.start();),线程即进入就绪状态。处于就绪状态的线程,只是说明此线程已经做好了准备,随时等待CPU调度执行,并不是说执行了t.start()此线程立即就会执行;

运行状态(Running):当CPU开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态。注:就     绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中;

阻塞状态(Blocked):处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才 有机会再次被CPU调用以进入到运行状态。根据阻塞产生的原因不同,阻塞状态又可以分为三种:

1.等待阻塞:运行状态中的线程执行wait()方法,使本线程进入到等待阻塞状态;

2.同步阻塞 -- 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态;

3.其他阻塞 -- 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。

死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

继承Thread类

 class MyThread extends Thread {
        private int index = 0;
        @Override
        public void run() {
            for (index = 0; index < 100; index++) {
                System.out.println(Thread.currentThread().getName() + " " + index);
            }
        }
        public static void main(String[] args) {
            MyThread myThread = new MyThread();
            myThread.start();
        }
    }

实现Runnable接口

class MyThread implements Runnable {
        private int index = 0;
        @Override
        public void run() {
            for (index = 0; index < 100; index++) {
                System.out.println(Thread.currentThread().getName() + " " + index);
            }
        }
        public static void main(String[] args) {
            MyThread myThread = new MyThread();
            Thread thread = new Thread(myThread);
            thread.start();
        }
    }

分析

从以上两个例子我们可以看到,继承Thread也好,实现Runnable接口也好,最终启动线程的时候都是通过

Thread类的start方法。

翻阅Thread类圆满我们可以很清晰的找到答案。

public class Thread implements Runnable 

首先Thread类本身是实现Runnable接口的。

public Thread(Runnable target) {
        init(null, target, "Thread-" + nextThreadNum(), 0);
}

在Thread类中有构造方法如上,用来接收Runnable 的实例对象。那么我们获取到了Runnble实例后最终是如何使用的呢?

  @Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }

见到此方法,那么就明了了。实现Runnable 接口,最终传递给Thread类,最终的作用只是用来更改线程的实现单元。那么我们经常使用的start方法又是用来做什么的呢?

 public synchronized void start() {
        /**
         * This method is not invoked for the main method thread or "system"
         * group threads created/set up by the VM. Any new functionality added
         * to this method in the future may have to also be added to the VM.
         * A zero status value corresponds to state "NEW".
         */
        if (threadStatus != 0)
            throw new IllegalThreadStateException();

        /* Notify the group that this thread is about to be started
         * so that it can be added to the group's list of threads
         * and the group's unstarted count can be decremented. */
        group.add(this);
        boolean started = false;
        try {
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
                /* do nothing. If start0 threw a Throwable then
                  it will be passed up the call stack */
            }
        }
    }
    private native void start0();

如源码所述,start方法最终是用来调用本地方法,启动线程的。

总结

通过源码阅读,我们知道继承Thread类重写run方法,还是实现Runnable类重写run方法,最终的作用是编写线程的执行单元,真正创建一个线程的是start方法。同理我们可以猜测,在开头提到的其他创建线程的方式,溯源的话都应该是通过Thread类的start方法来启动一个线程,只不过是封装的程度不一样。

一个简单的认识过程,于我们的学习来说,起到了一个入门的作用,接下来的一个系列我会通过我重零接触Java多线程的过程做一个简单分享。小伙伴们可以通过我不好的经验总结更加完善的知识体系。

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Play & Scala 技术分享

HTTP Cookie的域名和路径匹配

2725
来自专栏顶级程序员

为什么文件名要小写?

来自:阮一峰的网络日志 链接:www.ruanyifeng.com/blog/2017/02/filename-should-be-lowercase.htm...

2885
来自专栏腾讯IVWEB团队的专栏

nodejs 中错误捕获的一些最佳实践

本文为翻译文章,原文比较长,感觉也有点啰嗦,所以根据个人理解猜测梳理出本文。

5950
来自专栏進无尽的文章

设计模式| 行为型模式 (下)

行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代器模式、解释器模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式。分两篇文章...

1002
来自专栏林冠宏的技术文章

java 线程 Thread 使用介绍,包含wait(),notifyAll() 等函数使用介绍

(原创,转载请说明出处!谢谢--https://cloud.tencent.com/developer/user/1148436/activities)  此文...

1957
来自专栏阮一峰的网络日志

为什么文件名要小写?

上周,《中文技术文档写作规范》加入了文件的命名规则。 "文件名建议只使用小写字母,不使用大写字母。" "为了醒目,某些说明文件的文件名,可以使用大写字母,比...

2866
来自专栏JAVA高级架构

《深入理解java虚拟机-高效并发》读书笔记

Java内存模型与线程 概述   多任务处理在现代计算机操作系统中几乎已是一项必备的功能,多任务运行是压榨手段,就如windows一样,我们使劲的压榨它运行多个...

3257
来自专栏无题

线程安全与锁优化——深入理解JVM阅读笔记

我根据我的理解把一些关键的要点整理了出来,并对其中一些内容作了删改。 参考地址:http://www.cnblogs.com/pacoson/p/5351355...

3585
来自专栏Ryan Miao

redis学习之二from github

大概敲了一遍基本命令,熟悉了redis的存储方式。现在开始进一步系统的学习。学习教程目前计划有三个,一个是github上的https://github.com/...

3166
来自专栏逆向技术

16汇编第十讲完结Call变为函数以及指令的最后讲解

16汇编完结Call变为函数以及指令的最后讲解 学了10天的16位汇编,这一讲就结束了,这里总结一下昨天的LOOP指令的缺陷,因为lOOP指令的缺陷,所以我们都...

18510

扫码关注云+社区

领取腾讯云代金券