深入浅出:Java多线程编程实战(一)

前言

    在开发中我们经常使用线程来优化程序,提高系统执行效率,今天我们就来简单概述一下Java开发过程中需要了解的多线程知识点。首先,整理出一张图概括了Java多线程的体系:

一、进程与线程

    进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。

    线程,有时被称为轻量级进程(Lightweight Process,LWP),是程序执行流的最小单元。线程是程序中一个单一的顺序控制流程,在单个程序中同时运行多个线程完成不同的工作,称为多线程。

    进程和线程的关系可以用下图来描述:

二、同步与异步

    对于一次方法的调用来说,同步方法调用一旦开始,就必须等待该方法的调用返回,后续的方法才可以继续执行;异步的话,方法调用一旦开始,就可以立即返回,调用者可以执行后续的方法,这里的异步方法通常会在另一个线程里真实的执行,而不会妨碍当前线程的执行。


三、并行与并发

    并发和并行是两个相对容易比较混淆的概念。他都可以表示在同一时间范围内有两个或多个任务同时在执行,但其在任务调度的时候还是有区别的,首先看下图:

并发任务执行过程:

从上图中可以看到,两个任务在执行的时候,并发是没有时间上的重叠的,两个任务是交替执行的,由于切换的非常快,对于外界调用者来说相当于同一时刻多个任务一起执行了;而并行可以看到时间上是由重叠的,也就是说并行才是真正意义上的同一时刻可以有多个任务同时执行。


四、线程的状态

线程从创建、运行到结束总是处于下面五个状态之一:新建状态、就绪状态、运行状态、阻塞状态及死亡状态。

1、新建状态(New):

       当用new操作符创建一个线程时,例如new Thread(r),线程还没有开始运行,此时线程处在新建状态。 当一个线程处于新生状态时,程序还没有开始运行线程中的代码。

2、就绪状态(Runnable)

        一个新创建的线程并不自动开始运行,要执行线程,必须调用线程的start()方法。当线程对象调用start()方法即启动了线程,start()方法创建线程运行的系统资源,并调度线程运行run()方法。当start()方法返回后,线程就处于就绪状态,处于就绪状态的线程并不一定立即运行run()方法,线程还必须同其他线程竞争CPU时间,只有获得CPU时间才可以运行线程。因为在单CPU的计算机系统中,不可能同时运行多个线程,一个时刻仅有一个线程处于运行状态。因此此时可能有多个线程处于就绪状态,对多个处于就绪状态的线程是由Java运行时系统的线程调度程序(thread scheduler)来调度的。

3、运行状态(Running)

       当线程获得CPU时间后,它才进入运行状态,真正开始执行run()方法。

4、 阻塞状态(Blocked)

       线程运行过程中,可能由于各种原因进入阻塞状态: 1)线程通过调用sleep方法进入睡眠状态; 2)线程调用一个在I/O上被阻塞的操作,即该操作在输入输出操作完成之前不会返回到它的调用者; 3)线程试图得到一个锁,而该锁正被其他线程持有; 4)线程在等待某个触发条件; ...... 所谓阻塞状态是正在运行的线程没有运行结束,暂时让出CPU,这时其他处于就绪状态的线程就可以获得CPU时间,进入运行状态。

5、 死亡状态(Dead)

       有两个原因会导致线程死亡: 1) run方法正常退出而自然死亡; 2) 一个未捕获的异常终止了run方法而使线程猝死。 为了确定线程在当前是否存活(就是要么是可运行的,要么是被阻塞了),需要使用isAlive方法。如果是可运行或被阻塞,这个方法返回true; 如果线程仍旧是new状态且不是可运行的, 或者线程死亡了,则返回false。

现在我们用一张图来说明它们之间的状态:


五、创建线程的三种方式

(1)继承Thread类创建线程类

1、定义Thread类的子类,并重写该类的run()方法,该run()方法的方法体就代表了线程需要完成的任务.因此把run()方法称为线程执行体

2、创建Thread子类的实例,即创建了线程对象

3、调用线程对象的start()方法来启动该线程.

Java程序运行时默认的主线程,main()方法的方法体就是主线程的线程执行体。

可以看到Thread-0和Thread-1两个线程的输出的i变量不连续-----注意:i变量是FirstThread的实例变量,而不是局部变量,但是因为程序每次创建线程对象都需要创建一个FirstThread对象,所以Thread-0和Thread-1不能共享该实例变量。

使用继承Thread类的方法来创建线程类时,多个线程之间是无法共享线程类的实例变量。


(2) 实现Runnable接口创建线程类

1、定义Runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体

2、创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象

3、调用线程对象的start()方法来启动该线程

当线程类实现Runnable接口时,如果想获取当前线程,只能用Thread.currentThread()方法可以看到两个子线程的i变量是连续的这是因为采用Runnable接口的方式创建的多个线程可以共享线程类的实例变量.是因为:程序创建的Runnable对象只是线程的target,而多个线程可以共享一个target,所以多个线程可以共享一个线程类(实际上应该是线程的target类)的实例变量。


(3)使用Callable和Future创建线程

通过实现Runnable接口创建多线程时,Thread类的作用就是把run()方法包装成线程执行体.从Java5开始,Java提供了Callable接口,该接口可以理解为是Runnable接口的增强版,Callable接口提供了一个call()方法可以作为线程执行体,但call()方法比run()方法功能更强大,call()方法可以有返回值.call()方法可以声明抛出的异常。

但是Callable接口并不是Runnable接口的子接口,所以Callable对象不能直接作为Thread的target.而且call()方法还有一个返回值,call()方法并不是直接调用的,它是作为线程执行体被调用的.好在Java提供了Future接口来代表Callable接口里的Call()方法的返回值,并为Future接口提供了一个FutureTask实现类,该实现类既实现了Future接口,并实现了Runnable接口----可以作为Thread类的target。

在Future接口里定义了几个公共方法来控制它关联的Callable任务。

Callable接口有泛型限制,并且Callable接口里的泛型形参类型与call()方法返回值类型相同.而且Callable接口是函数式接口,可以用Lambda表达式创建Callable对象。

创建并启动具有返回值的线程的步骤如下:

1、创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,且该call()方法有返回值,再创建Callable实现类的实例.

2、使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值

3、使用FutureTask对象作为Thread对象的target创建并启动新线程

4、调用FutureTask对象的get()方法来获得子线程执行结束后的返回值.

(4)创建线程的三种方式对比

采用实现Runnable、Callable接口的方式创建多线程的优缺点:

1、线程类只是实现了Runnable接口或Callable接口,还可以继承其他类。

2、多个线程可以共享同一个target对象,非常适合多个相同线程来处理同一份资源的情况,较好的体现了面向对象的思想。

3、需要访问当前线程,则必须使用Thread.currentThread()方法。

采用继承Thread类的方式创建多线程的优缺点:

1、因为该线程已经继承了Thread类,所以不能在继承其他父类。

2、编写简单,如果需要访问当前线程,则无需使用Thread.currentThread()方法,直接使用this即可获得当前线程。

    之后还会继续跟大家分享多线程的三大核心、线程池及多线程的三大核心等内容,要学习请继续关注,若文中有所错误之处,还望提出!学习是一个循序渐进的过程,需要时间精力,更需要技巧和规律。天赋往往存在少数人身上,绝大多数人只能不断摸索,有的人花费大量时间还一无所获,有些人却既能轻松工作,又享受了生活。

    其中的原因不得而知:站在巨人的肩膀上必定是要比站在地上攀爬的人快一步的。

    为了让学习变得轻松高效, 现在给大家提供一个学习平台,让你在实践中积累经验掌握原理。主要方向是JAVA架构师,在这里你可以学习Java工程化、高性能及分布式、深入浅出、性能调优、Spring,MyBatis,Netty源码分析和大数据等知识点。可以加入Java后端技术群:819940388群里有阿里大牛直播讲解技术,或是关注微信公众号:Java资讯库,回复“架构”,免费的大型互联网Java技术视频分享给大家。

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

推荐阅读更多精彩内容