1.分类

18.锁机制_读锁

2.悲观锁和乐观锁

(1).悲观锁

概念:多个线程操作共享资源,存在竞争,需要加锁。

18.锁机制_java_02


过程:多个线程操作共享数据,一个线程获取操作权后,会给共享数据加锁,其它线程处于等待状态。

实现:synchronized和各种lock的实现类,如ReentrantLock和ReentrantReadWriteLock

使用场景:并发写操作多

悲观锁的问题

  • 阻塞和唤醒带来的系统开销,影响效率
  • 永久阻塞(持有锁的线程遇到无限循环或者死锁)

(2).乐观锁

概念:多个线程操作共享资源,不存在竞争,不需要加锁,只要执行完毕后校验一下即可。

18.锁机制_java_03


过程:多个线程操作共享数据,一个线程获取操作权后,不会给共享数据加锁,它会先记录一下共享数据的值,等待执行完毕后,该线程会判断共享数据的值有没有发生变化,如果没有发生变化,则该线程才会去修改,如果发生变化则该线程不进行此次修改。但是可能会发生ABA问题。

实现:java.util.concurrent.atomic包下的原子类

使用场景:并发读操作多3.可重入锁

(1).概念

可重入锁又称之为递归锁,是指同一个线程在外层方法获取了锁,进入内层方法会自动获取锁。

18.锁机制_jvm_04

(2).案例

public class RecursionDemo {

private static ReentrantLock lock = new ReentrantLock();

private static void accessResource() {
lock.lock();
try {
System.out.println("已经对资源进行了处理");
if (lock.getHoldCount() < 5) {
System.out.println(lock.getHoldCount());
accessResource();
System.out.println(lock.getHoldCount());
}
} finally {
lock.unlock();
}
}

public static void main(String[] args) {
accessResource();
}
}
public synchronized void mehtodA() throws Exception{
mehtodB();
}

public synchronized void mehtodB() throws Exception{
//do something
}

(3).实现
synchronized和各种lock的实现类,如ReentrantLock和ReentrantReadWriteLock。

(4).原理
有一个state变量,用于计算锁被获取次数,同一个线程可以重复获取,获取一次state就加1,释放1次,state就减1,如果state为0,别的线程就可以竞争锁。

4.公平锁和非公平锁

(1).公平锁

概念:指多个线程按照申请锁的顺序来获取锁,这里类似排队买票,先来的人先买,后来的人在队尾排着,这是公平的。

18.锁机制_System_05

static final class FairSync extends Sync {
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
//先判断队首是不是自己
if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
} else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
}

(2).非公平锁

概念:指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁,在高并发环境下,有可能造成优先级翻转,饥饿的状态(某个线程一直得不到锁)。

18.锁机制_java_06


为什么:Java模式使用非公平锁,目的是提高效率,避免唤醒带来的空档期。

abstract static class Sync extends AbstractQueuedSynchronizer {
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
} else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
}

5.独占锁和共享锁

(1).独占锁

概念:独占锁是指锁一次只能被一个线程所持有。如果一个线程对数据加上独占锁后,那么其他线程不能再对该数据加任何类型的锁。获得独占锁的线程即能读数据又能修改数据。

18.锁机制_System_07


实现:synchronized和各种lock的实现类,如ReentrantLock和ReentrantReadWriteLock。(2).共享锁

概念:共享锁是指锁可被多个线程所持有。如果一个线程对数据加上共享锁后,那么其他线程只能对数据再加共享锁,不能加独占锁。获得共享锁的线程只能读数据,不能修改数据。

18.锁机制_jvm_08


实现:ReentrantReadWriteLock。

public class CinemaReadWrite {
private static ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
private static ReentrantReadWriteLock.ReadLock readLock = reentrantReadWriteLock.readLock();
private static ReentrantReadWriteLock.WriteLock writeLock = reentrantReadWriteLock.writeLock();

private static void read() {
readLock.lock();
try {
System.out.println(Thread.currentThread().getName() + "得到了读锁,正在读取");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println(Thread.currentThread().getName() + "释放读锁");
readLock.unlock();
}
}

private static void write() {
writeLock.lock();
try {
System.out.println(Thread.currentThread().getName() + "得到了写锁,正在写入");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println(Thread.currentThread().getName() + "释放写锁");
writeLock.unlock();
}
}

public static void main(String[] args) {
new Thread(() -> read(), "Thread1").start();
new Thread(() -> read(), "Thread2").start();
new Thread(() -> write(), "Thread3").start();
new Thread(() -> write(), "Thread4").start();
}
}

非公平锁,读锁不插队(队首是写操作)

public class CinemaReadWriteQueue {
private static ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock(false);
private static ReentrantReadWriteLock.ReadLock readLock = reentrantReadWriteLock.readLock();
private static ReentrantReadWriteLock.WriteLock writeLock = reentrantReadWriteLock.writeLock();

private static void read() {
readLock.lock();
try {
System.out.println(Thread.currentThread().getName() + "得到了读锁,正在读取");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println(Thread.currentThread().getName() + "释放读锁");
readLock.unlock();
}
}

private static void write() {
writeLock.lock();
try {
System.out.println(Thread.currentThread().getName() + "得到了写锁,正在写入");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println(Thread.currentThread().getName() + "释放写锁");
writeLock.unlock();
}
}

public static void main(String[] args) {
new Thread(() -> write(), "Thread1").start();
new Thread(() -> read(), "Thread2").start();
new Thread(() -> read(), "Thread3").start();
new Thread(() -> write(), "Thread4").start();
new Thread(() -> read(), "Thread5").start();
}
}
Thread1得到了写锁,正在写入
Thread1释放写锁
Thread2得到了读锁,正在读取
Thread3得到了读锁,正在读取
Thread3释放读锁
Thread2释放读锁
Thread4得到了写锁,正在写入
Thread4释放写锁
Thread5得到了读锁,正在读取
Thread5释放读锁

非公平锁,读锁插队(队首是读操作)

public class NonfairBargeDemo {
private static ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock(true);
private static ReentrantReadWriteLock.ReadLock readLock = reentrantReadWriteLock.readLock();
private static ReentrantReadWriteLock.WriteLock writeLock = reentrantReadWriteLock.writeLock();

private static void read() {
System.out.println(Thread.currentThread().getName() + "开始尝试获取读锁");
readLock.lock();
try {
System.out.println(Thread.currentThread().getName() + "得到读锁,正在读取");
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
} finally {
System.out.println(Thread.currentThread().getName() + "释放读锁");
readLock.unlock();
}
}

private static void write() {
System.out.println(Thread.currentThread().getName() + "开始尝试获取写锁");
writeLock.lock();
try {
System.out.println(Thread.currentThread().getName() + "得到写锁,正在写入");
try {
Thread.sleep(40);
} catch (InterruptedException e) {
e.printStackTrace();
}
} finally {
System.out.println(Thread.currentThread().getName() + "释放写锁");
writeLock.unlock();
}
}

public static void main(String[] args) {
new Thread(() -> write(), "Thread1").start();
new Thread(() -> read(), "Thread2").start();
new Thread(() -> read(), "Thread3").start();
new Thread(() -> write(), "Thread4").start();
new Thread(() -> read(), "Thread5").start();
new Thread(new Runnable() {
@Override
public void run() {
Thread thread[] = new Thread[1000];
for (int i = 0; i < 1000; i++) {
thread[i] = new Thread(() -> read(), "子线程创建的Thread" + i);
}
for (int i = 0; i < 1000; i++) {
thread[i].start();
}
}
}).start();
}
}

写锁降级到读锁,读锁升级到写锁(导致死锁)

public class Upgrading {
private static ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock(false);
private static ReentrantReadWriteLock.ReadLock readLock = reentrantReadWriteLock.readLock();
private static ReentrantReadWriteLock.WriteLock writeLock = reentrantReadWriteLock.writeLock();

private static void readUpgrading() {
readLock.lock();
try {
System.out.println(Thread.currentThread().getName() + "得到了读锁,正在读取");
Thread.sleep(1000);
System.out.println("升级会带来阻塞");
writeLock.lock();
System.out.println(Thread.currentThread().getName() + "获取到了写锁,升级成功");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println(Thread.currentThread().getName() + "释放读锁");
readLock.unlock();
}
}

private static void writeDowngrading() {
writeLock.lock();
try {
System.out.println(Thread.currentThread().getName() + "得到了写锁,正在写入");
Thread.sleep(1000);
readLock.lock();
System.out.println("在不释放写锁的情况下,直接获取读锁,成功降级");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
readLock.unlock();
System.out.println(Thread.currentThread().getName() + "释放写锁");
writeLock.unlock();
}
}

public static void main(String[] args) throws InterruptedException {
System.out.println("先演示降级是可以的");
Thread thread1 = new Thread(() -> writeDowngrading(), "Thread1");
thread1.start();

System.out.println("演示升级是不行的");
Thread thread2 = new Thread(() -> readUpgrading(), "Thread2");
thread2.start();
}
}

6.自旋锁
(1).自旋的前提

  • 锁锁住共享资源的时间较短
  • 阻塞或唤醒一个Java线程需要操作系统切换CPU状态来完成,这种状态转换需要耗费处理器时间

(2).实施
线程继续占有CPU,尝试再次获取锁

(3).缺点
一直占用CPU的执行权,空耗资源

(4).适用场景
多核CPU,并发不是很多的情况下

(5).代码示例

public class SpinLock {
private AtomicReference<Thread> sign = new AtomicReference<>();

public void lock() {
Thread current = Thread.currentThread();
while (!sign.compareAndSet(null, current)) {
System.out.println("自旋获取失败,再次尝试");
}
}

public void unlock() {
Thread current = Thread.currentThread();
sign.compareAndSet(current, null);
}

public static void main(String[] args) {
SpinLock spinLock = new SpinLock();
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "开始尝试获取自旋锁");
spinLock.lock();
System.out.println(Thread.currentThread().getName() + "获取到了自旋锁");
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
spinLock.unlock();
System.out.println(Thread.currentThread().getName() + "释放了自旋锁");
}
}
};
Thread thread1 = new Thread(runnable);
Thread thread2 = new Thread(runnable);
thread1.start();
thread2.start();
}
}

7.锁优化
(1).锁粗化
锁粗化就是将多个同步块的数量减少,并将单个同步块的作用范围扩大,本质上就是将多次上锁、解锁的请求合并为一次同步请求。

private static final Object lock = new Object();

for(int i = 0;i < 100; i++) {
synchronized(lock){
//do something
}
}
private static final Object lock = new Object();

synchronized(lock){
for(int i = 0;i < 100; i++) {
//do something
}
}

(2).锁消除
锁消除是指虚拟机编译器在运行时检测到了共享数据没有竞争的锁,从而将这些锁进行消除。

public String test(String s1, String s2){
StringBuffer stringBuffer = new StringBuffer();
stringBuffer.append(s1);
stringBuffer.append(s2);
return stringBuffer.toString();
}

test方法中三个变量s1,s2和stringBuffer, 它们都是局部变量,局部变量存储在栈的,而栈是线程私有的,所以就算有多个线程访问test方法也是线程安全的。

8.锁升级
(1).介绍
JDK1.6为了提升性能减少获得锁和释放锁所带来的消耗,引入了4种锁的状态,无锁、偏向锁、轻量级锁和重量级锁,它会随着多线程的竞争情况逐渐升级,但不能降级。

(2).无锁
加锁的资源不存在竞争。

(3).偏向锁
如果一个加锁的资源,从头到尾只有一个线程访问,那么JVM就会给锁设置为偏向锁,通过控制对象的Mark Word的锁标志位设置。

(4).轻量级锁
如果有另一个线程来竞争锁资源时,线程会检查锁的持有者是否是自己,如果不是则偏向锁模式失效,JVM就会给锁设置为轻量级锁。线程会通过自旋来获取锁资源,如果获取到,则仍然为轻量级锁,如果没有获取到,则轻量级锁膨胀为重量级锁。

(5).重量级锁
升级到重量级锁就是互斥锁了,一个线程拿到锁,其余线程都会处于阻塞等待状态。