AQS
- 多线程存在数据不一致性
- volatile
- synchronized
- JUC包
- 锁分类
- 悲观锁:用于 写多,读少,
- 乐观锁:用于 读多,写少,<版本控制方式>
CAS
- 概念
- CompareAndSet
- CompareAndSwap:比较交换,一种无锁的原子算法,属于乐观锁
- 基本思想
- 给出一个期望值,与现有值比较,如果相等进行修改,如果不相等,那么就无法修改
- 表示方式:CAS (V, E, N),如果 V==E(期望值),将 N 赋值给 V
- 作用和优点:
- 实现复杂,但无锁(因此不存在阻塞),效率高<CPU吞吐量高>
ABA问题
出现:
- t1修改i(100-> 110)
- t2修改(110-> 100)
- t3修改(100-> 120),此时t3认为它拿到的100还是初始的100,出现ABA
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
public class CASDemo {
private static AtomicInteger atomicInteger = new AtomicInteger(0);
public static void main(String[] args) {
new Thread(() -> {
atomicInteger.compareAndSet(100, 110);
}).start();
new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(1);
atomicInteger.compareAndSet(110, 100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(1);
atomicInteger.compareAndSet(100, 120);
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
解决
- 使用 AtomicStampedReference
- 相当于加了版本<时间戳>
- 思路:原来是 A-B-A 加版本后:A1-B2-A3
public class CASDemo2 {
/**
* 给出值后,给出版本
*/
private static AtomicStampedReference asr =
new AtomicStampedReference(100, 1);
public static void main(String[] args) {
new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(2);
System.out.println(asr.compareAndSet(100, 110, asr.getStamp(), asr.getStamp()+1));
System.out.println(asr.compareAndSet(110, 100, asr.getStamp(), asr.getStamp()+1));
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
/**
* 先拿到版本,停留一段时间等待其他线程修改
* 此时该线程执行时,版本不对,修改失败
*/
new Thread(() -> {
try {
int stamp = asr.getStamp();
TimeUnit.SECONDS.sleep(3);
System.out.println(asr.compareAndSet(110, 100, stamp, stamp+1));
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
CAS 应用场景
(1) 适用于简单的数据计算
(2) 适合线程冲突少的场景
AQS
- 概念
-
* AbstractQueuedSychronizer 同步发生器,构造 JUC、Lock等
- 基本思想
-
* 通过内置得到FIFO同步队列来完成线程争夺资源的管理工作
- CLH同步队列
自定义锁
package test.aqsImpl;
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 MyLock implements Lock {
private Helper helper = new Helper();
private class Helper extends AbstractQueuedSynchronizer {
@Override
protected boolean tryAcquire(int arg) {
int state = getState();
if(state == 0) {
//使用 CAS 修改 state
if (compareAndSetState(0, arg)) {
//修改成功,设置当前线程占有资源
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
}
return false;
}
@Override
protected boolean tryRelease(int arg) {
int state = getState() - arg;
boolean flag = false;
if(state == 0) {
//如果 state = 0 说明其他线程可以占有该资源
setExclusiveOwnerThread(null);
return true;
}
// 这里不存在线程安全问题,因为释放前已经占有了资源
setState(state);
return false;
}
public Condition newConditionObject() {
return new ConditionObject();
}
}
@Override
public void lock() {
helper.acquire(1);
}
/**
* 以中断方式获取锁
* @throws InterruptedException
*/
@Override
public void lockInterruptibly() throws InterruptedException {
helper.acquireInterruptibly(1);
}
@Override
public boolean tryLock() {
return helper.tryAcquire(1);
}
/**
* 多久没有获取锁就抛出异常
* @param time
* @param unit
* @return
* @throws InterruptedException
*/
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return helper.tryAcquireNanos(1, unit.toNanos(time));
}
@Override
public void unlock() {
helper.release(1);
}
/**
* 在某些条件下 进行加锁
* @return
*/
@Override
public Condition newCondition() {
return helper.newConditionObject();
}
}
测试,解决数据不一致问题,输出顺序可能时乱序,但是保证了数据时一致的,也就是不会出现重复值
package test.aqsImpl;
public class TestMyLock {
private int m = 0;
private MyLock myLock = new MyLock();
public int next() {
myLock.lock();
try {
//TimeUnit.SECONDS.sleep(2);
return m++;
} finally {
myLock.unlock();
}
}
public static void main(String[] args) {
TestMyLock testMyLock = new TestMyLock();
for (int i = 0; i < 20; i++) {
new Thread(()->{
System.out.println(testMyLock.next());
}).start();
}
}
}
修改使其具有可重入性
package test.aqsImpl;
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 MyLock implements Lock {
private Helper helper = new Helper();
private class Helper extends AbstractQueuedSynchronizer {
@Override
protected boolean tryAcquire(int arg) {
int state = getState();
if(state == 0) {
//使用 CAS 修改 state
if (compareAndSetState(0, arg)) {
//修改成功,设置当前线程占有资源
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
//修改具有可重入性
} else if(Thread.currentThread() == getExclusiveOwnerThread()) {
setState(getState() + arg);
return true;
}
return false;
}
@Override
protected boolean tryRelease(int arg) {
int state = getState() - arg;
boolean flag = false;
if(state == 0) {
//如果 state = 0 说明其他线程可以占有该资源
setExclusiveOwnerThread(null);
return true;
}
// 这里不存在线程安全问题,因为释放前已经占有了资源
setState(state);
return false;
}
public Condition newConditionObject() {
return new ConditionObject();
}
}
@Override
public void lock() {
helper.acquire(1);
}
/**
* 以中断方式获取锁
* @throws InterruptedException
*/
@Override
public void lockInterruptibly() throws InterruptedException {
helper.acquireInterruptibly(1);
}
@Override
public boolean tryLock() {
return helper.tryAcquire(1);
}
/**
* 多久没有获取锁就抛出异常
* @param time
* @param unit
* @return
* @throws InterruptedException
*/
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return helper.tryAcquireNanos(1, unit.toNanos(time));
}
@Override
public void unlock() {
helper.release(1);
}
/**
* 在某些条件下 进行加锁
* @return
*/
@Override
public Condition newCondition() {
return helper.newConditionObject();
}
}
Lock的实现类
ReentrantLock
* 公平锁
* 非公平锁
- ReentrantReadWriteLock implement ReadWriteLock
- 读写锁
CountDownLatch
- 类似于计数器,比如有个任务A,必须等待其他4个任务完成才能执行
- 每当一个任务完成后,计数器减一,当计数器为0,那么可以恢复执行任务
package test.aqsImpl;
import java.util.concurrent.CountDownLatch;
public class CountDownLatchTest {
public static void main(String[] args) {
String[] works = new String[]{"work1", "work2", "work3", "work4"};
CountDownLatch countDownLatch = new CountDownLatch(works.length);
for (int i = 0; i < works.length; i++) {
int finalI = i;
new Thread(()->{
try {
System.out.printf("Doing %s\n", works[finalI]);
//完成任务 信号量减一
countDownLatch.countDown();
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
try {
//完成所有任务后唤醒
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
CyclicBarrier
- 原理:每个线程执行时都会碰到一个屏障,直到所有线程准备完成,屏障才会打开,使所有线程继续往下执行
- 首先传入一个 初始值 pairs,当每个线程准备完成后,state + 1,与初始值比较,当state = pairs,所有线程准备完成,可以继续执行自己剩余的任务
package test.aqsImpl;
import java.util.Random;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.TimeUnit;
public class CyclicBarrierTest {
public static void main(String[] args) {
CyclicBarrier cyclicBarrier = new CyclicBarrier(8);
for (int i = 0; i < cyclicBarrier.getParties(); i++) {
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(new Random().nextInt(2));
System.out.println(Thread.currentThread().getName() + "准备好了");
//准备工作完成后 唤醒状态
cyclicBarrier.await();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "开始执行");
}).start();
}
}
}
效果
Semaphore 信号量
- 信号量是可以使用的资源数量
- 请求资源 aquire()
- 使用资源
- 释放资源 release()
- 模拟停车场
package aqsImpl.semaphore;
import java.util.Random;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
/**
* 模拟停车场
* 现有2个车位,5辆车待停
*
* 如果车位占满,那么其他车就无法停入,除非在停车离开
*/
public class ParkStation {
public static void main(String[] args) {
//创建信号量
Semaphore semaphore = new Semaphore(2, true);
System.out.println("parking open ---");
for (int i = 0; i < 5; i++) {
new Thread(()->{
try {
semaphore.acquire();
} catch (Exception e) {
e.printStackTrace();
}
try {
System.out.println(Thread.currentThread() + " parked");
TimeUnit.SECONDS.sleep(new Random().nextInt(5));
} catch (InterruptedException e) {
e.printStackTrace();
}
semaphore.release();
System.out.println(Thread.currentThread() + " leaved");
}).start();
}
}
}