Java基础day11笔记:多线程概述|创建线程的两个方法|线程的运行状态|Thread中的一些方法|多线程的安全问题|多线程同步代码块

    01-多线程(概述)

        接下来,我们来说一说Java中特有的一个知识技术:多线程

        在说线程这个概念之前呢,需要说一个更显而易见的概念:进程。

        何为进程呢?进程就是正在进行中的程序。

        进程可以同时开启,其实就是cpu在对它们执行。

        不过,虽然它们看起来像是同时在执行,其实,cpu在某一时刻只能执行某一个程序,它只是在做着超快的切换,咻咻咻咻~才导致我们看到的是各个程序在同时执行。

        再讲个迅雷的例子。是不是有时候会有多条下载请求呀?一个进程,里面可能会有多条执行路径。什么意思呢?在我们进行迅雷下载的时候呢,大家都知道迅雷可以有多条下载路径,比如100M的文件,迅雷可能会分成五个部分,一个部分一个部分的同时发送请求到服务端去下载数据。我们想一想,如果我们下载是用一个路径,就是先下载第一个数据,然后一个一个下载,一直下载到第100个数据。可是如果是五条路径下载,五个人同时在搬东西,是不是就比一个人快多啦?而且这五个人都是在这一个进程中哦,这其中的每一个人,就叫做线程。线程是进程中的内容,每一个应用程序,里面都至少有一个线程。

        线程有一个特点,它是程序中的控制单元,或者叫执行路径。

        总结一下:

        进程:是一个正在执行中的程序。

                 每一个进程执行都有一个执行顺序,该顺序是一个执行路径,或者叫一个控制单元。

        每个程序执行,都需要启动内存空间,而进程其实就是用来标识内存空间的,它用来封装里面的那些控制单元。

        线程:就是进程中的一个独立的控制单元。

                  线程在控制着进程的执行。

                  一个进程中至少有一个线程。

        以Java为例,编译运行的时候,它有两个进程:编译进程和运行进程。

        我们重点说一下运行进程。

        Java虚拟机启动的时候会有一个进程java.exe。该进程中至少有一个线程来负责Java程序的执行,而且这个线程运行的代码存在于main方法中。该线程称之为主线程。

        其实,很多书里面都在说,这个程序在启动执行的时候是单线程程序,因为还没有其他线程存在呢。这样说理解也是对的。

        扩展:

        但是,如果你深究一下虚拟机的话,其实不是单线程。虚拟机启动的时候,它就是多线程。为什么呢?另外的线程是谁呢?

        试想一下,(这个只作为了解喔,因为说这个程序是单线程也没问题哒)程序在运行的时候,是不是要建立对象,调用方法,这个时候是主线程在帮我们做这件事情,堆里面会产生很多对象,如果其中某一个对象不被使用了,它就会被垃圾回收机制回收了。主线程还在继续执行着其他对象中的操作,而这个对象就被干掉了,是不是在同时进行呐?是滴!

        这个时候就产生了一个问题,主线程在继续调用方法的同时,另外的不被使用的对象就被垃圾回收机制回收了。所以这个时候虚拟机至少有两个线程,一个是主线程,一个是垃圾回收的线程。

        多线程存在的意义是什么呢?

        多线程的出现呢,可以让我们程序中的部分产生同时运行的效果。而且在下载东西的时候,多线程下载还可以帮我们提高效率。

    02-多线程(创建线程-继承Thread类)

        那么,如何在我们的程序中自定一个控制单元,或者说,自定一个线程?

        Java中已经为我们提供好了对这类事物的对象体现。

        通过对API的查找,我们发现,java的核心包java.lang包中就有一个对象叫做Thread,它就是程序中的执行线程,就是用于描述控制单元这类事物的对象。

        我们发现创建新执行线程有两种方法:

        连范例都有啦,我们自己来写~

        创建线程的第一种方式:继承Thread类。       

        第一步,继承Thread类:

        接下来我们看看Thread中的run方法是怎样的~

        第二部,重写run方法:

        继承完之后,我们接下来就要创建它的对象啦。注意哦,建立好一个对象,其实就是创建好一个线程。

        创建好对象之后就要调用run方法啦。

        我们看到范例中有一个start方法:

        查一下start方法是干什么的~

        OK,写好啦:
        

        现在编译运行:

        demo run成功打印了。

         总结一下, 创建线程的第一种方式:继承Thread类的步骤为:

        1,定义类继承Thread。

        2,复写Thread类中的run方法。

        3,调用线程的start方法,该方法有两个作用:启动线程,调用run方法。

        冷静下来思考一下,但是这跟我们以前调用方法的方式有什么区别呢?怎么看不出来呀?

        接下来我们让它多运行一会儿,写了一个循环~

        让hello world也参与一下~

        编译运行:

        我们分析一下它的执行路径: 

        所以我们在打印结果的时候是交替打印的。

        为什么是交替的呢?main线程和d线程它们俩不是同时执行吗?

        跟你讲哦,真实情况下是不可能的。

        windows本身是一个多任务操作系统,看上去它确实在同时执行,其实真正的情况是,cpu在某一时刻下,只能执行一个程序。为什么看起来是同时执行的呢?因为cpu在这些程序之间做着快速的切换,切换的速度是相当快的,快到你根本感觉不出来它在切换,所以你觉得它在同时执行。

        而进程中真正在执行的是线程,所以cpu在切换的是每一个进程中的线程。而一个进程中有多个线程的话,cpu也要做切换呢。(这也是机器中程序开的越多越慢的原因)

        我们现在就不说多进程啦,我们就说其中一个进程好啦。

        在这个例子中,这一个进程中,就已经有多个线程啦。cpu执行main一会儿,执行d一会儿。那么这种情况,我们形象的把它称之为,多个线程在抢劫cpu资源。这只是一种形象的说法,其实是cpu说了算。只是说“抢”更形象一点儿~

        #Java小剧场

        cpu:执行main一会儿,执行main一会儿,好了好了,再执行d一会儿,再执行d一会儿,再执行d一会儿,诶,好像都没顾到main了,那我再执行main一会儿好了。。。

        #

        早期有一些病毒就是通过消耗cpu资源让电脑死机的。它就是一直在抢用cpu资源,从而达到让别人抢不着的效果。

        再回到我们的例子中。

        到这里main线程就结束啦,那这个进程会结束吗?不会。因为d线程还没有结束,只要d线程还在,这个进程就存在。

        现在有了双核、多核处理器,就可以实现同时运行了,这个cpu处理这个,那个cpu处理那个。不过具体是怎么切换的,我们现在暂时控制不了,可以学一下多核编程玩一下~

        双核以后谁是瓶颈?内存。

        #Java小剧场

        小楠老师在做一个一对三的高考冲刺班,她同时带三个孩子,一会给这个辅导,一会给那个辅导,还算忙得过来。这个时候又有一个人报名了,小楠老师忙不过来了,于是就又请了一位老师,这位新老师来带新报名的这个孩子。这样,小楠老师和新老师可以同时带四个孩子。

        小楠老师的补习班空间不大,四个孩子就刚好坐满啦。其实新老师也可以再带两个孩子呢,这样小楠老师和新老师就可以同时带六个孩子,达到最大的效率。可是因为补习班太小,不能容纳更多孩子,小楠老师和新老师还是只能带四个孩子。

        #

        现在双核之后,还有四核、八核,听起来好腻害哦。但是可能会出现cpu想处理数据但没数据处理的情况。(没有地方装数据呀)

        再回到刚刚的例子中。我们又多运行了几次,发现每次运行打印的顺序都不同。

        因为多个线程都在获取cpu的执行权。cpu执行到谁,谁就运行。

        明确一点,在某一个时刻,只能有一个程序在运行。(多核除外)

        cpu在做着快速的切换,以达到看上去是同时运行的效果。

        我们可以形象的把多线程的运行行为看做在互相抢夺cpu的执行权。

        这就是多线程的一个特性:随机性。谁抢到谁执行,至于执行多长时间,cpu说的算。

        当然,也会出现这样的情况,hello world执行完了,才执行demo run,或者demo run执行完了,才想起来执行hello world。这种情况是cpu切换得有点慢。哈哈~迟钝的cpu~

        再当然,在没有被特意控制的情况下,不会出现cpu把一个全部执行完再执行另一个的情况。 因为cpu它在优化资源,它得去快速的做着切换,才能实现同时运行的效果,否则会出现一个程序在执行,另一个程序执行不了。

    04-多线程(线程练习)

        练习:创建两个线程,和主线程交替运行。

        运行结果:

        这样写会怎样呢:

        还是只有一个主线程在执行,另外两个线程没有开启。

        主线程先打印one run,再打印two run,最后打印main。

        这里我们的循环只有60次,所以主线程运行一段程序的时候,其他程序等待时间不会太长,但是如果是6000、60000次呢?这个其他程序的等待时间就很长啦。

        所以,建立多个线程,多个程序就可以“同时”运行~

    05-多线程(线程运行状态)

        线程的四种状态:

    06-多线程(获取线程对象以及名称)

        我们来看看关于线程名称的方法,有setName:

        getName:

        我们用getName方法来获取一下线程名称。

        依然用之前的那个例子~

        编译运行:

        我们发现有Thread-1和Thread-0两个名字。

        原来线程都有自己默认的名称。

        Thread-编号 该编号从0开始。

        我们也可以改变线程的名称,用setName方法就好啦。

        不过查一下Thread的构造函数,发现线程初始化的时候就可以有名称啦!

        我们调用一下父类的构造方法:

        现在在创建它们的时候就给它们起一下名字:

        编译运行:

        自定义线程名称成功啦~

        另外,Thread类还为我们提供了一个方法:currentThread()。

        这个方法是静态的,说明这个对象没有访问到对象的特有数据,用类名访问就可以啦。

        我们使用这个方法,来获得当前线程的名称:

        获取成功~(运行截图略,就和this.getName()运行结果一样)

        既然两种方法运行结果一样,它们有什么不同呢?

        试一下:

        运行:

        运行结果是true哦。所以它俩是一样哒。

        那用this调用就行了呀,多简单,用currentThread那么长,那么麻烦。

        不是的。this调用的方式并不通用,只有在类的对象调用类中方法时才可以用则中方式调用。而currentThread是标准通用方法,无论谁调用都可以用~

        我们来复习一下:

        static Thread currentThread():获取当前线程对象。

        getName():获取线程名称。

        设置线程名称:setName()或者构造函数。

        还有一个小问题:

        Thread-0和Thread-1“同时”运行的时候,用的x是同一个吗?

        不是的。

        Thread-0建立的时候,内存中会为它分配一块内存空间,其中有一块叫x;Thread-1建立的时候,内存也会为它分配另一块内存空间,其中也有一块叫x。多个线程“同时”运行的时候,注意局部变量是每个内存空间中都有一份哦。

    07-多线程(售票的例子)

        接下来,我们用一个事例来对第二种方法进行阐述。

        需求:简单的卖票程序。

        多个窗口同时卖票。

        现在4个窗口同时卖票:

        运行一下,发现分不清都是哪个窗口卖的:

        打印一下线程的名字:

        运行,我们发现了一个问题:

        1、2、3、4号窗口都卖了1号票。一节车厢就100个座,可是现在卖出了400个座。

        问题在于,创建一个对象,里面就有100张票。

        我们的解决方式:让4个对象共享100张票。

        该用静态啦!

        运行:

        OK啦。没有重复卖票的啦。

        但是,我们一般是不是不定义静态呀?因为它的生命周期太长啦!

        那怎么解决呢?

        用一个对象来卖100张票:

        运行:

        是不是也卖完啦~

        但是发现这些乱七八糟什么东西呀:

        也就是说,线程已经开启了,并且调用start函数,从开启状态进入了运行状态。这个时候又调用了start函数,又进入运行状态,这就是无效的呀。

        该怎么解决呢?

        快接着看下去~

    08-多线程(创建线程-实现Runnable接口)

        解决这个问题,我们就需要引入第二种创建线程的方式了。

        我们来看一下Runnable接口:

        这个接口里面非常的爽呀,就一个方法:

        我们来跟着示例代码来写~emmm...不太懂耶。

        那慢慢一步一步分析着来~先让Ticket类实现Runnable接口:

        主函数中:

        我们现在需要想办法让线程调用Ticket中的run,需要让Ticket中的run和Thread创建的对象有关系!

        我们需要在创建线程对象时就明确要运行什么代码。

        Thread有一个构造方法,比较特殊:

        它可以接收Runnable接口类型的对象。

        所以这就是我们要实现Runnable接口的原因,因为Thread类认识这个Runnable接口。

        所以,我们在new Thread方法的同时,就可以指定run方法所属的对象。

        编译运行:

        搞定!

        这就是创建线程的第二种方式:实现Runnable接口。

        步骤:

        1,定义类实现Runnable接口。

        2,覆盖Runnable接口中的run方法。

                将线程要运行的代码存放在该run方法中。

        3,通过Thread类建立线程对象。

        4,将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数。

                为什么要将Runnable接口的子类对象传递给Thread的构造函数?

                因为,自定义的run方法所属的对象是Runnable接口的子类对象。

                所以要让线程去指定对象的run方法。就必须明确该run方法所属的对象。

        5,调用Thread类的start方法开启线程,并调用Runnable接口子类的run方法。

        那么,这两种创建线程的方式,也就是实现方式和继承方式有什么区别呢?

        我们先讲一下Runnable接口的由来:

        左边的Student类,没有父类,它里面的run方法需要被多线程执行,所以就继承了Thread类。

        可是,Student在演变的过程中,抽取出来了一个父类Person,这个时候Student类中的run方法需要被多线程执行,但是因为它已经有父类了,而Java又不支持多继承,所以无法再继承Thread类。

        这时,Java提供了一个Runnable接口来解决这个问题,“你可以不叫我爸爸,我也可以帮你执行代码,只要你符合我的规则就行”,将这个功能抽取出来封装到了Runnable接口中。

        这就是接口的由来。

        因此,实现方式的好处:避免了单继承的局限性,把它作为了一种功能的扩展封装在了接口中。在定义线程时,建议使用实现方式。

        而且这个构造方法使用了多态:

        因为日后会出现什么样的接口子类是无法预知的,所以它用了多态的形式,你只要符合规则,它都可以用!你只要符合PCI的规则,后期粗现什么样的版本,它都可以帮你运行!

        我们发现,Thread类本身也实现了Runnable接口:

        Runnable接口的定义,其实就是在确立线程要运行代码所存放的位置。

        两种创建线程的方式还有一个区别:

        继承Thread:线程代码存放在Thread子类的run方法中。

        实现Runnable:线程代码存放在接口子类的run方法中。

        它们代码的存放位置不一样。

        当然,如果你的类没有父类,用第一种方式也是完全可以的。

    09-多线程(多线程的安全问题)

        现在只剩最后一张票了,然后4个窗口都拿到了执行资格,在等待执行权,最后执行权比如说给了0号,0号卖出后执行权比如给了1号,这个时候1再继续执行票数就为负了,这显然不符合常理。我们的程序有bug啦! 

        我们现在模拟一下这个过程,让我们真实的看到这个现象。

        怎么做呢?

        那怎么让它睡一下呢?

        看一下sleep方法:

        我们看一下这个方法的特点:

        它是静态的,没有访问到对象的特有数据,并且它抛出了异常。

        话不多说,写起来:

        因为Ticket实现了Runnable接口,而这个接口并未抛出异常,所以Ticket也不能抛出异常,所以只能try哦。

        运行一下:

        刚刚那个问题就出现啦。

        通过分析,发现,打印出0,-1,-2等错票。

        多线程的运行出现了安全问题。

        安全问题最可怕辣!

        多线程中一定要小心安全问题,一旦产生就非常要命。

        问题的原因:

        当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完,另一个线程参与进来执行,导致了共享数据的错误。

        解决办法:

        对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他过程不可以参与执行。

        Java对于多线程的安全问题提供了专业的解决方式。

        就是同步代码块。

        synchronized(对象)

        {

                需要被同步的代码

        }

        写起来~

        运行看一下效果:

        OK啦!

    10-多线程(多线程同步代码块)

        在上节课中,这里有个对象:

        这个对象如同锁,持有锁的线程可以在同步中执行。

        没有持有锁的线程,即使获得了cpu的执行权,也进不去,因为没有获取锁。

        火车上的卫生间就是一个经典的的例子。

        同步的前提:

        1,必须要有两个或者两个以上的线程才需要同步。(就你一个人用这个卫生间,就不需要锁门啦)

        2,必须是多个线程使用同一个锁才需要同步。(多个人使用的是同一个卫生间)

        必须保证同步中只能有一个线程在运行。

        同步的好处:

        解决了多线程的安全问题。

        不过它也有弊端:虽然解决了问题,但是执行的时候每次都要判断这个锁,所以较为消耗资源。(越安全越麻烦)

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

推荐阅读更多精彩内容

  • 进程和线程 进程 所有运行中的任务通常对应一个进程,当一个程序进入内存运行时,即变成一个进程.进程是处于运行过程中...
    胜浩_ae28阅读 5,084评论 0 23
  • 进程和线程 进程 所有运行中的任务通常对应一个进程,当一个程序进入内存运行时,即变成一个进程.进程是处于运行过程中...
    小徐andorid阅读 2,796评论 3 53
  • 本文主要讲了java中多线程的使用方法、线程同步、线程数据传递、线程状态及相应的一些线程函数用法、概述等。在这之前...
    4ea0af17fd67阅读 586评论 2 17
  • Java多线程学习 [-] 一扩展javalangThread类 二实现javalangRunnable接口 三T...
    影驰阅读 2,946评论 1 18
  • 单任务 单任务的特点是排队执行,也就是同步,就像再cmd输入一条命令后,必须等待这条命令执行完才可以执行下一条命令...
    Steven1997阅读 1,162评论 0 6