blog 2019.3.21 Day21 多线程

在这里插入图片描述

windows 即是多线程 又是多进程

多线程学习

理解进程和线程

进程:运行中的应用程序称为进程,进程的特点是进程拥有(占用)Cpu和内存资源。 当某一个程序没有任务执行,是空闲的 则在cpu内会显示00
在这里插入图片描述

线程:线程是进程中的一段代码,一个进程可以有多段代码,(一个进程包含多个线程),线程本身是不拥有资源的。共享所有进程的资源。

多进程:在操作系统中能同时运行多个任务(qq,eclipse)
多线程:在同一个应用程序中,有多个顺序流同时执行(在QQ里面既可以打电话,同时还可以发消息,传输文件)
在这里插入图片描述

开启这个程序的时候,在操作系统里面产生一个进程,程序是main方法开始的,当执行main方法的时候在当前进程里面开启了一个线程(主线程),main调用method1方法,main进入到了method1继续执行代码,main里面的其他代码无法马上执行,必须等待method1执行完了再回来执行main的输出语句。

整个程序的流程从开始到结束都只有一个线程,一条线下来的,被称为单线程程序。执行路径只有一条。

单线程线程:一个任务完了继续后面任务,不能同时执行,所花的时间是所有任务分别花的时间加在一起。

进程在何时产生?整段代码和内存的关系是?

  1. 把应用程序代码放在内存中的代码区,代码放在方法区中并没有马上执行的,但是这个时候已经说明了一个进程正在准备开始。进程产生但是没有运行,所以说进程其实是一个静态的概念
  2. 进程在哪个时候执行? 进程的执行指的是当前的进程里面的主线程开始执行的时候。也就是main方法的执行。在机器上运行任务的全都是线程。

windows操作系统是一个多进程的系统,同时可以执行很多的线程。Linux、unix,这些也都是多线程系统。
Dos(命令行)是单线程系统、执行完上面一句才能敲下一句。

思考?多线程表示多个任务同时执行,那么CPU如何分配资源的?

Cpu的运行速度非常快,大概一秒钟可以进行几亿次的运算,所以将cpu的运行时间分成一个一个的小片段,每个任务都可以在一个时间片段里面执行一次,如果在当前片段里面没有执行完,会在其他任务执行完了继续执行。因为速度很快,所以几乎感觉不到排队的现象,给我们的感觉是在同时执行。
实际上,在一个时间节点上,只有一个任务在执行

如果你的电脑是双CPU、多核,实现真正的多线程。

java是如何去创建线程的?

java中有很多种方案创建多线程
一共有四种:

  1. 继承java.lang.Thread类,当前类就表示多线程
  2. 实现Runable接口,也可以表示多线程类
  3. 实现Callable接口,也可以实现多线程。
  4. 线程池

一般问到的都是前面两种。


thread的用法

使用Thread类的时候,必须重写run方法。
在这里插入图片描述
然后执行main方法。
在这里插入图片描述

任何一个线程的主线程都是自动产生的,这上面的t。start 是用户产生的,所以一共有两个线程


在这里插入图片描述

通过t.start() 启动了两个线程 两个线程是同时运行的
而t。run 只能启动单线程,变成了一个线程从上而下执行

只要编译器发现了start关键字,无论是否有线程在执行,马上给start开辟一个新的线程

在这里插入图片描述


Runnable

在这里插入图片描述

为什么有start没有开辟线程?因为主线程在执行的时候进入到循环里面了,所以都还没有开启子线程,主线程一直再循环,所以成了“单线程”一样。

所以start 要放在主线程 里面最前面
在这里插入图片描述

若有两个start 两个线程,如何区分哪个是哪个?
构造一个方法
在这里插入图片描述
在这里插入图片描述


Thread.currentThread()可以获取到当前执行的线程。通过它获取线程的信息(名字、id、其他 )

为什么启动线程start,为什么调用它而不是调用run?

api中提出了start才是启动线程的方法,run是线程体,直接调用相当于直接调用方法。就不会产生多线程程序了。
而调用start,首先会将线程放入到线程组(线程队列)里,线程队列有个特点,先进先出。 接下来会调用本地方法Strat0,通知虚拟机来执行线程,虚拟机默认会调用线程的run方法来执行。所以一定要执行start方法。


注意 继承Thread类,可以直接Thread t = new Thread ,直接调用Thread的方法。

在这里插入图片描述
在这里插入图片描述

如果是实现Runnable, 需要在下面声明一个Thread类, 并把Runtime类new的对象传进去。

在这里插入图片描述
在这里插入图片描述

在设计的过程中,尽量使用Runnable 因为java是单继承,多接口


线程优先级。

《默认的优先级是1-10
主线程的优先级默认是5
用户可以自己去设置优先级,优先级越大,它获得的cpu运行的权力可能会更多。》

java提供了一个线程调度器来监控程序中启动后进入就绪状态(可以被调用的)的线程。线程调度器会根据线程优先级来判断先调度哪个线程执行。

线程优先级我们用数字1-10来表示。值越大优先级越高,默认的缺省值为5(main主线程默认就是5)程序的运行结果还是由虚拟机来安排,无法预知运行后的一个结果,线程优先级的高低只是一个参考值,相对的结果,理论上具有高优先权的程序占有更多的cpu使用权,但是有时候并不是这样的结果。
在这里插入图片描述

setPriority() 设置优先级
getPriority() 获取优先级

线程的生命周期

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

五种状态

  1. 新建
  2. 就绪
  3. 运行
  4. 阻塞
  5. 死亡

阻塞又分为三种
6. 等待阻塞
7. 同步阻塞
8. 其他阻塞

其他阻塞包括:
sleep()    \join()     \IO操作
线程主动让出执行权:yield()

三种其他阻塞详解

Sleep

sleep:Thread.sleep() sleep属于Thread静态方法,单位是毫秒,一旦调用了sleep,当前线程处于睡眠状态,当睡眠时间过去后,马上恢复到就绪状态。此方法会造成线程阻塞。

在main方法里面写
在这里插入图片描述
休眠的是主线程。

若想让某一个子线程休眠,就在这个子线程对应的方法内写这个休眠的代码,而不是要在main里面写
在这里插入图片描述


让哪个线程休眠,写到哪个类里、。
在这里插入图片描述

若想在main方法里面打断休眠 两种方法。

  1. 休眠体有循环的情况下,可以设置子线程循环为false
    在这里插入图片描述 在这里插入图片描述

2.通过interrupt方法

在这里插入图片描述


join阻塞

join:合并某个线程,如果说有两个线程,一个是A一个是B,在运行过程中,A必须等待B运行完了才能继续往下运行,我们可以调用join来完成这项工作,把两个线程合并起来。合并过后就相当于以前直接调用方法了,而不再是开线程了。
在这里插入图片描述
在这里插入图片描述
只要调用join 那么就会将子线程合并到主线程内,按顺序输出,先执行子线程,执行完了执行主线程,相当于方法的调用。

并发运行的过程中,如果我想把某个任务运行完了再执行其他任务 那就用join。


输入阻塞(IO阻塞)

IO阻塞,让出cpu
在这里插入图片描述


yield

yield:在多线程运行过程中,当前抢到cpu资源的线程想让出使用权,调用yield方法马上就让出使用权,变成就绪状态,但是还可能被jvm分配使用权。在实际开发中,无法保证yield让步

如果j对2取余等于0(数字为偶数),让出资源。
在这里插入图片描述
在这里插入图片描述
虽然把资源让出来了,并不一定给别人运行,它可能又回来抢占了资源(因为大家都处于公平竞争,都是就绪状态)
在这里插入图片描述


同步阻塞:
等待阻塞:

线程的同步

什么是同步

多个线程同时运行,有时候线程之间要共享数据,一个线程需要其他线程的数据,否则就不能保证程序的运行结构。

eg:余票为1,我抢了一张票,会把剩余0这个结果传给下个线程(下个线程需要这个线程的数据),保证了程序的运行结构,这时候其他人就不能抢票了

并发:同一个时间点,多个线程访问同一个资源。
共享资源(临界资源):多个线程共享数据 称为 共享资源,或者说称为临界资源。

线程同步:同步就是协同步调的意思,按照预先的先后顺序进行执行,先抢到资源的线程先执行,后抢到资源的线程后执行,两个线程相互配合,共享数据。

比如余票为1,我抢了一张票,会把剩余0这个结果传给下个线程(下个线程需要这个线程的数据),保证了程序的运行结构,这时候其他人就不能抢票了
同步:我在操作这个资源的时候,你比如要等我操作完把结果反馈给你之后你才能接着操作。

在java中要实现多线程同步的方法 最常见的三种:

  1. 同步块
  2. 同步方法
  3. volatile方法

同步块:

相当于给程序加了一把锁。使用关键字修饰语句块的时候,相当于自动加上内置锁,一旦我把他锁住,其他线程就无法访问了,这样就实现了同步操作。
同步快相当于自动加上内置锁,实现同步操作,局部区域代码块锁。

老师的例子:TestTicket.java

语法:

Synchronized(){
    //代码只能在同一时间由一个线程访问
}

在这里插入图片描述
另一种同步方法:
在方法访问修饰符后加上synchronized

public synchronized void add(){} 表示整个方法都同步

在这里插入图片描述

有时候只需要同步局部数据,不需要同步整个方法块,可以用第一个。

内置锁

在java中,每个对象都会有一个内置锁,当你使用关键字synchronized 时候,内置锁会保护我们整个方法,在调用方法前需要获取到内置锁,否则就处于等待状态。获取到内置锁了,就可以执行锁里面的内容了,其他线程就等待,谁获得了内置锁,谁就运行锁内的代码,大家都在抢这个锁,谁抢到谁用,剩余人等待。而且用完的人要把数据共享给其他人

反正遇到了共享数据,大家运行的时候可能要产生冲突的时候就加一个synchronized,但是效率会变慢

volatile关键字

给局部变量的访问提供了一种免锁机制,一个变量使用它来修饰告诉虚拟机该域可能会被其他线程更新,所以每次使用当前值的时候,都要重新计算一下,默认的值保存在寄存器中。所以说线程互斥的情况下,它就会直接取寄存器的数据。volatile不会提供任何原子操作,也不能用final来修饰
在这里插入图片描述
在这里插入图片描述

线程互斥:多个线程在共享某一个变量,而且都对变量有修改。如果不考虑在运行过程中相互协调的问题,就会出现数据不一致,数据安全问题。

比如余票为1 你抢了一张 我也能抢一张,这时候就出问题 了。

作业:
在这里插入图片描述

3.22日补充:

死锁问题

一般在程序中要避免死锁。一旦遇到死锁,很难定位问题的位置。
死锁的概念:主要指两个或两个以上的线程,因为争夺资源而造成的一种相互等待的现象,大家都在争资源。
死锁若发生了,若没有外部处理的话,线程将面对无限期等待的问题。(大家相互都把对象锁起来,我锁1你锁1,我锁2你也锁2,我想用1的时候发现1也被别人锁了我没法用,别人想用2但是被我锁了他也用不了。)

造成死锁的原因:

  1. 系统资源不足
  2. 进程的推进顺序有问题
  3. 资源分配不当

死锁的案例:

系统中有两个对象 A/B
线程1:对A加锁 (synchronized A,这时候A只能线程1 用,没有释放锁之前其他人都不能访问)------对B加锁------执行-----释放b------释放a
线程2:(线程1对A加锁的时候)对b加锁------------对A加锁 ------执行-----释放A-------释放B

死锁。线程1对a加锁没问题,但是对b加锁的时候发现b已经被别人加锁了,他想用B必须等待。线程2对b加锁没问题,但是对a加锁的时候发现对象a已经被加锁了, 没法往下执行了必须等待,这样就形成了一个死锁。


代码:TestMain

线程数据共享(了解)

在java中,可以用wait notify notifyall 完成线程之间的通信。在开发中最常见的例子就是生产者和消费者的问题,生产者生产铲平通知消费者去消费产品。生产者将产品放入到队列里面,消费者就可以从队列里面获取数据,如果没有数据那就等待。

wait、notift、notifyall

在这里插入图片描述
都属于Object对象提供,主要用来控制线程的状态。
wait和sleep不一样,sleep到时间就自动恢复了,而wait必须用notift、notifyall来唤醒,而且一唤醒马上就能抢占到cpu资源。

wait

该方法是用来将一个线程置入休眠的一个方法,休眠过后不会自己恢复,必须等待调用notift或者notifyall唤醒,当前线程才能被唤醒。

调用wait的时候,线程必须要获取到对象的对象级别锁。(wait必须在synchronized 中用。)使用wait的时候,wait必须在synchronized 中间调用,在线程中执行wait时,这个线程必须已经获取了线程对象锁了。

一旦调用了wait,当前线程就处于休眠状态,当前线程锁就释放,不再占用对象锁了,其他线程可以来抢资源,进入到这个锁内了

notify

用于唤醒在此对象监视器下面等待的单个线程。(也就是说notify唤醒 使用了wait休眠的线程,操作本对象的线程),如果有多个线程在此对象上等待,会随机唤醒一个线程,并等待获取对象的对象锁(等待获取的意思,表示当前就算收到了通知,wait线程也不会马上获取到对象锁,它得等notify唤醒的线程执行完了才能获取对象锁)

notifyall

唤醒当前对象上面阻塞的所有线程,唤醒的时候顺序是随机的,要注意的地方的是在使用notifyall中也必须在synchronized 里面完成。


思考
  1. 总结一下 sleep和wait的区别
  2. 总结一下notify和notifyall的区别
  3. 说一下哪个时候用sleep哪个时候用wait

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值