java多线程

java内存模型 java memory model

cpu缓存模型:将数据从硬盘中取出,先放入主内存,然后再加载进CPU高速缓存区,最终CPU运行数据主要是与CPU高速缓存区打交道。

java线程内存模型jmm:

同cpu缓存模型类似,是java虚拟机定义的一种抽象规范,用来屏蔽不同的操作系统和硬件的内存访问差异,让java程序在各种平台下都能达到一致的内存访问效果。

  1. 主内存被所有的线程共享,对于一个共享变量来说,主内存中存储了它的“本体”。
  2. 每一个线程拥有自己的工作内存,对于一个共享变量来说,工作内存中存储了它的“副本”。
  3. 线程对共享变量的所有操作都必须在工作内存中进行,不能直接读写主内存中的变量。不同线程之间也无法访问批次的工作内存,变量值的传递只能通过主内存来进行。

并发编程的三大特性

  • 可见性,多线程变量在多个线程缓存副本之间达到一致。
  • 原子性,一个或多个操作要么全部执行成功,要么全部失败。
  • 有序性,防止指令重排。

指令重排:在不影响单线程程序执行结果的前提下,CPU为了最大限度的发挥机器性能,会对代码的执行顺序重排序优化。

jvm编译器同样也存在指令重排。


JMM数据原子操作

  1. read:从主内存读取数据
  2. load:将从主内存读取的数据写入工作内存
  3. use:从工作内存读取数据进行运算等操作
  4. assign:将计算好的值重新赋值给工作内存中
  5. store:将工作内存数据写入主内存
  6. write:将store过去的变量值赋值给主内存中对应的变量
  7. lock:将主内存加锁,标识为线程独占状态
  8. unlock:解锁,解锁后其他线程可锁定该变量

volatile关键字

当变量加上volatile修饰之后,便会开启总线级别的缓存一致性协议(MESI)和CPU总线嗅探机制。

当某个线程修改该变量传回总线时,其他线程便会监听到此变量已经被修改,然后便会将本线程中持有的该变量失效掉,进而重新从主内存总读取该变量。

MESI:多个CPU从主内存读取同一个数据到自己的高速缓存区,当其中某个CPU修改了缓存中的数据,该数据会马上同步回主内存,其他CPU通过总线嗅探机制可探知到数据的变化从而让自己缓存区内的数据失效。

volatile保证了可见性和有序性。

原理:

  1. 可见性:同上
  2. 有序性:volatile修饰符禁止了该变量访问前后的指令重排。

无法保证原子性;举例:

假设有两个线程A、B,分别都进行10此i++,i初始值为0,被volatile修饰;

当A、B线程从主内存中读取到i=0到工作内存后同时执行完i++use操作,且开始assign操作,假设此时A线程assign操作较快一步,并且开始store和writ,将i=1存入主内存后便会开启MESI使B线程中工作内存中的i失效掉,那么B线程就会重新读取i,此时i已经等于1了,在进行i++操作并写回主内存。此时实际已经执行了三次i++操作,但是i的值却是2。所以volatile无法保证原子性。


synchronized关键字

synchronized关键字可同时保证可见性,有序性,原子性。

在JDK1.6之前属于互斥锁,悲观锁,同步锁,重量级锁。(叫法不同,本质都一样)

当加上synchronized关键字后,对象中的对象头的锁标识位便会指向10,指向的是monitor对象。

monitor是一个同步工具,提供线程(进程)被阻塞和被唤醒的管理机制。

monitor对象存在于每个java对象头中,synchronized便是通过这种方式获取锁,java中任意对象都可以作为锁,notify/notify/wait等方法存在于祖宗对象Object当中。

monitor对象中有两个队列,WaitList和EntryList,分别是线程生命周期中的等待队列和锁池。

缺点:synchronized在运行过程中会涉及到线程阻塞,上下文切换,操作系统对线程的调度等,占用资源过大。

synchronized在JDK1.6之后优化;

(程序在大部分时候都只有一个线程来运行加锁的代码块)

  1. 如果只有一个线程,首先将线程ID存入对象头中的偏向ID中,若下次来执行的线程ID于对象头中的相同,则直接执行该线程。这就是偏向锁(也叫偏心锁)
    java线程默认启动时不开启偏向锁,可通过修改虚拟机参数修改。XX:+UseBiasedLocking
  2. 当由多个线程后,synchronized升级为CAS。
  3. 当线程更多后,锁膨胀升级为重量级锁,也就是1.6之前的synchronized。此时锁标记为10,指针指向monitor。


1.乐观锁:自旋锁,共享锁,轻量级锁,共享锁

假定当前环境时读多写少,遇到并发的概率比较低,读数据时认为别的线程不会正在修改。写数据时判断当前值于期望值是否相同,否则继续进行CAS操作。

java中的乐观锁:CAS,比较并替换,比较当前值(主内存中的值),于预期值(当前线程中的值,主内存中的拷贝)是否一样,一样则更新,否则继续进行CAS操作。

2.悲观锁:独占锁,重量级锁,互斥锁,同步锁

与乐观锁相反

java中的悲观锁:synichronized修饰的方法和代码块、ReentrantLock。

只能有一个线程进行读操作或者写操作,其他线程读写操作均不能进行。

3.自旋锁

java中的自旋锁:CAS操作中比较操作失败后的自旋等待。

4.分段锁

分段锁是一种机制,具体可参考concurrentHashMap,其中用到的就是分段锁。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值