Bootstrap

【JavaEE初阶 — 多线程】认识线程

 83fa83208d714018ac75190a40c156e9.gif

目录

认识线程(Thread)

1 线程是什么?

2 为什么要有线程

3 进程和线程的区别

区别一

区别二

区别三

区别四

4. Java的线程和操作系统线程的关系

5.创建第一个多线程程序

引入Thread类

重写run()

start()与run()区别

降低多线程对CPU的占用率

 处理sleep()异常

线程的随机调度

6.使用 jconsole 命令观察线程


 


 

认识线程(Thread)


1 线程是什么?


  • 一个线程就是一个 "执行流"。
  • 每个线程之间都可以按照顺序执行自己的代码. 多个线程之间 "同时" 执行着多份代码。
  • 将一个大任务分解成不同小任务,交给不同执行流分别排队执行。

2 为什么要有线程


 

d9504f19322e4438aee1867ddfe7879b.png

 

但是在服务器开发场景上,一般情况下,客户端,服务器都在一台计算机上;但是我们可以把客户端和服务器分别放在两台电脑上。

此时我们可以通过网络的方式,远程访问mysql服务器,这也是以后工作中,最典型的一种场景

c1743ce267254d2f9c4a49e41af02fa6.png

在这种服务器的情况下,涉及一个非常关键的问题,一个服务器程序,同一时刻,是需要给多个客户端提供服务的:

8c7759f73a7148eab196b0caab64c151.png

客户端按照并发的方式,发送请求到服务器,服务器就要能对这些请求进行处理。怎么处理?

9f1dd611a0484e6fac5369f0629d90cf.png


68a91a9627c549ecae3974f0cbccc59e.png


 1a368f450e2a44318ce8ec09fdf11b86.png

dffef7adb3e446c4b0759b5f9f7ae53e.png

ead7f13283bd4584b5f2f08919f3cfef.png

094defad4842468a9dba7948092146b7.png

af36bc4ad9e143ad9f23fe6a1b77d148.png

 

总结:

引入线程,也可以解决并发编程,提高效率,同时节省开销。

但是,线程不是引入越多越好,适量的引用可以提高效率,引入太多,会因为线程调度的开销过大,反而拖慢程序的性能。

多个线程共享同一份资源,可能会产生冲突,导致线程安全问题,甚至进一步的,如果某一个线程抛出异常没有得到及时的处理,可能会带走整个进程。


3 进程和线程的区别


e46ecb9d68ef49dbb17affda9f77cbae.png

 

区别一

进程是包含线程的. 每个进程至少有一个线程存在,即主线程。

cf42a9d65c354d95bb6080f5ea4c4293.png

259a8154370042e4a68e612f21bbd7eb.png

windows 的任务管理器中,我们无法看到进程内部的线程。需要借助一些其他调试工具(VS的调试器,Windbg.....),才可以看到每个进程中的线程


区别二

进程和进程之间不共享内存空间. 同一个进程的线程之间共享同一个内存空间

3670ca429f4b47ab9a644665406998ab.png

区别三

进程是系统分配资源的最小单位,线程是系统调度的最小单位。

af3c3951c8ab473291dc9393ca5ed56a.png

为什么不能说线程共享CPU资源呢?

6d76f357a92d4877bf6b114912dafa1d.png

区别四


一个进程挂了一般不会影响到其他进程. 但是一个线程挂了, 可能把同进程内的其他线程一起带走(整个进程崩溃).

d2a50a2ac8164cf6943a30eda7275db1.png

进程与进程之间被虚线隔开,互不影响;但是在同一个进程内的多个线程,可以彼此影响。如果一个线程出现异常,当异常没有被及时捕获,而提交给JVM时,当前线程被迫终止;甚至影响多个线程,整个进程都会崩溃:

54ab441ebf244392a4e9d71499a1adc6.png

 

这就好比刚刚的滑稽老师吃🐔,🐔是要处理的问题。而滑稽老师扮演线程,桌子扮演进程。

对于桌子上的十只🐔,适量的请几位滑稽老师来解决即可。

请的滑稽老师太多了,会因为大家都得吃🐔,又不得不彼此谦让,最后导致吃🐔效率降低。

因为线程引入过多,导致不但线程的调度开销变大,还是得效率降低。

不同桌子的🐔,由相应的滑稽老师们解决,桌子与桌子之间互不影响。

进程与进程之间资源独立,互不打扰。

线程与线程共享内存资源。

一个桌子的两个或者多个滑稽老师,抢同一只🐔,可能导致其中一个滑稽老师红温,掀桌子,导致这个桌子的滑稽老师都吃不到🐔了。

如果提前发现红温,想要掀桌子的滑稽老师,并及时劝阻,大家又能继续一起吃🐔。


4. Java的线程和操作系统线程的关系

线程是操作系统中的概念。操作系统内核实现了线程这样的机制, 并且对用户层提供了一些 API, 供用户使用(例如 Linux 的 pthread 库)


5c9754c3d5844139a8934bfb81b616ff.png


通俗地讲,API就是,Java的开发大佬写了一些类或者函数,我们拿过来直接用即可。如:

2a00d2587b554ef49cae8560fcde03d1.png


Java 标准库中 Thread 类可以视为是对操作系统提供的 API 进行了进一步的抽象和封装.

64f5b0b0a5a64399b92f351826704e2e.png


5.创建第一个多线程程序


• 每个线程都是一个独立的执行流

• 多个线程之间(在一个进程内)是 "并发" 执行的


引入Thread类


引入Thread类,创建除主线程(main)外的另一个线程 t 。 

18b76b7456a04de1b18e1c0082c45ff4.png

在用Thread类的引用时,是不需要import任何包的, 因为Thread类,这个由标准库提供的API,是在java.lang文件目录底下的,idea在创建新项目时,默认已经导入了java.lang的包

5728e7693441434497291a8086894fe4.png

重写run()


我们在MyThread类中,重写父类Thread中的run方法,用自己的逻辑,替代Thread类中,run()原有的逻辑:

1174ba8d94544f30a24d42887849fa61.png

通过两个while循环,我们可以直观的感受到多线程和单线程的区别 

968ed362d23f41b589dbd3f748e55fd9.png


start()与run()区别


5c85a803b1d045e096fe632f565ba3ae.png

在完善好当前多线程程序后,屏蔽t.start(),调用t.run();对于后续的打印结果进行分析:

5b2f1fe48eba450dac5e12ee2bdeb529.png

此时,run()相当于回调函数,我们不需要关心 run() 方法的调用,线程建好后,JVM会负责调用run() 。还学过的回调函数有,优先级队列。我们只需要为PriorityQueue传入比较器Comparator,只需要把比较的逻辑,写入比较器中的方法即可,不需要关心比较器里面的方法什么时候被调用。


降低多线程对CPU的占用率


此时,main 线程 和 t 线程 并发执行,会比单线程运行效率更高,CPU会以更高的频率(功率)工作,产生更多的热量;

此时CPU为了散热,会降低运行频率,从而CPU运行效率降低。

fa00a436c7004c389fad3c733722760d.png

在两个while循环中,我们调用Thread类提供的一个静态方法sleep(),参数为毫秒;

调用类的静态属性,不需要通过类名实例化一个对象,再通过对象的引用调用属性;而是直接通过类名调用静态属性;

而通过Thread类,两次调用sleep(),可以让当前线程暂时放弃CPU,降低CPU占用率。


 处理sleep()异常


再来分析两处地方,对sleep()进行调用报错的原因。

8d5258d61df3440aaed0167914065d33.png

报错原因,是因为我们并没有捕获异常

d6bc7bcd32474fb2865905404fe66a59.png


对于这两处关于调用sleep()的报错:

5784d862cdbc4e2c966cb2d7f70daca1.png

第一处报错的解决方案,只有通过try...catch来捕获异常,而不是在run()后面throw异常;

因为run(),是Thread类的run()的重写方法;在Thread类中的run,并没有在方法名后throw异常;

2e02550a9f4a4c0489fbbc0a31d8638a.png

重写的方法的方法名,方法参数,抛出的异常都得和父类中的一致,因为这些限制,我们不能在run() 后 throws 异常,但是main方法可以。

4eda08506f194da197109b93981002fb.png

但是,上述两种处理sleep()异常的方式,并没有对异常本身的问题进行任何的处理,在实际开发中,我们处理异常的方式如下:

f15cce542f3c4bd2ace480be7ea5fe67.png


线程的随机调度


观察程序运行结果,对于先打印“hello thread”还是先打印“hello main ”,并不是一定的:

828dbddb570d4325a48e14cc506e5463.png


6.使用 jconsole 命令观察线程


6ca91b2b6bd64d9fa597146a8e5032ff.png

可以借助这个工具,查看这个程序有多少个线程;

因为一个进程中,如果线程多了,可能会对程序运行产生一定的影响。因为线程调度过多,会消耗更多资源。

我们通过这个图,查看某一个时间的线程是不是特别多的问题:

db420327d43c4880ba8cb529d9112bc2.png

这就是我们看到的线程的详细信息,主要还是因为当前代码比较简单,也没有复杂的对应关系,所以看起来也没有太多的信息,但是实际上这些信息非常关键;

因为在实际工作中,我们涉及到线程会有很多个,而且里面线程的对应关系也可能很复杂。

所以当我们去排查一些问题的时候,首当其冲的,就是要先去查看,这个进程里面有什么线程,以及每个线程正在干什么。

这些都是我们排查问题,找出bug的重要依据。

9390068cdabd40689af596ff5430ab4c.png

 3c40b8f148d740e9b13ab2bca71824ec.gif

 

 

 

 

;