美文网首页
30 多任务

30 多任务

作者: ca8519be679b | 来源:发表于2020-03-20 16:11 被阅读0次

    并发和并行

    并发:多个事件在同一时间段内发生

    并行:多个事件在同一时刻发生

    这个Python那里有讲过,具体不再复述

    进程

    是指内存中的应用程序,我们WINDOWS常见任务管理器查看进程,原本的可执行程序是在硬盘中的,我们点击或者命令行运行程序,程序必须进入到内存中

    线程

    线程是进程的一个执行单元,一个进程至少一个线程(可以有多个),多线程可以理解为如360可以同时杀毒,清理垃圾,系统漏洞扫描

    像AMD,Inter Corei7 4核8线程,4个cpu核,可以同时实现8个任务同时执行,我们点击360管家,就会将其进程刷入到内存中,当我们依次点击我们的任务,就会开启一条线程告诉cpu去执行我们的任务

    其中一个cpu核有2个线程通路,每个线程通路会对多线程任务轮流切换,好像同时执行一样

    线程调度

    分时调度:所有任务分时使用线程

    抢占调度:某任务占据线程(优先级高低),如果优先级相同会随机一个,Java使用的是抢占式调度

    1

    windows任务管理器可以右键查看设置进程(线程)级别

    单线程程序

    程序从main方法开始从上到下依次执行

    2

    我们定义Person类,定义构造getter,setter,然后定义run方法,循环20次,打印次数和名字

    3

    当然程序是自顶向下的,老大跑完老二才能跑

    从头到尾,main方法使内容进入栈,只有从上到下,就是一个线程,也叫主线程

    单线程有缺陷,就是主线程会因异常而终止运行,

    4

    如上,我们在老大老二之间添加了个人为异常,这时就会导致main程序终止,异常提示可以看到是main线程即主线程

    Thread类

    java.lang包下,不需要导包

    5

    文档告诉我们创建新线程有2种方法,其中一种是创建继承Thread的子类,然后重新run方法,通过start启动

    6

    如上,我们自定义线程类,重写run方法,当然父类super的run还是要保留,然后

    7

    然后我们main里自身有主线程循环,同时子线程也开启,可以看到抢占式的效果,谁抢到资源执行谁,当jvm导入main方法时,jvm为main向os请求开启通向cpu的线程路径,当子线程start又会开启一条通往cpu路径,2者属于抢占cpu资源,而cpu则是不受我们控制,哪个执行是其自身控制

    8

    我们需要通过内存图说明run和start的区别,我们调用main,把main压栈执行,创建线程实例,在堆内存开辟空间,这时调用其run方法,run进栈,但run是默认通过main执行的,所以其是单线程

    9

    而我们使用start方法,是会开辟新的栈空间,在里面运行run方法,而对于cpu执行哪个栈空间就有了选择方法,所以多线程有好处是每个线程运行在不同的栈空间,一个挂了,可以运行其他的

    Thread类常用方法

    获取线程名称

    获得当前的线程名称,有2种,一种是通过实例getName获得,另一种是其静态工具方法Thread.currentThread()获得,可以查看文档

    10 11

    上面是通过getName获得

    12 13

    如上,我们使用Thread.currentThread()也可以获取线程名称,这里其实是是默认用了toString方法,因为返回的是线程的引用

    设置线程名称

    void setName(String s)给线程设置名称

    14

    设置后,获取线程名称就可以看到我们的设置

    15 16

    还有一种设置是通过继承Thread类的带String字符串的构造方法,自带传入线程名称

    sleep(long millis)休眠方法(静态)

    使当前线程休眠特定毫秒,时间到后执行

    17

    我们知道for循环间隔是很短的,我们使用sleep达到1s一数数,现在我们设置1000毫秒就是1s,这里java9要求抛出打断异常,而视频里没有,由于IDE提示只有照做

    创建线程的另一种方法

    之前讲了通过Thread的一种创建线程方法,使用复写run,这次使用Runnable接口

    18 19

    Runnable接口,必须实现类定义run的无参方法,然后实现类对象传给Thread,创建线程对象,线程运行还是通过start方法

    20 21

    如上

    使用Runnable的实现类好处

    实现类继承接口还可以继承其他,而Thread重新run就麻烦,这是黑马老师BB的,然而我觉得一样,都得写run,就是Runnable只要用Thread就好,但是里面传的是实现类对象,而后者是创建自己的线程类

    匿名内部类实现创建线程

    之前讲解过匿名内部类,只需要实例一次,而且此实例作为参数传递,可使用,这里我们就可以Thread类传入匿名内部类实例

    22

    代码如上

    线程安全

    多线程访问共享数据是会有问题,如卖票,三个窗口都卖,开始都认为有满的票,但是第一个窗口被一下买了100张,后几个窗口应该就该提示买不到,否则出现了负数的票的问题

    我们先对共享数据设置非负,如下,循环控制票数减少,开启3个线程运行如下

    23 24

    查看运行结果。好像没什么问题,只是有的线程卖的快慢的问题

    25

    可是我们加上休眠时间,问题就出现了,ticket正常是不会重复的,但是运行过程中就有重复的值

    现在就需要解释下线程安全问题,当变成89时,另一个线程也是用ticket值,然而语句没运行完,没实现--,即2个89.其实全部打印数据还有很多问题

    线程同步

    为了防止出现上面的问题,需要进行线程同步,一共有3种同步方法,1同步代码块,2同步方法,3使用线程锁

    同步代码块

    格式 synchronized(锁对象){可能出现访问共享数据的代码}

    锁对象可以保证把多线程锁住,同一时间只有一个线程访问共享数据,而且多个线程的锁对象必须都是一个,锁对象可以是任意对象(比如Object)

    26

    如上,我们可以看到使用同步代码块,没有重复的票,而且也是1--100的值

    其原理是使用同步锁对象,运行到同步代码块时,会自动检测代码块是否有锁对象,有则获取运行,没有得到锁的线程就会变为阻塞状态直到有锁可以获取,之前占用同步代码块的线程执行完同步代码块,就会把锁归还,几个线程再次进行竞争抢锁,在同步代码块内会降低一点效率,但是安全

    同步方法

    我们可以给访问共享变量的代码抽象成一个函数,然后使用synchronized修饰他,

    格式    修饰符 synchronized 返回类型 函数名(参数列表){代码块}

    27

    如上,虽然我们是想象中的同步代码,其实也是实现了同步,但是出现了0和-1这种情况,这是因为跳出循环是ticket不大于0,而一个线程到0后,其他线程还在循环,会继续执行--操作

    28

    正确的方法是这种有while循环时,在同步函数里也定义条件,如上使用if

    其实同步方法也是有锁对象,我们可以通过打印验证

    29

    我们将之前的匿名类改成实现类,里面打印this,然后我们在实例时也打印实例

    30

    可以看到几个地址值都相同,当然这没什么说的,本来就是几个实例引用传给构造,只不过视频说拿来做锁对象,这里记住就行了

    31

    所以我们也可以把原来的同步函数写成普通函数加使用this做锁的同步代码块

    静态同步方法

    我们知道静态方法只能访问静态变量,而且方法一旦定义静态我们就不能使用实例了,他归于本类,如果我们static修饰了同步方法,那锁对象就不是this实例了,其是class本类文件对象

    32

    Lock锁接口

    Jdk1.5以后出现的,在java.util.concurrent.locks下,需要导包

    我们之前使用同步代码块无法知道什么时候具体获得锁和释放锁

    而其方法有获取锁lock()和释放锁unlock()可以看的比较明显

    因为是接口我们需要使用实现类实现,文档可以看到实现类ReentrantLock互斥锁

    我们可直接使用这个类实例,在可能出现共享变量访问之前使用获得锁,之后使用释放锁

    33

    这里还是要判断ticket是否大于0再去--,因为while限制不住

    34

    文档演示,要保证一定释放锁,否则造成死锁,要在finally使用unlock()

    35

    线程状态概述

    36

    我们查看文档,Thread.State属性返回线程状态

    37

    6种状态如上,我们新建的线程就是新建状态,当start后,就争抢cpu资源,抢到进入运行,没有的进入阻塞,当线程结束或stop终止或异常停止,就进入死亡状态,当线程sleep或者wait一定时间就进入到休眠状态,时间到了就查看cpu,有资源可用就运行,要么就进入阻塞,而等待状态,是使用Object对象的wait方法,想恢复到运行或者阻塞要使用Object.notify来唤醒,这里又把休眠和等待成为冻结状态

    等待唤醒案例

    等待分为计时等待,锁等待(也算是阻塞),还有wait无限等待,这里研究无限等待

    我们研究一个案例,顾客买包子,然后老板做包子,5s后包子做好

    38

    代码如上,这里需要使用ojbect的wait和notify,我们要给顾客和老板各开一个线程,而且他们以obj对象做1锁同步,顾客线程开始是靠obj.wait阻塞等待,然后老板线程的时间到,给obj对象唤醒,开始我想只开一个顾客线程,把老板放在主线程,且没有使用同步代码块,发现IDE提示模拟异常,还是老实的按视频操作吧

    计时唤醒

    sleep和wait可以加毫秒值,不同的是,wait可以在中间被notify,如果一直没有Notify,则时间到进入阻塞或者运行状态,notify如果多个线程wait则随机唤醒一个,想同时唤醒可以使用notifyAll

    线程间通信--等待唤醒机制

    多任务处理同一个资源,但是任务却不相同

    39 40 41

    之前的吃包子可以理解为通过包子的有无,分别进行操作,如上

    42

    如上,也是我们之前说了,必须要使用同一个锁对象来调用,而且必须使用同步代码块或者同步函数

    我们继续改之前的案例,这里实现包子铺一个线程,顾客3个线程都吃包子,一个人吃3个,1s做一个,

    43

    如上,我们定义包子线程,使用num同步,其是通过构造方法传递过来,为了2个对象都能使用同一个对象,将这个对象设为一个元素的num数组,

    44

    如上,我们定义顾客线程,给顾客起好名字,同样传入num数组地址,然后包子数量大于3的时候就去抢包子,此部分被同步代码包裹,

    45

    main方法,创建数组,初始化值为0,然后将其传给几个线程,运行如上

    线程池

    46

    我们使用线程实例创建,但是我们数量多时,而且每个线程很短时间就结束,频繁创建线程降低效率,因为线程创建销毁都要消耗时间,今天就来了解下线程池

    47

    线程池就是存放线程对象的容器集合(队列),使用LinkedList或者ArrayList,删除返回被移除元素,然后这个对象去run,结束后,将其添加到集合末尾,先入先出,jdk1.5以后有内置线程池,我们直接使用即可

    48

    如上,我们某个任务由5个子任务组成,我们线程池容量为3,前三个任务进来,被3个线程接走,而4,5任务只有等待其他3个线程运行完返回线程池,才能被接收执行

    在java.util.cocurrent下。有Executors类,其一个方法newFixedThreadPool可以返回可复用线程池

    49

    工厂类返回的是ExecutorService线程池接口对象,

    50

    我们比较关注得到是shutdown方法和submit方法,分别用于关闭线程池,不接受新任务,和执行线程的start

    线程池使用步骤:1使用Executors.newFixedThreadPool()生产一个指定容量的线程池

    2创建runnable实现类,重新run方法

    3调用1步返回的线程池的submit,传递实现类,执行

    4调用shutdown关闭线程池,不再接收新的任务(不建议这步?。。视频里这么说)

    51

    如上,我们使用线程池接口实现3个线程得到线程池,然后使用其执行5个任务,这时我们打印线程名字,可以看到,1线程执行了3次任务,2,3线程执行了1次

    相关文章

      网友评论

          本文标题:30 多任务

          本文链接:https://www.haomeiwen.com/subject/syywehtx.html