小白并发编程学习系列文章之六:Synchronized & CAS

1.Synchronized

1) Synchronized 语义

synchronized 是 Java语言的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码,而这段代码也被称为临界区

synchronized 有多个叫法,而每个叫法都表明synchronized 的特性:
1、内置锁(又叫 隐式锁):synchronized 是内置于JDK中的,底层实现是native;同时,加锁、解锁都是JDK自动完成,不需要用户显式地控制,非常方便。

2、同步锁: synchronized 用于同步线程,使线程互斥地访问某段代码块、方法。这就是意味着最多只有一个线程能够获得该锁,当线程A尝试去获得线程B持有的内置锁时,线程A必须等待或者阻塞,知道线程B释放这个锁,如果B线程不释放这个锁,那么A线程将永远等待下去。

3、对象锁:准确来说,是分为对象锁、类锁。synchronized 以当前的某个对象为锁,线程必须通过互斥竞争拿到这把对象锁,从而才能访问 临界区的代码,访问结束后,就会释放锁,下一个线程才有可能获取锁来访问临界区(被锁住的代码区域)。synchronized锁 根据锁的范围分为 对象锁 和 类锁。对象锁,是以对象实例为锁,当多个线程共享访问这个对象实例下的临界区,只需要竞争这个对象实例便可,不同对象实例下的临界区是不用互斥访问;而类锁,则是以类的class对象为锁,这个锁下的临界区,所有线程都必须互斥访问,尽管是使用了不同的对象实例;

总的来说,对象锁的粒度要比类锁的粒度要细,引起线程竞争锁的情况比类锁要少的多,所以尽量别用类锁,锁的粒度越少越好。

2) Synchronized 原理

对下面代码

public class SynchronizedTest {

    public synchronized void doSth(){
        System.out.println("hello World");
    }

    public void doSth1(){
        synchronized (SynchronizedTest.class){
            System.out.println("Hello World");
        }
    }
    public static void main(String[] args) {
        new SynchronizedTest().doSth();
        new SynchronizedTest().doSth1();
    }
}

查看其字节码如下:

 public synchronized void doSth();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_SYNCHRONIZED
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #3                  // String hello World
         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 13: 0
        line 14: 8
      LocalVariableTable:
        Start  Length  Slot  Name   Signature

 public void doSth1();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=3, args_size=1
         0: ldc           #5                  // class com/example/springbootannotationdemo/currentThread/SynchronizedTest
         2: dup
         3: astore_1
         4: monitorenter
         5: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         8: ldc           #6                  // String Hello World
        10: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        13: aload_1
        14: monitorexit
        15: goto          23
  

反编译后,我们可以看到Java编译器为我们生成的字节码。在对于doSth和doSth1的处理上稍有不同。也就是说。JVM对于同步方法和同步代码块的处理方式不同。

对于同步方法,JVM采用ACC_SYNCHRONIZED标记符来实现同步。 对于同步代码块。JVM采用monitorentermonitorexit两个指令来实现同步。

3) Synchronized 总结

同步方法通过ACC_SYNCHRONIZED关键字隐式的对方法进行加锁。当线程要执行的方法被标注上ACC_SYNCHRONIZED时,需要先获得锁才能执行该方法。
同步代码块通过monitorenter和monitorexit执行来进行加锁。当线程执行到monitorenter的时候要先获得所锁,才能执行后面的方法。当线程执行到monitorexit的时候则要释放锁。
每个对象自身维护这一个被加锁次数的计数器,当计数器数字为0时表示可以被任意线程获得锁。当计数器不为0时,只有获得锁的线程才能再次获得锁。即可重入锁

2. Synchronized 的等待唤醒机制

synchronized的等待唤醒是通过notify/notifyAll和wait三个方法来实现的,这三个方法的执行都必须在同步代码块或同步方法中进行,否则将会报错。

wait方法的作用是使当前执行代码的线程进行等待,notify/notifyAll相同,都是通知等待的代码继续执行,notify只通知任一个正在等待的线程,notifyAll通知所有正在等待的线程。wait方法跟sleep不一样,他会释放当前同步代码块的锁,notify在通知任一等待的线程时不会释放锁,只有在当前同步代码块执行完成之后才会释放锁。

3. CAS

在synchronized的优化过程中我们看到大量使用了CAS操作,CAS全称Compare And Set(或Compare And Swap),CAS包含三个操作数:内存位置(V)、原值(A)、新值(B)。简单来说CAS操作就是一个虚拟机实现的原子操作,这个原子操作的功能就是将旧值(A)替换为新值(B),如果旧值(A)未被改变,则替换成功,如果旧值(A)已经被改变则替换失败。

CAS 最常见如原子类的实现,以下为两个java 版本 AtomicInteger的实现

//jdk1.8前
private volatile int value;

public final int incrementAndGet() {
    for (;;) {
        int current = get();
        int next = current + 1;
        if (compareAndSet(current, next))
            return next;
    }
}

通过compareAndSet将变量自增,如果自增成功则完成操作,如果自增不成功,则自旋进行下一次自增,由于value变量是volatile修饰的,通过volatile的可见性,每次get()都能获取到最新值,这样就保证了自增操作每次自旋一定次数之后一定会成功。

JDK1.8中则直接将getAndAddInt方法直接封装成了原子性的操作,更加方便使用。

//jdk 1.8
public final int getAndIncrement() {
    return unsafe.getAndAddInt(this, valueOffset, 1);
}

CAS操作是实现Java并发包的基石,他理解起来比较简单但同时也非常重要。Java并发包就是在CAS操作和volatile基础上建立的,下图中列举了J.U.C包中的部分类支撑图:
在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值