首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

Java多线程基本概念

马上就要放假了,兄弟们国庆玩的开心啊,虽然马上就要放假了,但是不能妨碍推文啊哈哈哈,今天的车是多线程的基本概念!为下来的并发编程做准备!上车!

Java程序就是天生的多线程程序,所以学好Java,就必须得学多线程,在Android中多线程用的还是很多的,今天就一起了解一些多线程的基本概念,和线程的生命周期

1. 基本概念

1.1 CPU核心数和线程数的关系

1.1.1 CPU核心数:

在计算机里,核心就是指的是处理器

我们去买电脑的的时候,不论买的是台式的还是笔记本,还是手机,都会关注这个Cpu是几核的

对吧,看我的电脑就是6个内核,也就是六个处理器,在计算机的早期,cpu是没有多核概念的,是单核的,后来为什么出现了多核呢?

多核就是因为按照摩尔定律,计算机的芯片里的晶体管的密度,会每18个月会翻一番,翻到了如今这个地步,cpu里的自存会到3nm,到3nm就会翻不动了,为啥翻不动了,我也不知道,牵扯到量子隧穿这个东西了,为了继续提高运算速度,才提出了多核心的概念,在 一块物理芯片上面,我们集成多个物理处理器(CMP),CMP是由美国斯坦福大学提出的,单芯片多处理器,也指多核心其思想是将大规模并行处理器中的SMP(对称多处理器)集成到同一芯片内,各个处理器并行执行不同的进程

1.1.2 cpu核心数和线程数的关系

CPU核心数:线程数=1:1,他俩是一比一的关系,线程数是指的是,正在运行的线程最大数量,比如我的电脑是六核的,我电脑同时在跑的线程,最大的数量的是6个,线程数并不是我们程序里进程开了多少个线程,但是有的CPU也是六核的,但是线程数能达到12个,这是怎么做到的呢?Intel的超线程技术可以做到,把一个物理的cpu,把它模拟成两个逻辑cpu,一个物理核心对应两个逻辑核心

1.2 cpu的时间片轮转机制

在我们开发的过程中,好像并没有受到核心数的影响,我们在程序中,想起几个线程就起几个线程,cpu轮转机制是最简单,最古老,最公平,而且使用的也是最广泛的一种算法机制,也被称为RR调度,即使我就有一个CPU,有一百个线程在执行,但是cpu给我们的感觉是。一百个线程同时在运行的,这是怎么做到的呢

我们把cpu的运行时间分片,打个比方每个线程执行5ms,分到很小很小5ms,每个线程执行5ms,100个线程都执行一遍,也就500ms,对于我们人来说,我们可能就感觉不到他在停顿,就是因为这个机制,所以才让我们感觉我们起的线程,就是在同时运行,事实上不是那么回事

时间片设置的大小,和操作系统有关,和Cpu也有关系

CPU是时间轮转的,在java中能不能,让CPU指定执行某个线程呢,这个功能是能做到,但是在java里不能实现,得调用Linux内核的一个API去设置

1.3 什么是线程,什么是进程

标准说法:进程是操作系统进行资源分配和调度的基本单位,线程thread是操作系统能够进行运算调度的最小单位

我们电脑启动的程序就是一个进程,比如微信,AndroidStudio,IDEA,从概念规模上来讲,进程是大于线程的,线程不能单独存在的,线程必须依附于进程才能存在,如果一个进程中有一个线程活着,那么这个进程就是活着的,进程里的线程是会共享进程里边的所有资源内存,cpu和IO,进程和进程之间是相互独立的,进程和cpu没有必然关系,线程才是调度cpu的最小单位

在操作系统中对线程的限制,Linux中new出的线程不能超过1000,在Windows不能超过2000,

1.4 并行和并发

专业术语有点麻烦,并行,就是能同时运行的最大线程数量,intel的四核并行度是8,它可以同时运行8个线程

并发 指单位时间处理任务的个数,一定是和时间挂钩的,对于并发来讲, 脱离了单位时间是没什么意义的,比如1秒钟CPU执行了100个时间片,他的并发量就是 100,另一个cpu它能一秒钟执行 200个时间片,他的并发量就是200

1.5 高并发编程的意义,好处和注意事项

如果不进行高并发编程,我们就不能够完全最大化的利用这种多核多线程的能力,使用高并发编程,就能提高我们程序的响应能力,比如迅雷,提供了多线程下载,

高并发编程可以在我们编程的时候,使我们的程序模块化,异步化,和简单化,

但是在并发编程里面最需要注意的就是安全问题,因为在同一个进程下的线程是共享这个进程的所有资源,很有可能产生线程安全问题和死锁的问题,在linux里或者在windows里,线程开的太多了,会造成内存耗尽,从而造成死机

时间片轮转机制也是有代价的,会进行上下文的切换,这个线程在让出cpu的时候,操作系统会把当前的数据状态存到内存或者磁盘里,当再一次轮到这个线程的时候,又把它取出来加载进去,这个就是上下文切换,切换一次上下文需要耗费大约20000个cpu的时间周期,这个也是非常消耗cpu资源的,如果线程过多,会进行大量的上下文切换,这样就很有可能造成一种情况,假如你起了100个线程,用时间片轮转机制所耗费的时间,远远大于你一个一个线程执行的时间,所以多线程要用好,就得考虑的要全面

2. 认识Java里的线程

我们看上面这一串代码,如果我执行这个main方法,他会执行几个线程呢,有兴趣的小伙伴可以把代码跑一遍,去验证一下,没错他会执行6个线程

为啥我只执行了一个main方法,为啥会有六个线程在执行呢

1、main就是我们所说的主线程

2、Finalizer

我们在学习Java的时候,Java是一个面向对象的语言,所有类的父类是那个是Object,在Object里有这样一个方法

我们一个对象在有资源要回收的时候,会重写这个方法,然后将回收资源的操作,放到这个方法里面执行,这个方法就会由Finalizer这个线程执行,但是有的时候会发现,这个finalize方法不一定会执行,导,有些关于对象的资源就会没有被回收掉,为什么会这样呢?这个锅得Finalizer线程来背了,是因为Finalizer这个线程是一个守护线程,和主线程同生共死,极有可能主线程挂掉了finalize这个方法还没来得及执行,然后Finalizer也挂掉了

2.1 启动线程的两种方式

Java里的程序天生就是多线程的,新启动线程的方法有:

Thread类

实现接口Runnable

实现接口Callable 有返回值

Callable和Runnable实现的方法其实可以看做一种方式,因为在启动线程的时候Callable是放到FutureTask里面的,我们去看看FutureTask是什么东西他是个泛型类,实现了RunnableFuture这个泛型接口,RunnableFuture这个接口又继承了Runnable,和Future,所以我们可以把Runnable和Callable看成一种方式,Callable这种方式是怎么拿到返回值的呢?

futureTask.get()拿到就是返回值,首先我们看一看Future这个接口都有什么方法

我们再去看实现

它会先判断这个任务执行的状态是否完成,如果没有完成它会进入等待完成这个方法

它会一直等待执行完成

完成之后它会 执行report方法,咱再看report方法

然后我们再去看看Run方法

它会把Call的返回值通过set方法赋值给outcome,get的时候再把outcome返回回去

这就 能拿到Callable的返回值了,在这里我们要区分Thread和Runnable,Thread是唯一一个对操作系统线程的抽象,而Runnable和Callable是对任务的抽象,一定要区分

2.2 线程安全的停止工作

有开始就有结束,怎样才能让Java里的线程安全的停止工作呢stop()还是isInterrupted(    ),static 方法interrupted()?我们要深了解这些方法

一般情况下,Thread的run方法执行完毕,或者 抛出了异常,这个线程就在执行完毕,自己就停止了,这是一般情况下,但是我们往往因为业务的需求,需要在满足某种条件的时候,去手动停止这个线程,这个时候我们应该怎么办呢?首先我们还是去看代码去看看Thread里有什么方法

我们看到了stop(),destroy(),还有suspend(),但是他们都被@Deprecated注解了,JDK就告诉我们这几个方法就不要用

线程的挂起函数suspend(),这个线程在执行挂起的时候,线程不会释放资源,比如它在执行的时候拿到一个锁,挂起的时候他不会释放这个锁,这样就很有可能会导致死锁的问题

线程的停止函数stop(),这个函数是极其野蛮的,你调用这个函数,他会立马把这个线程干掉,他不会 管你这个线程的资源是否正常的释放,比如你在一个线程里写一个文件,写到一半的时候你调用这个方法,造成文件不能正常结束,而且线程里占据操作系统的资源文件句柄啥的没有得到释放

resume是唤起挂起线程的,和suspend配对使用的

其实我们在终止线程的时候,有这么几个方法可以用

interrupt是终止线程,interrupted,isInterrupted是获取线程是否终止 ,interrupt终止线程是一种协作方式不是那种抢占式,话不多说上代码,

这段代码是在主线程里面新启动一个线程,主线程休眠30ms去调用useThread的interrupt(),结果怎么样,结果useThread根本不鸟你,一直在执行,我们把代码做一下更改

我们发现它终止了跳出了循环

我们发现在Java里Thread的interrupt方法只是将中断信号由false改为了true而已,如果线程中没有对这个信号 做任何处理,它是不会终止这个线程的,所以这个 interrupt是协作式的,不是抢占式的

细心的同学可能发现了,获取线程是否中断,有两个方法,一个是 Thread的成员方法 isInterrupted,一个是静态方法interrupted,这两个方法有什么区别呢,

interrupted在获取这个线程状态的时候,如果这个中断状态是true,他会获取这个状态的同时,把这个状态设置成false

上面的代码,最终的打印结果为true,我们再改一下代码瞧一瞧

2.3 深入理解Thread的run()和start()

run方法和我们普通类里的成员方法是没什么区别的,start方法我们点进去也没什么东西,start方法最后执行的是native方法stat0,我们可以调用run,话不多说上代码

上面这个代码也能正常的调用,输出的结果

但是我们run改成start之后

输出结果是这样婶的,Thread只有在执行完start之后才和操作系统的线程挂钩,如果不执行start和普通的类没什么区别,还有同一个线程不能执行两次start,在第二次执行start的时候会抛出 java.lang.IllegalThreadStateException

2.4 Thread的join方法和yield()方法

了解一下线程的生命周期

一个线程在执行完start方法之后不一定会执行,它会进入就绪状态,等待操作系统的调度,操作系统让它执行,它才执行,执行join方法之后会获得执行权,进入到运行状态,在运行状态中执行yield,会进入就绪状态,等待获取CPU的执行权,在运行状态中执行sleep方法会进入到阻塞状态,时间到了或者执行了interrrupt方法会进入就绪状态,在运行状态执行wait方法,也会进入阻塞状态,被唤醒之后也会进入就绪状态,在运行状态执行完自己的任务,线程就会进入死亡阶段

join方法可以理解为去插队,举个简单的例子

轩轩吖是个线程,他要去微波炉去热饭,微波炉可以看成cpu,热饭是一个任务,热饭我需要热个5分钟,当我热到二分钟的时候,我的女神来了,我就可以让我的女神先热饭,女神这个线程join之后,女神就可以热饭了,而我只能等女神热完饭之后,我才能继续热我的饭,女神热到一半的时候她男朋友也过来了,女神又把她男朋友join了一下,那我得等她男朋友热完饭,她热完之后,我才能继续热我的饭

而yield方法,是我正在热饭,然后我把饭拿出来,和女神,和女生的男朋友一起去抢微波炉,谁抢到谁就可以热饭了

2.5 Thread的sleep()和wait()

sleep方法指的是线程休眠,阻塞线程,到时间之后会继续往下执行,而wait方法,是此时此刻某一个资源不满足条件而等待,等这个资源满足之后我在用notify或者notifyAll去唤醒,接着往下执行,wait方法也能设置等待时间,它和sleep方法看起来很像,但是意义是不一样的

3. 总结

从操作系统方面说了一些基础的关于cpu和线程的概念,然后重头戏还是Java里的线程,分别从启动终止,和它的生命周期说了说,希望写到的东西能够帮到大家,希望大佬们一键三连!

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20210930A09FO300?refer=cp_1026
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券