锁
引言
上篇讲到高并发的三大特性,包括可见性,有序性和原子性,也是高并发编程需要面对的三个问题。最后讲到用锁保障操作的原子性,这篇就来介绍下锁的相关知识。
JVM中的两种锁
重量级锁synchronized,需要经过操作系统的调度,悲观锁
轻量级锁,CAS实现,不需要要经过操作系统的调度,亦称为乐观锁、无锁、自旋锁。
CAS
概念
CAS全称compare and swap,CAS操作会先取得对象当前的值,要对其进行修改时,首先判断该对象是否被修改过,没有修改则更新,修改过取当前值重复上述操作。
CAS操作的原子性
CAS是不加锁的,可以多个线程对一个对象进行CAS操作,那么怎么保证原子性呢,实际CAS的底层实现是lock cmpxchg,也是加锁的。
java中CAS的常见实现类
AtomicInteger:对应Integer类型,线程安全
public final int incrementAndGet() {
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return next;
}
}
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
Unsafe:底层是通过地址偏移,对JVM中内存进行直接操作
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
synchronized
发展
早期只有重量级锁,每次使用需要jvm向内核申请 ,发起80中断。
现在使用会随着线程的竞争,将锁一步步进行升级,升级过程为:无锁状态->偏向锁->自旋锁->重量级锁。升级过程中锁的信息会放在锁定对象的markdown里面。
锁升级过程
-
开始对象处于无锁状态
-
有线程获得锁,升级为偏向锁
-
有线程竞争,升级为自旋锁
-
竞争的线程达到一定数量,升级为重量级锁
其他特性
- 可重入性
- wait释放锁,sleep不释放锁,join只会释放Thread的锁
- synichronized锁的是对象,不是代码
- 保障可见性
- 临界执行时间长,等的线程多时适合使用
JUC中的各种锁
ReentrantLock
使用场景
代替synchronized加锁,保证原子性
常规使用
Lock lock = new ReentrantLock();
void m1() {
try {
lock.lock(); //与synchronized类似,可替代其
for (int i = 0; i < 10; i++) {
TimeUnit.SECONDS.sleep(1);
System.out.println(i);
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
设置为公平锁
ReentrantLock lock=new ReentrantLock(true);
尝试锁定
lock.tryLock(5, TimeUnit.SECONDS)
设置为可打断
lock.lockInterruptibly(); //可以对interrupt()方法做出响应
CountDownLatch
使用场景
阻塞一个线程,由其他线程调用countDown的次数决定是否打开阻塞。
常规使用
private static void usingCountDownLatch() {
Thread[] threads = new Thread[10];
CountDownLatch latch = new CountDownLatch(threads.length);//设置门闩数
for(int i=0; i<threads.length; i++) {
threads[i] = new Thread(()->{
// 将门闩减一
latch.countDown();
});
}
for (int i = 0; i < threads.length; i++) {
threads[i].start();
}
try {
latch.await();// 阻塞住
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("end latch");
}
Semaphore
使用场景
指定线程数限流
常规使用
//允许一个线程同时执行
Semaphore s = new Semaphore(1);
new Thread(()->{
try {
// 获取信号量,可以往下执行
s.acquire();
System.out.println("T1 running...");
// 释放一个信号量,其他线程可以再获得
// s.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
new Thread(()->{
try {
s.acquire();
System.out.println("T2 running...");
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
CyclicBarrier
使用场景
阻塞一定数目线程,线程数满了后,执行指定方法。阻塞的线程也能执行下去。
常规使用
public static void main(String[] args) {
CyclicBarrier barrier = new CyclicBarrier(20, () -> System.out.println("满人"));
for(int i=0; i<100; i++) {
new Thread(()->{
try {
barrier.await();
System.out.println("我执行了");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
}
}
Phaser
使用场景
设置一个个阶段,线程达到此阶段阻塞住,每个阶段达到一定的线程后,执行该阶段的任务,并释放阻塞。
常规使用
public static void main(String[] args) {
MarriagePhaser phaser = new MarriagePhaser();
// 设置每个阶段需要线程到达数
phaser.bulkRegister(5);
for(int i=0; i<5; i++) {
final int nameIndex = i;
new Thread(()->{
Person p = new Person("person " + nameIndex);
p.arrive();
// 阻塞,达到数目后打开,phaser的阶段提升一级
phaser.arriveAndAwaitAdvance();
p.eat();
phaser.arriveAndAwaitAdvance();
p.leave();
phaser.arriveAndAwaitAdvance();
}).start();
}
}
static class MarriagePhaser extends Phaser {
// 满足线程数时执行的方法
@Override
protected boolean onAdvance(int phase, int registeredParties) {
switch (phase) {
case 0:
System.out.println("所有人到齐了!");
return false;
case 1:
System.out.println("所有人吃完了!");
return false;
case 2:
System.out.println("所有人离开了!");
System.out.println("婚礼结束!");
return true;
default:
return true;
}
}
}
LockSupport
使用场景
可用来替代wait和 notify
常规使用
LockSupport.park();//阻塞
LockSupport.unpark(t); // 打开线程的阻塞
ReadWriteLock
使用场景
读多写少,读不加锁,写加锁
常规使用
static ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
static Lock readLock = readWriteLock.readLock(); //读锁
static Lock writeLock = readWriteLock.writeLock();//写锁
Exchanger
使用场景
两个线程一起交换数据后,一起往下走
常规使用
static Exchanger<String> exchanger = new Exchanger<>();
public static void main(String[] args) {
new Thread(()->{
String s = "T1";
try {
s = exchanger.exchange(s);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " " + s);
}, "t1").start();
new Thread(()->{
String s = "T2";
try {
s = exchanger.exchange(s);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " " + s);
}, "t2").start();
}
AQS
上述JUC的锁都是AQS实现的
AQS原理
CAS + AQS + volatile
- 变量state由voaltile修饰,根据子类的实现取不同的值 (重入次数)
- 双向链表,实现任务队列,放来操作state的队列。
- CAS判断能否拿锁,拿不到锁队尾入队列。
- 头部节点操作state,由于是双端队列,头部第二个节点可以判断头节点是否执行结束,结束了移出其,并获得锁。