原子(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