热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Java锁机制2.0

在Java锁机制1.0中已经介绍了锁这种抽象概念,Java如何实现互斥锁(synchronized)、互斥锁的工作原理以及互斥锁的优化及锁

在 Java锁机制1.0中已经介绍了 锁 这种抽象概念,Java如何实现互斥锁(synchronized)、互斥锁的工作原理以及互斥锁的优化 及 锁的四种状态及其转化条件。

1.为什么需要CAS

悲观: 操作系统会悲观的认为如果不严格同步线程调用,就一定会造成异常。

悲观锁: 当有多个线程要操作同一对象,如果使用互斥锁,但是它的同步方式是悲观的,所以互斥锁将会锁定资源,只供一个线程调用,而阻塞其他线程——悲观锁。

悲观锁不适用场景1: 当大部分调用都是读操作(线程安全问题都是对全局/静态变量进行写操作造成的),那么就没有必要在每次调用的时候都锁定资源。

悲观锁不适用场景2: 同步代码块执行的耗时远远小于线程切换的耗时。

那么,如何不对共享资源进行锁定,也能对线程调用进行协调呢?

于是,CAS(Compare And Swap) 算法就诞生了。

2.啥是CAS?

通过下列场景进行描述CAS:两个线程争夺一个对象资源的情况。

注意: 上述的比较值和切换值同一时间只能有一个线程进行操作 → CAS必须是原子性的

如何实现CAS的原子性?
当然不能通过锁

各种不同的4架构的CPU都提供了指令级别的CAS原子操作,如X86(cmpxchg支持CAS)、ARM(LL/SC),而不需要通过操作系统的同步原语(如mutex),CPU已经原生支持了CAS,上层调用即可,这样就能不依赖锁来进行线程同步。但这样并不意味着CAS就能代替锁。

这些通过CAS来实现同步的工具,由于不会锁定资源。而且当线程需要修改共享资源时,总是会乐观地认为对象状态值没有被其他线程修改过,而是每次自己都会主动尝试去比较状态值,相较于悲观锁,这种机制被称为 乐观锁 。但这是一种无锁的同步机制。

3.Java如何实现CAS

案例: 使用3条线程,将一个值,从0累加到1000

先演示不用锁,也不用CAS来进行

public class TestDemo1 {static int num &#61; 0;public static void main(String[] args) {for (int i &#61; 0; i < 3; i&#43;&#43;) {Thread thread &#61; new Thread(() -> {while (true){if (num >&#61; 1000){break;}System.out.println("thread name: " &#43; Thread.currentThread().getName() &#43; ":" &#43; num&#43;&#43;);}});thread.start();}}
}

查看控制台输出&#xff1a;

出现了重复打印的结果

接着演示&#xff0c;使用互斥锁&#xff08;悲观锁&#xff09;来改进&#xff1a;

public class TestDemo1 {static int num &#61; 0;public static void main(String[] args) {for (int i &#61; 0; i < 3; i&#43;&#43;) {Thread thread &#61; new Thread(() -> {while (true){synchronized (TestDemo1.class){ //给对象资源上锁if (num >&#61; 1000){break;}System.out.println("thread name: " &#43; Thread.currentThread().getName() &#43; ":" &#43; num&#43;&#43;);}}});thread.start();}}
}

使用CAS

public class TestDemo1 {static AtomicInteger num &#61; new AtomicInteger(0);public static void main(String[] args) {for (int i &#61; 0; i < 3; i&#43;&#43;) {Thread thread &#61; new Thread(() -> {while (true){if (num.get() >&#61; 1000){break;}System.out.println("thread name: " &#43; Thread.currentThread().getName() &#43; ":" &#43; num.incrementAndGet());}});thread.start();}}
}

4.AtomicInteger是如何实现CAS同步的&#xff1f;

使用 Unsafe.compareAndSwapInt 进行更新

进入源码&#xff1a;

public class AtomicInteger extends Number implements java.io.Serializable {private static final long serialVersionUID &#61; 6214790243416807050L;//设置使用 Unsafe.compareAndSwapInt 进行更新// setup to use Unsafe.compareAndSwapInt for updatesprivate static final Unsafe unsafe &#61; Unsafe.getUnsafe();private static final long valueOffset;

找到incrementAndGet()

public final int incrementAndGet() {return unsafe.getAndAddInt(this, valueOffset, 1) &#43; 1;}

进一步点进getAndAddInt()
可以看到&#xff0c;其调用了compareAndSwapInt()

public final int getAndAddInt(Object var1, long var2, int var4) {int var5;//这个循环就是自旋操作do {var5 &#61; this.getIntVolatile(var1, var2);} while(!this.compareAndSwapInt(var1, var2, var5, var5 &#43; var4));return var5;}

注意&#xff1a; 此处的自旋并不会一直循环下去&#xff0c;其默认最大循环次数为10

5.了解unsafe类

java不能直接访问操作系统底层&#xff0c;而是通过本地方法来访问。Unsafe类提供了硬件级别的原子操作&#xff0c;主要提供了以下功能&#xff1a;

  • 通过Unsafe类可以分配内存&#xff0c;可以释放内存
  • 可以定位对象某字段的内存位置&#xff0c;也可以修改对象的字段值&#xff0c;即使它是私有的
  • 挂起与恢复&#xff08;park挂起线程、unpark恢复挂起的线程&#xff09;
  • CAS操作&#xff08;通过compareAndSwapXXX方法实现&#xff09;
    CAS操作有3个操作数&#xff0c;内存值M&#xff0c;预期值E&#xff0c;新值U&#xff0c;如果M&#61;&#61;E&#xff0c;则将内存值修改为B&#xff0c;否则啥都不做。

推荐阅读
author-avatar
建铭琼伶俊涵
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有