一网打尽!synchronized关键字入门(一)

简介: 一网打尽!synchronized关键字入门(一)

1.1 多线程一定快吗?


有人做过这样一个实验:“一段代码,有两个方法对各自的属性进行累加操作,其中一个方法采用多线程”,部分结果如下:

1.png


我们可以明显的看到,多线程不一定比单线程快


1.2 上下文切换


单核处理器也支持多线程执行代码:


通过给线程分配时间片来实现这个机制。


时间片一般是几十毫秒,所以CPU需要通过不停地切换线程来执行。当前执行完一个时间片后会切换下一个任务。在切换前会保存当前任务的状态,可以恢复这个任务之前的状态。 任务从保存到再次被加载的过程就是一次上下文切换。


时间片:操作系统分配给每个正在运行的进程(线程)微观上的一段CPU时间



测试上下文切换:


当我们一直运行多线程程序时,发现CS飙升到1000以上。

2.png


1.3 Java内存模型


在深入学习synchronized关键字之前,有必要先了解一下Java的内存模型


Java Memory Model(JMM):


Java线程之间的通信由JMM来控制,其决定一个线程对共享变量的写入,何时对另外一个线程可见。

3.png


为了提高效率,线程之间的共享变量是存储在主存当中,每一个线程都有一个属于自己的本地内存。


如果线程A与线程B之间要通信,需要经历下面两步:


1 线程A把本地内存中更新过的共享变量,刷新到主存中。


2 线程B到主存中重新读取更新后的共享变量。


1.4 主存与工作内存间的数据交互过程


4.png


lock     :作用于主存的变量,把一个变量标识为线程独占状态

unlock :作用于主存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定

read    :作用于主存变量,它把一个变量的值从主存传输到线程的工作内存中,以便随后的load动作使用

load     :作用于工作内存的变量,它把read操作从主存中变量放入工作内存中

use      :作用于工作内存中的变量,它把工作内存中的变量传输给执行引擎,每当虚拟机遇到一个需要使用的变量的值,就会使用到这个指令

assign :作用于工作内存中的变量,它把一个从执行引擎中接受到的值放入工作内存的变量副本中

store    :作用于主存中的变量,它把一个从工作内存中一个变量的值传送到主存中,以便后续的write使用

write    :作用于主存中的变量,它把store操作从工作内存中得到的变量的值放入主存的变量中

JMM对这八种指令制定了如下规则:


不允许read和load、store和write操作之一单独出现。即使用了read必须load,使用了store必须write

不允许线程丢弃他最近的assign操作,即工作变量的数据改变了之后,必须告知主存

不允许一个线程将没有assign的数据从工作内存同步回主内存

一个新的变量必须在主内存中诞生,不允许工作内存直接使用一个未被初始化的变量。就是怼变量实施use、store操作之前,必须经过assign和load操作

一个变量同一时间只有一个线程能对其进行lock。多次lock后,必须执行相同次数的unlock才能解锁

如果对一个变量进行lock操作,会清空所有工作内存中此变量的值,在执行引擎使用这个变量前,必须重新load或assign操作初始化变量的值

如果一个变量没有被lock,就不能对其进行unlock操作。也不能unlock一个被其他线程锁住的变量

对一个变量进行unlock操作之前,必须把此变量同步回主内存


2.1 多线程带来的可见性问题


可见性:一个线程对主存的修改可以及时被其他线程观察到。


5.png


上图,线程一把flag属性读取到线程私有的本地内存中,值为false;线程二把flag属性修改为true,并且刷新到主内存当中,但是线程一不知道flag被修改了。


解决方案:如果有加同步的机制,则会有lock、unlock操作,lock操作会使本地内存中的属性失效,从而去主内存中重新读取数据。



2.2 多线程带来的原子性问题


原子性:同一个时刻只能有一个线程来对它进行操作。


 如果使用五个线程同时对变量 i 进行1000次 ++ 操作,最后的结果并不一定等于5000。


 反编译结果可以看出:

6.png


index ++ 一共涉及到4条指令;


假设线程一执行到步骤三时CPU切换线程。线程二执行步骤一,这时index的值还是等于0,因为线程一并没有执行步骤四就被切换上下文了。 等线程二执行完成,又切回到线程一,线程一会接着执行步骤四,并不会重新获取index的值,导致计算结果不正确。



当加上了synchronized同步机制之后, 会插入monitorenter、monitorexit两条指令。


7.png


若线程一执行到步骤三,被切换到线程二,线程二执行monitorenter指令时会发现,这个对象已经被其他线程占用了,所以只能等待。


当又切回到线程一时,线程一操作完整个步骤执行monitorexit来释放锁。此时线程二才可以获得锁。 这样就能保证原子性。


2.3 多线程带来的有序性问题


有序性:Java在编译时和运行时会对代码进行优化,会导致程序最终的执行顺序与编写的顺序不同。


一个典型的例子就是JVM中的类加载:

8.png


类从加载到JVM到卸载一共会经历五个阶段:加载、连接、初始化、使用、卸载。这五个过程的执行顺序是一定的,但是在连接阶段,也会分为三个过程,即验证、准备、解析阶段,这三个阶段的执行顺序不是确定的,通常交叉进行,在一个阶段的执行过程中会激活另一个阶段。


解决方案:加锁。


2.4 多线程带来的活跃性问题


活跃性问题关注的是:某件事情是否会发生。


典型的就是死锁问题:如果一组线程中的每个线程都在等待一个事件的发生,而这个事件只能由该组中正在等待的线程触发,这种情况会导致死锁。


2.4.1 死锁的4个必要条件


造成死锁的原因有四个,破坏其中一个即可破坏死锁


互斥条件:在一段时间内某资源只由一个进程占用。如果此时还有其它进程请求资源,则请求者只能等待,直至占有资源的进程释放。

请求和保持条件:进程已经保持至少一个资源,但又提出了新的资源请求,而该资源已被其它进程占有,此时请求进程阻塞,但又对自己已获得的其它资源保持占有。

不剥夺条件:进程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完时由自己释放。

循环等待:在发生死锁时,必然存在一个进程对应的环形链。


2.5 性能问题


文章开头就提到过,在多线程中有一个非常重要的性能因素就是上下文切换。线程间的切换会涉及到一下几个步骤:

9.png


将CPU从一个线程切换到另一线程涉及挂起当前线程,保存其状态,例如寄存器,然后恢复到要切换的线程的状态,加载新的程序计数器。


2.5.1 引起线程切换的几种方式


当前正在执行的任务完成,系统的CPU正常调度下一个需要运行的线程

当前正在执行的任务遇到I/O等阻塞操作,线程调度器挂起此任务,继续调度下一个任务。

多个任务并发抢占锁资源,当前任务没有获得锁资源,被线程调度器挂起,继续调度下一个任务。

用户的代码挂起当前任务,比如线程执行sleep方法,让出CPU。

使用硬件中断的方式引起

 


相关文章
|
6月前
|
存储 Java 中间件
《吊打面试官系列》从源码全面解析 ThreadLocal 关键字的来龙去脉
《吊打面试官系列》从源码全面解析 ThreadLocal 关键字的来龙去脉
|
3月前
|
Java 程序员 开发者
深入解读:synchronized关键字背后的“黑科技”!
深入解读:synchronized关键字背后的“黑科技”!
30 4
|
5月前
|
缓存 Java 编译器
必知的技术知识:Java并发编程:volatile关键字解析
必知的技术知识:Java并发编程:volatile关键字解析
27 0
|
6月前
|
安全 Java 编译器
是时候来唠一唠synchronized关键字了,Java多线程的必问考点!
本文简要介绍了Java中的`synchronized`关键字,它是用于保证多线程环境下的同步,解决原子性、可见性和顺序性问题。从JDK1.6开始,synchronized进行了优化,性能得到提升,现在仍可在项目中使用。synchronized有三种用法:修饰实例方法、静态方法和代码块。文章还讨论了synchronized修饰代码块的锁对象、静态与非静态方法调用的互斥性,以及构造方法不能被同步修饰。此外,通过反汇编展示了`synchronized`在方法和代码块上的底层实现,涉及ObjectMonitor和monitorenter/monitorexit指令。
394 0
|
Java
关于关键字volatile的一二
关于关键字volatile的一二
79 0
|
Java
JUC系列(五)| Synchonized关键字的进一步讲解
JUC系列(五)| Synchonized关键字的进一步讲解
114 0
JUC系列(五)| Synchonized关键字的进一步讲解
|
存储 Java C++
一网打尽!synchronized关键字入门(二)
一网打尽!synchronized关键字入门(二)
一网打尽!synchronized关键字入门(二)
|
存储 安全 Java
一网打尽!synchronized关键字入门(三)
一网打尽!synchronized关键字入门(三)
一网打尽!synchronized关键字入门(三)
|
存储 编译器 Linux
C深剖关键字(4)
目录 一:volatile关键字 二:extern关键字 三:struct关键字
C深剖关键字(4)
|
编译器 程序员 Linux
C深剖关键字(3)
目录 一:void关键字 二:return关键字 三:const关键字
C深剖关键字(3)