Java多线程(三)显式锁和AQS
·什么是原子操作?怎么实现原子操作?
synchronized关键字是基于阻塞的锁机制,有几个问题:
1 被阻塞的线程优先级很高怎么办
2 拿到锁的线程一直不释放锁
3 有大量线程竞争怎么办,会消耗CPU,会有死锁或者活锁出现
4 力度太大,如计数器就不需要这么限制
CAS的原理(compare and swap):
指令级别保证这是一个原子操作
三个运算符: 内存地址V,期望值A,一个新值B
如果地址V上的值和期望的值A相等,那么就给地址V赋新值B
如果一直不是期望的值,就在循环(自旋、死循环)里面不断进行CAS,一直到成功为止
CAS会带来三个问题:
ABA问题:A-->B-->A第一次取的时候是A值,第二次放的时候还是A值,以为没有变化实际上变化了(解决方法: 添加版本号)
如果CAS操作长期不成功,cpu不断循环,开销问题
CAS只能保证单个变量的原子操作
CAS为什么要在循环里面做:

因为不能保证在做compareandset的时候的i就是之前取到的i,可能会有其他线程改变了i
·原子操作类的使用

AtomicInteger中包含方法:get()、getAndIncrement()、IncrementAndGet()等
AtomicReference中compareandSet()并不会改变原始对象
AtomicMarkableReference,boolean,只关心又没有人动过(ABA问题解决)
AtomicStampedReference,记录动过几次(ABA问题解决)
·显式锁:
synchronized关键字别名是内置锁,是由java语言特性本身提供的功能,但是所得获取和释放固化了,智能先获取,再释放,Lock接口灵活许多,synchronized在获取锁的过程中是不能够被中断的
synchronized代码简洁,获取锁可以被中断,超时获取锁,尝试获取锁的时候用Lock
package thread;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LockDemo {
//Lock是一个接口,java为我们提供了其实现类ReentrantLock
private Lock lock = new ReentrantLock();
private int count;
public void increment(){
//首先要拿到锁
lock.lock();
//拿到锁是一定要释放的,为了保证释放,要用finally
//这是一种使用锁的范式
try{
count++;
}finally {
lock.unlock();
}
}
public void inc2(){
count++;
}
}
可重入锁,应用场景有递归、syn方法调用syn方法时
reentrantLock和syn都是排他锁;读写锁:同一时刻允许多个线程同时访问,但是写线程访问的时候,所有的读和写都被阻塞。
ReadWriteLock,内部维护了一个读锁和一个写锁
package thread.rw;
import java.util.concurrent.TimeUnit;
/**
* 用syn关键字内置锁来实现商品服务接口
*/
public class UseSyn implements GoodsService {
private GoodsInfo goodsInfo;
public UseSyn(GoodsInfo goodsInfo) {
this.goodsInfo = goodsInfo;
}
@Override
public synchronized GoodsInfo getNum() {
try {
TimeUnit.MILLISECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
return this.goodsInfo;
}
@Override
public synchronized void setNum(int number) {
try {
TimeUnit.MILLISECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
goodsInfo.changeNumber(number);
}
}
用读写锁实现接口,效率会高很多(在读多写少的情况下)
package thread.rw;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class UseRwLock implements GoodsService {
private GoodsInfo goodsInfo;
private final ReadWriteLock lock = new ReentrantReadWriteLock();
private final Lock getLock = lock.readLock();
private final Lock setLock = lock.writeLock();
public UseRwLock(GoodsInfo goodsInfo) {
this.goodsInfo = goodsInfo;
}
@Override
public GoodsInfo getNum() {
getLock.lock();
try{
TimeUnit.MILLISECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
getLock.unlock();
}
return this.goodsInfo;
}
@Override
public void setNum(int number) {
setLock.lock();
try{
TimeUnit.MILLISECONDS.sleep(5);
goodsInfo.changeNumber(number);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
setLock.unlock();
}
}
}
·Condition接口
package thread.Condition;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ExpressCond {
public final static String CITY = "shanghai";
private int km;//里程数
private String site;//快递到达地点
private Lock kmLock = new ReentrantLock();
private Lock siteLock = new ReentrantLock();
private Condition kmCond = kmLock.newCondition();
private Condition siteCond = siteLock.newCondition();
public ExpressCond() {
}
public ExpressCond(int km, String site) {
this.km = km;
this.site = site;
}
/*
变化公里数,然后通知处于wait状态并需要处理公里数的线程
进行业务处理
*/
public void changeKm(){
kmLock.lock();
try{
this.km = 101;
//通知
//这也是等待通知模式的一个应用(类似于Wait、notify)
//不同于NotifyAll,这里只需要signal就可以了
//因为针对两个成员变量我们设置了两个Condition
kmCond.signal();
}finally {
kmLock.unlock();
}
}
public void changeSite(){
siteLock.lock();
try{
this.site = "beijing";
siteCond.signal();
}finally {
siteLock.unlock();
}
}
public void waitKm(){
kmLock.lock();
while (this.km<=100){
try{
kmCond.await();
System.out.println("check km thread"+
Thread.currentThread().getId()+
"is notified");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
kmLock.unlock();
}
}
System.out.println("the km is"+this.km);
}
public synchronized void waitSite(){
siteLock.lock();
while (CITY.equals(this.site)){
try{
siteCond.await();
System.out.println("check site thread"+
Thread.currentThread().getId()+
"is notified");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
siteLock.unlock();
}
}
System.out.println("the site is"+this.site);
}
一个锁上面可以挂多个Contidion,这里只用一个锁也是可以的,主要作用就是针对不同的条件去唤醒线程
·LockSupport工具
作用:阻塞一个线程、唤醒一个线程、构建同步组件的基础工具
以park开头的都是将线程阻塞、unpark开头的都是将线程唤醒
·AbstractQueuedSynchronizer(AQS)
显示锁、读写锁都是基于此,应用了模板方法设计模式
模板方法:
acquire、acquireInterruptibly、tryAcquireNanos(独占式获取锁)
acquireShared、acquireSharedInterruptibly、tryAcquireSharedNanos(共享式获取锁)
release(独占式释放锁)
releaseShared(共享式释放锁)
需要子类覆盖的流程方法:
tryAcquire独占式获取
tryRelease独占式释放
tryAcquireShared共享式获取
tryReleaseShared共享式释放
isHeldExclusively这个同步器是否处于独占模式
同步状态state:
getState():获取当前的同步状态
setState():设置当前同步状态
compareAndSetState():使用CAS设置状态,包装状态设置的原子性
模板设计模式:
父类定义了框架方法,但是里面的流程方法需要子类自己实现
比如读锁就是覆盖了共享方法
实现自己的锁:
package thread.aqs;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
/**
* 自己实现的锁,类似于reentrantLock
*/
public class selfLock implements Lock {
//state=1表示当前线程获取到了锁
//state=0表示当前线程没有获取到锁
private static class Sync extends AbstractQueuedSynchronizer{
//当前锁是不是被占用了
@Override
protected boolean isHeldExclusively() {
return getState()==1;
}
@Override
protected boolean tryAcquire(int arg) {
//当前线程拿到了锁
if (compareAndSetState(0,1)){
//表示当前拿到锁的式当前线程
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
@Override
protected boolean tryRelease(int arg) {
if (getState()==0){
throw new UnsupportedOperationException();
}
//当前没有线程独占了
setExclusiveOwnerThread(null);
//为什么这里不用原子变量去设置呢
//只有拿到锁得线程释放才有用,所以不需要
setState(0);
return true;
}
Condition newCondition(){
return new ConditionObject();
}
}
private final Sync sync = new Sync();
@Override
public void lock() {
sync.acquire(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(time));
}
@Override
public void unlock() {
sync.release(1);
}
@Override
public Condition newCondition() {
return sync.newCondition();
}
}

Node是AQS中定义的内部类
CANCELLED:线程等待超时或者被中断了,需要从队列里面移走
SIGNAL:后续的节点等待状态,当前节点通知后面节点去运行
CONDITION:当前节点处于等待队列
PROPAGATE:状态要往后面的节点传播
获取锁失败时将线程打包成一个Node放进同步队列
注意:往队列后面加的时候需要CAS(因为会有竞争)
队列头节点删除时不需要

························独占式·····························
观察AQS类中的acquire方法:

线程获取锁的方法,如果说子类覆盖的tryAcquire方法获取成功则跳出,否则通过addWaiter方法将该线程
加入同步队列的末尾,acquireQueued方法如下:

自旋操作,不断地判断前驱节点是不是头节点或者拿锁不成功的时候,就会进入一个阻塞状态(parkxxx,通过LockSupportl.park()方法来操作)

释放锁的release操作:
首先通过自己实现的tryRelease操作,先拿到头节点,再唤醒头节点的继承节点。
······························共享式(暂时跳过)···································

Condition等待队列是一个单链表,对于一个同步器来说同步队列只有一个,Condition队列声明多少个就会有多少个

await方法调用前提是获取了锁
·读写锁:一个sync里面只有一个状态,怎么来表示两个锁的状态呢?
每个读锁获得过几次锁都会记录在ThreadLocal里面

浙公网安备 33010602011771号