浅谈多线程

一、进程与线程
在介绍线程之前,简单地介绍一下进程和线程的关系。
在一个并发程序中,一共有2种方式来进行通信:
①共享内存
②消息传递(消息队列)

简而言之,进程可以简单地想成main函数,线程可以看作是函数里开的不同的分身(类似?)
而进程和线程也有各自的通信方式。
对于进程而言,只可以通过消息传递的方式来进行通信。进程间是拥有私有空间,彼此互相隔离,是重量级的程序,进程间是独享内存的。
对于线程而言,可以通过共享内存和消息传递的方式进行通信,这是程序内部的控制机制,是一种轻量级的程序,进程可以创建多个线程,线程间内存地址一样,共享内存。

二、线程的创建
在这里插入图片描述
创立线程的时候,通过向Thread里传Runnable对象,然后重写Runnable里的run方法。

在这里插入图片描述
然后调用线程的start方法,则会自动调用重写的run方法。
这里简单地提一下,假设我在main函数里新建立了一个线程,那此时程序里会有2个线程,分别是主线程(main函数)和自己新建立的线程。

三、有关线程的注意事项
对于每一个线程如何执行,是由操作系统来决定的。如下图所示

在这里插入图片描述
这里要引入一个叫切片的概念,每一个线程不是刚刚好完整地执行完,而是通过操作系统自行调度,会分时地对不同线程进行调度,从而实现效率的最大化。因为由于操作系统自行调度的原因,即我们先创建的线程不一定比后创建的线程先执行完,先后顺序是不固定的。
而如果想某些语句不切片,直接完整地执行完,就需要用到原子操作,这里用一个例子简单地解释下。
在这里插入图片描述
对于其中的x = x * 2操作,这看似只是一个操作,其实可以分解成3个原子操作。
在这里插入图片描述
分别是:
①读取x
②算出x2
③将算出的x
2写回到x里

对于单行语句是否为原子操作,则需要根据JVM(Java虚拟机)来决定,不过大部分单行语句都可以分解成若干个原子操作。而分解后,单行语句就可能会发生竞争。举个例子:

在这里插入图片描述
若原来是单线程,分别执行方法A,B。A先获取balance=0,然后+1,再写回,此时balance=1.这时候B再获取balance=1,然后+1,再写回,最终balance=2.
而此时是多线程的话,且分解成原子操作后,经操作系统分配,若以此顺序来执行。A先获得balance=0,B再获得balance=0,对A的balance+1=1,再对B的balance+1=1.然后A的写回,balance=1.B的也写回,balance=1,最终balance=1.

接下来会通过一个更具体的例子对线程竞争的影响做一个直观的说明。每行语句会转化成若干行原子操作,故多线程可能会导致有非常多的结果。故为了防止这种影响,可以将某行会导致有偏差的语句强转成原子操作,即不让切片执行。

在这里插入图片描述
给定这个例子,假设方法A,B是多线程发生的,要写出所有的可能结果。

在这里插入图片描述
为了分析的更加全面,我们要将单行语句分解成原子操作来看。分别都是取、乘、写回。而方法A由于第一个的写回和第二个的读重合了,故只需要一个。所以A,B分别对应5,3个原子操作。
而为什么会有不同的结果呢,主要原因就是第3,5,8步的写回操作,会导致覆盖。正常来说,我们计算完,然后写回。以单线程看是对的。但万一出现这种情况,A已经计算完了,B也已经计算完了。此时A执行写回操作,B也执行写回操作,就会发现,B的结果直接覆盖了A的结果,导致A的计算都白计算了。在有了上述的理解后,我们进行分组讨论:
① 6-7-1-2-3-4-5-8,最终第8步的写回覆盖了之前的写回,最终x=5.
② 1-2-3-4-6-7-8-5,最终第5步的写回覆盖了之前的写回,最终x=6.
③ 1-2-3-6-7-4-5-8,首先第3步的写回使x=2,然后第6,7步已经算出了r=10,最终第8步覆盖了第5步的写回,最终x=10.
④ 6-7-1-2-3-8-4-5,首先第8步覆盖了之前的写回算出x=5,然后第5步算出x=15,最终x=15.
⑤ 1-2-3-4-5-6-7-8,类似单线程一步步地写下去,最终x=30.
然后下面是更明显的示意图,不过图中少了x=15的情况。
在这里插入图片描述

然后我们看下列代码
在这里插入图片描述
定义了一个全局变量i,通过打印的结果来感受一下线程切片的随机性。在线程的run里执行i++,然后打印i。

在这里插入图片描述
第一次遇到的是2个i都是3,即代表两个线程在执行到打印前,都已经执行完了i++。

在这里插入图片描述
第二次遇到的是线程1是2,线程2是3.即代表线程1在线程2执行i++之前就已经执行了打印i的操作。

而理论上还有一种情况是线程1是3,线程2是2.这代表线程2在线程1执行i++之前就已经执行了打印i的操作。但这仅限于理论上,我在测试的过程中没有遇到这种情况,可能是大部分JVM都还是保持线程的顺序执行,然后你在每个线程内部操作都一样的情况下,应该是先执行的线程先完成。

四、线程中断
某个线程是否中断由此线程来决定。
首先介绍3个方法:
①interrupt:使标志位(中断信号位)置1。
②interrupted:返回标志位后,再复位(返回原状态)。如标志位在由0变1的时候,再用了interrupted后,会返回0。
③isinterrupted:单纯地返回标志位。

在sleep()后如果执行interrupt中断线程,此时会报错。因为sleep()的时候系统会检测是否收到中断信号,如果收到,则会抛出异常。

在这里插入图片描述
以此图为例,main函数在执行到t.start的时候,线程t开始执行,并且一直在while循环里不断的计算中,直到main函数线程执行到t.interrupt()的时候,若t线程刚好也执行了t.sleep(),此时就会报错。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值