JAVA Lock与CAS 分析

原子(atomic),本意是指“不能被进一步分割的粒子”。原子操作意味着“不可被中断的一个或一系列操作”。在Java中通过循环CAS的方式实现原子操作。

机制保证了只有获得锁的线程才能操作锁定的内存区域,在JDK 5之前Java语言是靠synchronized关键字保证同步的,synchronized可以保证方法或代码块在运行时,同一时刻只有一个线程可以进入到临界区(互斥性),同时它还保证了共享变量的内存可见性。

Java中的每个对象都可以作为锁。

  • 普通同步方法,锁是当前实例对象。

  • 静态同步方法,锁是当前类的class对象。

  • 同步代码块,锁是括号中的对象。

我们先来看一下等待/通知机制:

import java.util.concurrent.TimeUnit;

public class WaitNotify {
   

   static boolean flag = true;
   static Object lock = new Object();

    public static void main(String[] args) throws InterruptedException {
        Thread A = new Thread(new Wait(), "wait thread");
        A.start();
        TimeUnit.SECONDS.sleep(2);
        Thread B = new Thread(new Notify(), "notify thread");
        B.start();
    }

    static class Wait implements Runnable {
   
        @Override
        public void run() {
            synchronized (lock) {
                while (flag) {
                    try {
                        System.out.println(Thread.currentThread() + " flag is true");
                        lock.wait();
                    } catch (InterruptedException e) {
                    }
                }
                System.out.println(Thread.currentThread() + " flag is false");
            }
        }
    }

    static class Notify implements Runnable {
   
        @Override
        public void run() {
            synchronized (lock) {
                flag = false;
                lock.notifyAll();
                try {
                    TimeUnit.SECONDS.sleep(7);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

等待/通知机制相关方法在java.lang.Object上定义,线程A在获取锁后调用了对象lock的wait方法进入了等待状态,线程B调用对象lock的notifyAll()方法,线程A收到通知后从wait方法处返回继续执行,线程B对共享变量flag的修改对线程A来说是可见的。

整个运行过程需要注意一下几点:

  • 使用wait()、notify()和notifyAll()时需要先对调用对象加锁,调用wait()方法后会释放锁。

  • 调用wait()方法之后,线程状态由RUNNING变为WAITING,并将当前线程放置到对象的等待队列中。

  • notify()或notifyAll()方法调用后,等待线程不会立刻从wait()中返回,需要等该线程释放锁之后,才有机会获取锁,之后从wait()返回。

  • notify()方法将等待队列中的一个等待线程从等待队列中移动到同步队列中;

  • notifyAll()方法则是把等待队列中的所有线程都移动到同步队列中,被移动的线程状态从WAITING变为BLOCKED。

  • 从wait()方法返回的前提是,该线程获得了调用对象的锁。

那么,它是如何实现线程之间的互斥性和可见性?

互斥性
我们通过一段代码解释:

public class SynchronizedDemo {
    private static Object object = new Object();
    public static void main(String[] args) throws Exception{
        synchronized(object) {
   //同步代码块
        }
    }
    public static synchronized void m() {}
    //同步方法
}

上这段代码中,使用了同步代码块和同步方法,

  • 同步代码块使用了 monitorenter 和 monitorexit 指令实现。

  • 同步方法中依靠方法修饰符上的 ACC_SYNCHRONIZED 实现。

无论哪种实现,本质上都是对指定对象相关联的monitor的获取,这个过程是互斥性的,也就是说同一时刻只有一个线程能够成功,其它失败的线程会被阻塞,并放入到同步队列中,进入BLOCKED状态。

锁的内部机制

通常情况下锁有4种状态:无锁状态,偏向锁状态,轻量级锁状态,重量级锁状态。
在进一步了解锁之前,我们需要了解两个概念:对象头和monitor。

什么是对象头?
锁存在Java对象头里。如果对象是数组类型,则虚拟机用3个Word(字宽)存储对象头,如果对象是非数组类型,则用2字宽存储对象头。在32位虚拟机中,一字宽等于四字节,即32bit。

长度 内容 说明
32/64bit Mark Word 存储对象的hashCode或锁信息等。
32/64bit Class Metadata Address 存储到对象类型数据的指针
32/64bit Array length 数组的长度(如果当前对象是数组)


在hotspot虚拟机中,对象在内存的分布分为3个部分:对象头,实例数据,和对齐填充。
mark word 被分成两部分,lock word和标志位。
Klass

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值