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

    01-多线程(概述)

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

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

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

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

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

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

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

        总结一下:

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

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

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

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

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

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

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

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

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

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

        扩展:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        第一步,继承Thread类:

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

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

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

        第二部,重写run方法:

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

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

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

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

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

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

        OK,写好啦:
        

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

        现在编译运行:

        demo run成功打印了。

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

        1,定义类继承Thread。

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

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

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

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

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

        让hello world也参与一下~

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

        编译运行:

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

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

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

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

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

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

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

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

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

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

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

        #Java小剧场

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

        #

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

        再回到我们的例子中。

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

        到这里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-多线程(线程练习)

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

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

        运行结果:

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

        这样写会怎样呢:

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

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

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

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

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

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

        线程的四种状态:

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

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

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

        getName:

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

        依然用之前的那个例子~

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

        编译运行:

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

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

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

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

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

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

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

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

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

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

        编译运行:

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

        自定义线程名称成功啦~

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

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

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

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

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

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

        试一下:

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

        运行:

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

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

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

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

        我们来复习一下:

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

        getName():获取线程名称。

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

        还有一个小问题:

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

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

        不是的。

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

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

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

        需求:简单的卖票程序。

        多个窗口同时卖票。

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

        现在4个窗口同时卖票:

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

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

        打印一下线程的名字:

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

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

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

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

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

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

        该用静态啦!

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

        运行:

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

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

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

        那怎么解决呢?

        用一个对象来卖100张票:

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

        运行:

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

        是不是也卖完啦~

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

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

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

        该怎么解决呢?

        快接着看下去~

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

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

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

        我们来看一下Runnable接口:

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

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

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

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

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

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

        主函数中:

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

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

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

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

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

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

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

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

        编译运行:

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

        搞定!

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

        步骤:

        1,定义类实现Runnable接口。

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        这就是接口的由来。

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        怎么做呢?

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

        那怎么让它睡一下呢?

        看一下sleep方法:

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

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

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

        话不多说,写起来:

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

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

        运行一下:

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

        刚刚那个问题就出现啦。

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

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

        安全问题最可怕辣!

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

        问题的原因:

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

        解决办法:

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

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

        就是同步代码块。

        synchronized(对象)

        {

                需要被同步的代码

        }

        写起来~

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

        运行看一下效果:

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

        OK啦!

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

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

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

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

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

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

        同步的前提:

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

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

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

        同步的好处:

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

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

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