java线程同步原理主要2个概念:互斥(mutual exclusion)和可见性。
其中互斥保证了在同一时刻只有一个线程可以访问临界区,可见性保证一个线程对共享变量的修改能够及时被其他线程看到。 第3节我们简单介绍了内存可见性相关内容,并在4、5分别介绍了因缓存一致性导致的伪共享问题和代码重排序。这一章节主要了解下互斥相关。 Java提供了2种锁机制来控制多个线程对共享资源的互斥访问。下文分别从可重入性、公平性了解这2种锁的实现方式。
-
- JVM实现的synchronized内置锁,在软件层面依赖JVM
-
- JDK实现的ReentrantLock显式锁,在硬件层面依赖特殊的CPU指令
锁
锁的名字千千万,但首先锁分为内置锁/隐式锁/自动锁和显式锁。关于锁的共享与独占,是否可重入(避免死锁),公平非公平,可中断锁的概念会在之后进行介绍。本节主要了解说明内置锁和显式锁。
synchronized
以最简单的synchronized为例,synchronized(this)中的块,可以保证同时只有一个线程执行。
public class Counter{
private int count = 0;
public int inc(){
synchronized(this){
return ++count;
}
}
}
synchronized使用方法
可以修饰在不同层级:修饰实例方法、修饰静态方法、修饰代码块。通过在对象->对象头->mark word标记字段
中修改锁状态标志,
- 修饰实例方法,常量池多了ACC_SYNCHRONIZED标示符,根据标示符实现方法同步。调用指令时,会检查标示是否存在,如果设置执行线程就先获取monitor,获取后才能执行方法体,执行完后释放monitor。
public synchronized void synchronisedCalculate() {
setSum(getSum() + 1);
}
- 修饰静态方法
public static synchronized void syncStaticCalculate() {
staticSum = staticSum + 1;
}
- 修饰代码块,修饰代码块时会通过monitorenter和monitorexit指令获取Monitor所有权和退出Monitor。
public void performSynchronisedTask() {
synchronized (this) {
setCount(getCount()+1);
}
}
锁升级
除了无锁状态,Synchronized还有偏向锁、轻量级锁、重量级锁概念。
Lock
Lock提供了一种无条件的、可轮询的、定时的以及可中断的锁获取操作,所有加锁和解锁的方法都是显式的。在 Lock的实现中必须提供与内部锁相同的内存可见性语义,但在加锁语义、调度算法、顺序保证以及性能特性等方面可以有所不同。以最简单的Lock为例,lock()加锁unlock()释放锁。Lock中while(isLocked)为自旋锁,
public class Counter{
private Lock lock = new Lock();
private int count = 0;
public int inc(){
lock.lock();
int newCount = ++count;
lock.unlock();
return newCount;
}
}
public class Lock{
private boolean isLocked = false;
public synchronized void lock()
throws InterruptedException{
while(isLocked){
wait();
}
isLocked = true;
}
public synchronized void unlock(){
isLocked = false;
notify();
}
}
Synchronized和Lock区别
无法中断、无法实现非阻塞这些都是不满足于使用隐式锁synchronized的原因。Lock在加锁和内存上提供的语义与与内置锁相同,此外它还提供了一些其他功能,包括定时的锁等待、可中断的锁等待、公平性,以及实现非块结构的加锁。
- Lock加解锁的顺序灵活
- Lock可通过lockInterruptibly中断,Synchronized不可中断
- Lock可以被多个线程持有例如读写锁ReadWriteLock中读锁/StampedLock实现乐观读(多个线程并发读,一个线程写),Synchronized没有并发读
- Lock可设置为公平锁或非公平锁
- Lock底层原理是AQS和CAS,Synchronized底层是互斥Mutex Lock
- Lock的等待和唤醒通过Condition的await和signal实现,Synchronized的等待和唤醒通过wait和notify实现。都为同步队列和等待队列的切换。
- Lock有和Synchronized都只有一个同步队列,但是Lock有多个等待队列,Synchronized只有一个等待队列。
- Lock的tryLock可以实现非阻塞,Synchronized只有阻塞
- Lock支持定时的锁等待,可以通过tryLock设置超时机制,synchronized无超时机制
- Lock可定时可轮训
相关概念
- 线程同步
同步是在互斥的基础上,通过其他机制实现访问者对资源的有序访问。
- 同步队列
等待竞争锁的队列
- 等待队列
等待被唤醒后进入同步队列以获取竞争锁的队列
参考:
- https://jenkov.com/tutorials/java-concurrency/locks.html
- https://cloud.tencent.com/developer/article/1465413
- https://www.cnblogs.com/CarpenterLee/p/7896361.html
- https://www.baeldung.com/java-synchronized
- https://segmentfault.com/a/1190000041268785
- https://segmentfault.com/a/1190000023150863
- 《Java并发编程实战》第13章