线程----入门级教程

进程:操作系统资源分配的基本单位

  1. 线程是依赖于进程的,一个进程里面包含多个线程(每个应用程序就是一个进程)
  2. 进程是对应的应用程序,每个进程都有自己的内存空间
  3. 进程是正在运行的程序

线程:任务调度和执行的基本单位

  1. 是进程中单个顺序控制流
    【线程是程序中的一个执行流,每个线程都有自己的专有寄存器(栈指针、程序计数器等),但代码区是共享的,即不同的线程可以执行同样的函数。】
  2. 一个进程如果有一条执行路劲,称为单线程程序
  3. 一个进程有多条执行路劲,称为多线程程序

如果是多核CPU的话,那么可以在物理上真实地做到同一时间做多个线程;而如果是单核,那么即使是多线程,一个时刻也只能做一个线程。
BUT,在单核的前提下,假如有10个任务。如果用单线程程序,那么我可以依次做这10个任务-----做完地一个做第二个做完第二个做第三个…而如果用多线程的话,等效于有一个任务队列,我run了这10个任务线程等于把这10个线程都插入了这个任务队列中,然后这10个任务去抢CPU。感觉上有点异步的味道:我不必等一个任务完成了才能执行第二个、我不必等这10个任务都完成了才能继续往下走。
在这里插入图片描述
上图链接:https://www.jianshu.com/p/5d273e4e3cbb

多线程程序的优点:
在编程的时候,可以把费时间的任务移到后台,在前台用另一个线程接受用户的输入。
例如:在编写串口程序时可以在后台传输文件,仍然可以在界面操作,就不用等文件传输后再操作界面。大大节约了时间。在编写上位机程序时可以在后头一直接收测量数据应答帧,仍然可以在界面操作模拟数据发送,就不用担心只能模拟数据或者只能接收数据。
一、线程基础语法

如何初始化线程:【主要分为两类】

  1. Thread方式:子类继承并去实现Thread的run方法
  2. Runnable方式:子类实现接口的run方法

Runnable接口比Thread类的优点
1.避免JAVA单继承的局限性
2.适合多个相同的程序代码【比如买票卖票】去处理同一个资源的情况,【如果是thread的话就只能new多个对象,而runnable的话则是对同一个对象的资源进行操作】
3.把线程和程序的代码有效分离,较好体现了面向对象的设计思想

设置和获取线程名称:
void setName(String name):设置线程名称
String getName():得到线程名称

线程方法

1.static void sleep()

在这里插入图片描述
注意点:sleep只是线程阻塞,但不让出锁。wait方法线程阻塞,让出锁
2.void join()

thread.Join把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行的线程。比如在线程B中调用了线程A的Join()方法,直到线程A执行完毕后,才会继续执行线程B。
要先调用线程的start方法之后才能调用join()方法
在这里插入图片描述
刚开始时,只有主线程在使用CPU的执行权,因为其他两个线程还没有被创建,这时主线程的代码就自上而下的去执行。
假如子线程在主线程的最后才被start的话,当主线程的内容执行完毕后,就开始创建并启动其他的线程,因此我们会看到子线程在轮流抢占CPU的执行权来输出他们各自的内容。
解决办法:
把主线程的内容放到子线程启动之后去执行,就会出现三个线程轮流抢占CPU执行权的情况。
在这里插入图片描述
在这里插入图片描述

3.setDeamon(守护线程)

当运行的线程都是守护线程时,java虚拟机退出
如何设置守护线程

thread = new Thread(this);
thread.setDaemon(true);【必须在start之前设置守护线程】
thread.start();

1.首先我们必须清楚的认识到java的线程分为两类: 用户线程和daemon线程

A.  用户线程: 用户线程可以简单的理解为用户定义的线程,当然包括main线程(以前我错误的认为main线程也是一个daemon线程,但是慢慢的发现原来main线程不是,因为如果我再main线程中创建一个用户线程,并且打出日志,我们会发现这样一个问题,main线程运行结束了,但是我们的线程任然在运行).

B.  daemon线程: daemon线程是为我们创建的用户线程提供服务的线程,比如说jvm的GC等等,这样的线程有一个非常明显的特征: 当用户线程运行结束的时候,daemon线程将会自动退出.(由此我们可以推出下面关于daemon线程的几条基本特点)

当java虚拟机中没有非守护线程在运行的时候,java虚拟机会关闭。当所有常规线程运行完毕以后,守护线程不管运行到哪里,虚拟机都会退出运行。所以你的守护线程最好不要写一些会影响程序的业务逻辑。否则无法预料程序到底 会出现什么问题。

4.suspend

挂起(suspend)和继续执行(resume)线程【不推荐使用】
– suspend()不会释放锁
– 如果加锁发生在resume()之前 ,则死锁发生
在这里插入图片描述
5.yield

让出cpu资源,当前线程从运行状态进入就绪状态
notify和wait方法放在锁之后讲
二、线程生命周期

线程的生命周期包含5个阶段,包括:新建、就绪、运行、阻塞、销毁。

就绪状态start和运行状态run
如果直接用Thread调用run()方法的话,相当于在主线程里直接调用一个普通的方法(Thread重写Runnable里面的run()方法)。因此要按照顺序执行,即在主线程里先执行前一个Thread类的run()方法直到结束,再执行后一个类的方法。
如果调用start方法的话,才是真正的启动线程,实现多线程,start使线程进入就绪状态,并没有真正的执行。多线程是利用cpu的时间片来达到并发的效果,直接调用run()就不会有并发的效果
线程调度:
1.分时调度模型:
所有线程轮流使用cpu,平均分配每个线程占用cpu时间。
2.抢占调度模型
优先让优先级高的线程是使用cpu,如果线程的优先级相同,那么会随机选择一个线程进行运行,优先级高的线程获得cpu时间【相对】多一些。还是存在随机性的。
Java使用的是抢占调度模型,线程默认的优先级别是5
thread.getpriority
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
线程chi
在这里插入图片描述
三、线程同步

线程同步要解决的问题(数据安全的问题)

  1. 是否是多线程环境
  2. 是否有共享数据
  3. 是否有多条语句操作共享数据

在一个线程还没完成的时候,其他线程可能来抢你的CPU资源。这个就需要锁
如果多个线程抢夺资源,我们要考虑线程锁
锁的作用:当某个线程执行完一段程序后,然后再另一个线程执行

JAVA 的锁机制说明:每个对象都有一个锁,并且是唯一的。假设分配的一个对象空间,里面有多个方法,相当于空间里面有多个小房间,如果我们把所有的小房间都加锁,因为这个对象只有一把钥匙,因此同一时间只能有一个人打开一个小房间,然后用完了还回去,再由JVM 去分配下一个获得钥匙的人。

锁(同步加锁)的语法
第一种语法:语句块加锁
在这里插入图片描述
在这里插入图片描述尽量用这种方法,把公共资源写成类,便于利用对象
同步方法(同步方法锁的是当前对象this)
第二种语法:方法加锁
在这里插入图片描述
锁分为三类:
类锁【 class,针对静态方法】
对象锁【同步方法必须要用同一个对象锁】
私有锁

在这里插入图片描述
在这里插入图片描述
notify和wait

wait既释放锁又释放cpu。如果wait后面直接跟notify【没有任何的假设条件】。那么notify将不会被运行,因为线程执行到wait时,线程阻塞,将不会执行下面的notify。那么另一个线程也没有被唤醒。
notify并不释放锁,只是告诉调用过wait方法的线程可以去参与获得锁的竞争了,但不是马上得到锁,因为锁还在别人手里,别人还没释放。如果notify方法后面的代码还有很多,需要这些代码执行完后才会释放锁,可以在notfiy方法后增加一个等待和一些代码,看看效果),调用wait方法的线程就会解除wait状态和程序可以再次得到锁后继续向下运行。
notify是唤醒一个线程,notifyAll是唤醒全部线程。

一般什么时候用到notify和wait?什么时候用到thread.sleep
当遇到下列题时,我们考虑用到notify和wait
1.编写一个程序,开启3个线程,这3个线程的ID分别为A、B、C,每个线程将自己的ID在屏幕上打印10遍,要求输出结果必须按ABC的顺序显示;如:ABCABC….依次递推。
2.线程A输出1-52,线程B输出A-Z,最后的输出结果是:12A34B56C…。

1题看到就会想到有3个线程。每个线程分别输出A、B、C。分别都输出10次。
因此一定会用到wait。当数字位在14710时A线程输出A,其余数字位时A线程阻塞(index-1)%30
当数字位在25811时B线程输出B,其数字位时B线程阻塞(index-2)%3
0
当数字位余3等于0时C线程输出C,其余数字位C线程阻塞
由上述分析可以看出,输出ABC的方法共用了一个数字位资源。因为需要建立一个类PrintABC【为了共用数字位资源】.建立一个A类B类C类分别继承Runnable方法。再用一个Test类,建立三个线程对象,传入同一个PintABC对象

2.题看到就会想到有2个线程,一个线程输出数字,一个线程输出字母,分别调用52次和26次。
输出12等B输出34等C。因此一定会用到wait。当数字位余3等于0时输出字母,其余数字位线程阻塞
当数字位余3不等于0时输出数字,其余数字位线程阻塞
由上述分析可以看出,输出数字和字母方法共用了一个数字位资源。因为需要建立一个类Printer【为了共用数字位资源】.建立一个PrintNumber和PrintChar分别继承Runnable方法。再用一个Test类,建立两个线程对象,传入同一个Printer对象

2.模拟多个人通过一个山洞:这个山洞每次只能通过一个人,每个人通过山洞的时间为5秒,随机生成10个人,同时准备过此山洞,显示一下每次通过山洞人的姓名。

这种题不存在等待问题。随机生成10个人,就可以看做随机生成10个线程,每个线程执行过山洞方法一次。由于同时准备过山洞。因此都处于就绪状态。谁过了谁就拿到锁

在这里插入图片描述
拿到题时首先要分析:

几个线程
每个线程执行方法多少次
几个类
哪些方法有共享资源的

四、多线程开发

多个线程对象去执行一个【多线程程序】(Runable的好处就是多个相同de程序代码对同一资源进行操作)
并不是多个线程去执行程序

为什么需要多线程实现群聊和私聊?
当多客户端和服务器沟通时,就需要多线程同时进行了,不然就必须要一个客户端执行完方法再执行下一个客户端
https://blog.csdn.net/qiukui111/article/details/104487411
服务器必须要给出线程代理socket

服务器的多线程在于每个线程要接手单独的一个客户端的流,并进行转发
客户端的多线程在于,有很多个客户端。由于要从控制台输入信息,就不能通过new的方法,只能run很多个窗口【每个窗口都是一个线程】。

五、生产者与消费者

在这里插入图片描述
1.操作的共有资源是什么?
面包---------买面包和卖面包操作的都是共有资源【所有师傅做的面包所有消费者买的都是面包】。因此面包个数是static的。买和卖的方法放在一个类里面。并且买和卖方法加锁(想象一个放面包的地方,拿和放不是同时进行的,所以加锁)
然后创造以上的4个类
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
判断题
在这里插入图片描述
1.编写一个程序,用线程。要求输出结果必须按ABC的顺序显示;如:ABCABC….依次递推不能用到notify和wait
使用sleep(有助于时间片的理解)【但是有风险,需要协调好sleep的时间和run方法里代码块的时间】
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值