版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
1. volatitle和synchronized关键字
① volatitle关键字
- 对共享变量进行准确和一致的更新时,线程需要通过排它锁单独获得这个共享变量。
- 使用排他锁的代价是非常大的,我们使用volatitle来保证共享变量的可见性。
- 可见性: 一个线程修改共享变量时,其他线程能获得共享变量修改后的值。
- 定义为volatitle的共享变量具有的特性:
- 线程之间的可见性
- 允许多个线程同时读,而且读到的值不过期
- 只允许单线程写
- volatitle是轻量级的synchronized,不会引起线程的上下文切换和调度。
- volatitle的优化: 在
LinkedTransferQueue
中,使用volatile变量时,使用一种追加字节的方式优化队列出队和入队的性能。
② synchronized关键字
- synchronized实现同步的基础:Java中的每一个对象都可以作为锁。
- 对于普通同步方法,锁是当前实例对象。
- 对于静态同步方法,锁是当前对象的 Class 对象。
- 对于同步方法块,锁是 Synchonized 括号里配置的对象。
- synchronized的实现的过程:
- 通过进入和退出Monitor对象实现方法同步和代码块同步。
- 代码块的同步使用
monitorenter指令
和monitorexit指令
实现,monitorenter指令
编译后插入到同步代码块的开始处,monitorexit指令
编译后插入同步代码块的结束处和异常处。 - 每个对象都有monitor与之关联,线程执行到
monitorenter指令
时,需要获取monitor的持有权,即获取对象的锁;线程执行到monitorexit指令
时,需要释放monitor的持有权,即释放锁。
- synchronized的存储位置
- 锁存在 Java 对象头里,如果对象是数组类型,则虚拟机用 3 个 Word(字宽)存储对象头;如果对象是非数组类型,则用 2 字宽存储对象头。
- 在 32 bit虚拟机中,一字宽(Word)等于四字节,即 32bit。64 bit的虚拟机,word则是64 bit。
长度 | 内容 | 说明 |
---|---|---|
32 bit/64 bit | Mark Word | 存储对象的hashCode、分代年龄、锁等信息 |
32 bit/64 bit | Class Metadata Address | 存储到对象类型数据的指针(对象的地址) |
32 bit/64 bit | Array Length | 存储数组长度(数组类型的对象才有) |
2. Lock锁
① Lock锁的概述
- JVM使用
synchronized
关键字实现锁功能,隐式地获取和释放锁,给Java编程人员带来了一定的便捷性。却又增加了一些不便性: 如果需要手把手的获取和释放锁,就难以使用synchronized
关键字实现。 - 手把手获取和释放锁的场景:
获取锁A ----> 获取锁B ----> 释放锁A同时获取锁C ----> 释放锁B同时获取锁D
,以此类推。 - Java SE 5之后,在Java的并发包(
java.util.concurrent
, JUC)中提供了Lock接口及其实现类,用于实现锁功能。 - 与synchronized关键字相比,Lock接口需要显式地获取和释放锁,拥有获取和释放锁的可操作性、可中断的获取锁、超时获取锁等synchronized关键字不具有的同步特性。
- Lock接口相对于synchronized关键字所具有的主要特性:
- 尝试非阻塞的获取锁: 当前线程尝试获取锁,如果同一时刻没有其他线程获取到该锁,则获取锁成功,返回true;否则,获取锁失败,返回false。
- 可中断的获取锁: 如果当前线程使用可中断的方式获取锁,在获取锁的过程中可以响应中断,抛出中断异常。
- 超时获取锁: 当前线程尝试获取锁,如果不能立即获取锁则等待指定的时间。超时仍未获取到锁,则返回。
- 如何使用Lock接口:
Lock lock=new ReentrantLock();
lock.lock(); // 获取锁
try{
...
}finally {
lock.unlock(); // 释放锁
}
- 在try语句之前获取,如果在try语句中获取锁时发生了异常,则会抛出异常并自动释放锁。
- 在finally语句块中释放锁,保证无论程序正常运行结束、还是发生异常退出,都会执行finally语句块中的释放锁操作。
② Lock接口的API
- Lock是一个接口,定义了获取和释放锁的基本操作。
public interface Lock {
void lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
void unlock();
Condition newCondition();
}
- lock()方法: 获取锁(阻塞地获取锁)。如果该锁已被其他线程获取,则当前线程进行等待,直到获得锁后返回。
- lockInterruptibly()方法: 可中断的获取锁。与lock()方法不同的是,当前线程在获取的过程可以响应中断,抛出中断异常。
- tryLock()方法: 尝试非阻塞的获取锁。如果当前线程能立即获取锁,则返回
true
;否则,返回false
。
Lock lock = new ReentrantLock();
if (lock.tryLock()) {
try {
// manipulate protected state
} finally {
lock.unlock();
}
} else {
// perform alternative actions
}
- tryLock(long time, TimeUnit unit)方法: 超时获取锁。如果获取到锁,返回
true
;否则,返回false
。与tryLock()方法
相比,如果当前线程不能立即获取锁,则进入超时等待。有以下3种情况之一,该线程才能返回:
- 超时等待的过程中,当前线程获取到了锁。
- 超时等待的过程中,当前线程被中断。
- 超时等待结束,当前线程仍未获取到锁。
- unlock()方法: 释放锁。
- newCondition()方法: 获取锁的Condition对象,调用
condition.await()方法
当前线程进入等待状态,直到调用condition.signal()方法
或发生中断才返回。
3. 队列同步器(AQS)
① AQS概述
- 队列同步器(
AbstractQueuedSynchronizer
,AQS),以下简称同步器,是实现锁或其他同步组件的基础框架。 - 同步器的两个核心: 使用int成员变量表示的同步状态、具有FIFO顺序的同步队列。
- 同步状态的获取和释放有两种方式:独占式的获取和释放同步状态、共享式的释放和获取同步状态。
- 利用同步器实现同步组件的方式,以实现Lock接口的锁为例:
- 创建同步器的子类,并且重写同步器中的指定方法,如
tryAcquire()
,tryRelease()
,isHeldExclusively()
。注意: 同步器的子类一般都作为同步组件的静态内部类(private static class
)。 - 创建Lock接口的实现类,调用同步器的模板方法重写Lock接口中的方法(
lock()
、lockInterruptibly()
、tryLock()
、tryLock(time, unit)
、unlock()
、newCondition()
等主要方法)。 - 同步器的模板方法会调用重写方法,而重写方法会通过
getState()
、setState(int newState)
、compareAndSetState(int expect, int update)
,这三种方法访问或更新同步状态。
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
public class Mutex implements Lock {
//静态内部类,自定义同步器
private static class Sync extends AbstractQueuedSynchronizer{
//是否处于占用状态
protected boolean isHeldExclusively(){
return getState() == 1;
}
//当状态为0时获取锁
protected boolean tryAcquire(int arg){
if(compareAndSetState(0,1)){
setExclusiveOwnerThread(Thread.currentThread()); //设置成功,则代表获取了同步状态。
return true;
}
return false;
}
//释放锁,将状态设置为0
protected boolean tryRelease(int releases){
if(getState()==0)
throw new IllegalMonitorStateException();
setExclusiveOwnerThread(null);
setState(0);
return true;
}
//返回一个Condition,每个condition都包含了一个condition队列
Condition newCondition(){
return new ConditionObject();
}
}
//仅仅需要将操作代理到Sync上即可
private final Sync sync = new Sync();
public boolean isLocked(){
return sync.isHeldExclusively(); //是否处于同步状态
}
@Override
public void lock() {
sync.acquire(1); //获取锁,将状态设置为1
}
@Override
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
@Override
public boolean tryLock() {
return sync.tryAcquire(1);//尝试获取锁
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return sync.tryAcquireNanos(1,unit.toNanos(1000));
}
@Override
public void unlock() {
sync.release(1); //释放锁
}
@Override
public Condition newCondition() {
return sync.newCondition();
}
}
② 同步器的三种核心方法
- 同步器中使用int变量表示同步状态,提供三种核心方法访问或更新同步状态。重写同步器的指定方法,需要调用这三种核心方法来访问或更新同步状态。
int getState():
获取同步状态的当前值void setState(int newState):
设置同步状态的值boolean compareAndSetState(int expect, int update):
使用CAS原子性地设置同步状态的值,当同步状态的当前值为expect时,将其设置为给定值(update)。
③ 同步器的可重写方法
- 同步器的可重写方法主要包括:独占式的获取或释放同步状态、共享式的获取或释放同步状态、同步器是否在独占模式下被线程占用。
boolean tryAcquire(int arg):
独占式的获取同步状态,实现该方法需要先获取并判断同步状态是否符合预期,再通过CAS设置同步状态。(即使用compareAndSetState(expect, update)
方法)。可用于实现Lock接口中的tryLock()
方法。boolean tryRelease(int arg):
独占式的释放同步状态,等待获取同步状态的线程将有机会获取同步状态。int tryAcquireShared(int arg):
共享式的获取同步状态,返回大于等于0的值表示获取成功,否则获取失败。boolean tryReleaseShared(int arg):
共享式的释放同步状态boolean isHeldExclusively():
用于获取同步器是否在独占模式下被线程占用,一般用来表示是否被当前线程所独占。
④ 同步器中的模板方法
- 实现自定义的同步组件,需要调用同步器中模板方法,而模板方法内部则调用的是同步器的可重写方法。
- void acquire(int arg): 独占式的获取同步状态,会调用tryAcquire()重写方法。如果当前线程获取同步状态成功,则直接返回;否则,进入同步队列等待。
- void acquireInterruptibly(int arg): 与
acquire()
相同,但可以响应中断。如果当前线程获取同步状态失败,进入同步队列,直到获取成功或响应中断。如果响应中断,会抛出中断异常(InterruptException
)。 - boolean tryAcquireNanos(int arg, long nanosTimeout): 在
acquireInterruptibly()
的基础上增加了等待时间,如果等待超时,则返回false
;如果在等待的时间内,成功获取则返回true
。 - boolean release(int arg): 独占式的释放同步状态,并在释放同步状态之后,唤醒同步队列中第一个节点包含的线程。
- void acquireShared(int arg): 共享式的获取同步锁,与独占式获取的主要区别:同一时刻可以有多个线程获取到同步状态。
- void acquireSharedInterruptibly(int arg): 与
acquireShared()
相同,但可以响应中断。 - boolean tryAcquireSharedNanos(int arg, long nanosTimeout): 在
acquireSharedInterruptibly()
的基础上增加了等待时间。 - boolean releaseShared(int arg): 共享式的释放同步状态
- Collection getQueuedThreads(): 获取在同步队列中等待的线程集合。