【并发编程之美】并发编程介绍

什么是多线程并发编程

并发:是指同一个时间段内多个任务同事都在执行,并且没有执行结束,而且是在单位时间内多个任务同时在执行。

并发强调的时在同一个时间段内同时执行多个任务,所以在单cpu的时候会根据时间片来进行执行,执行完一个时间片之后切换到下一个进程的时间片再去进行执行,以此来觉得是同时执行,而实际上每个cpu只会干一件事。

在多cpu的时候,会每个线程在一个cpu中执行,这时候每个线程在自己的cpu上进行工作,互不干扰,这才是真正的并行运行。

为什么要进行多线程并发编程

多个cpu意味着每个线程可以使用自己的cpu运行,减少了线程上下文切换的开销,但随着对应用系统性能和吞吐量要求的提高,出现了处理海量数据和请求的要求,这些都对高并发编程有着迫切的需求。

JAVA中的线程安全问题

线程安全是只当多个线程同时读写一个共享资源并且没有任何同步措施时,导致出现脏数据或者其他不可预见的结果的问题。

共享资源:一个资源被多个线程所持有或者说时被多个线程所访问,就叫共享资源。

线程不安全的例子

主内存中现在有两个线程,线程A和线程B,现在这两个线程同时执行对内存中的变量进行加一操作,但是此时就可能会遇到线程安全的问题。
在这里插入图片描述
主内存里面的变量现在的值为0,线程A对count进行加一,线程B也对count进行加一,当然我们最后想要的值肯是2,但是如果线程不安全的话,可能最后的值就会有问题。

我们对count进行加一的操作需要有三步操作:1,获取count的值,2,进行加一,3,把count的值存回去。

首先,线程A获取到count的值为0,然后线程A正在执行加一操作,同时线程B也去主内存中获取了count的值,此时线程A还没有将加一的结果赋给主内存的count,所以此时就会出现这种情况:
在这里插入图片描述
现在线程A要将自己的值保存到主内存中,此时主内存中count的值为1,然后线程B对count加一
在这里插入图片描述
线程B将count的运算结果保存到主内存中

最后得到的结果,count=1,这个结果是错误的,实际上得到的结果应该是2。

用时间节点来表示一下:
在这里插入图片描述
所以这里就涉及到一个多线程共享变量的可见性问题

synchronized关键字

synchronized块是java提供的一种原子性内治所,java中的每个对象都可以把它当作一个同步锁来使用,这些java内置的使用者看不到的锁被称为内置锁,也叫做监视器锁。
被synchronized所标注的代码块会自动获取内部锁,然后其他访问当前代码块的线程会被阻塞挂起。内置锁是排他锁,其他线程必须等待该线程释放锁后才能获取锁。
由于java中的线程和操作系统中的线程是一一对应的,所以阻塞线程需要进行线程切换,从用户态切换到内核态执行阻塞操作,这个过程是非常耗时的,而且synchronized又会导致线程的阻塞,进而导致线程的上下文切换并带来线程调度开销。

synchronized可以解决上面的共享变量内存可见性问题。

volatile关键字

volatile是怎样实现了?比如一个很简单的Java代码:

instance = new Instancce() //instance是volatile变量

在生成汇编代码时会在volatile修饰的共享变量进行写操作的时候会多出Lock前缀的指令(具体的大家可以使用一些工具去看一下,这里我就只把结果说出来)。我们想这个Lock指令肯定有神奇的地方,那么Lock前缀的指令在多核处理器下会发现什么事情了?主要有这两个方面的影响:

  • 将当前处理器缓存行的数据写回系统内存;
  • 这个写回内存的操作会使得其他CPU里缓存了该内存地址的数据无效

为了提高处理速度,处理器不直接和内存进行通信,而是先将系统内存的数据读到内部缓存(L1,L2或其他)后再进行操作,但操作完不知道何时会写到内存。如果对声明了volatile的变量进行写操作,JVM就会向处理器发送一条Lock前缀的指令,将这个变量所在缓存行的数据写回到系统内存。但是,就算写回到内存,如果其他处理器缓存的值还是旧的,再执行计算操作就会有问题。所以,在多处理器下,为了保证各个处理器的缓存是一致的,就会实现缓存一致性协议,每个处理器通过嗅探在总线上传播的数据来检查自己缓存的值是不是过期了,当处理器发现自己缓存行对应的内存地址被修改,就会将当前处理器的缓存行设置成无效状态,当处理器对这个数据进行修改操作的时候,会重新从系统内存中把数据读到处理器缓存里。因此,经过分析我们可以得出如下结论:

  • Lock前缀的指令会引起处理器缓存写回内存;
  • 一个处理器的缓存回写到内存会导致其他处理器的缓存失效;
  • 当处理器发现本地缓存失效后,就会从内存中重读该变量数据,即可以获取当前最新值。

这样针对volatile变量通过这样的机制就使得每个线程都能获得该变量的最新值。

在什么时候使用volatile关键字

  • 写入变量值不依赖变量的当前值时。因为如果依赖当前值,将是获取——计算——写入散步操作,这三步操作不是原子性的,而volatile不保证原子性。
  • 读写变量值时没有加锁。因为锁本身已经保证了内存可见性,这时候不需要把变量声明为volatile的。

参考

彻底理解volatile - 掘金
Java 理论与实践: 正确使用 Volatile 变量

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Geek-Banana

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值