(1)一个人只要自己不放弃自己,整个世界也不会放弃你.
(2)天生我才必有大用
(3)不能忍受学习之苦就一定要忍受生活之苦,这是多么痛苦而深刻的领悟.
(4)做难事必有所得
(5)精神乃真正的刀锋
(6)战胜对手有两次,第一次在内心中.
(7)好好活就是做有意义的事情.
(8)亡羊补牢,为时未晚
(9)科技领域,没有捷径与投机取巧。
(10)有实力,一年365天都是应聘的旺季,没实力,天天都是应聘的淡季。
(11)基础不牢,地动天摇
(12)写博客初心:成长自己,辅助他人。当某一天离开人世,希望博客中的思想还能帮人指引方向.
(13)编写实属不易,若喜欢或者对你有帮助记得点赞+关注或者收藏哦~
AQS底层原理、运用和volatile详解
文章目录
1.AbstractQueuedSynchronizer
1.1什么是AQS,为何学习它?
public abstract class AbstractOwnableSynchronizer
implements java.io.Serializable {
(1)它是JDK并发编程的基础构建。
(2)AQS是用来构建同步组件的
(3)AbstractQueuedSynchronizer
实现并发编程的时候,都要依赖state状态作文章,就是要去修改其所处状态。
private volatile int state;
1.1.1AQS使用方式和其中的设计模式
(1)AQS在使用方式上面采用的是继承的方式
(2)是在同步工具类的内部,通过定义内部类继承AQS(AbstractQueuedSynchronizer),这个同步工具类对外所暴露的方法,全部将内部类相关的一些操作都封装起来了。使我们觉得没有任何与AQS相关的东西。
(3)如果要自定义一个AQS同步工具类,则需要了解其采用的设计模式。
通常采用的是模板方法的设计模式。
1.1.1.1模版方法设计模式
(1)声明执行一件事情的模版,先做什么,后做什么,最后做什么,即规定了必须要做哪些事情,但不规定具体怎么做这些事情。
/**
* 类说明:抽象蛋糕模型
*/
public abstract class AbstractCake {
protected abstract void shape();/*造型*/
protected abstract void apply();/*涂抹*/
protected abstract void brake();/*烤面包*/
/*做个蛋糕 */
public final void run(){
this.shape();
this.apply();
this.brake();
}
}
(2)具体实现
/**
* 类说明:芝士蛋糕
*/
public class CheeseCake extends AbstractCake {
@Override
protected void shape() {
System.out.println("芝士蛋糕造型");
}
@Override
protected void apply() {
System.out.println("芝士蛋糕涂抹 芝士");
}
@Override
protected void brake() {
System.out.println("芝士蛋糕烘焙 5 min");
}
}
/**
* 类说明:奶油蛋糕
*/
public class CreamCake extends AbstractCake {
@Override
protected void shape() {
System.out.println("奶油蛋糕造型");
}
@Override
protected void apply() {
System.out.println("奶油蛋糕涂抹");
}
@Override
protected void brake() {
System.out.println("奶油蛋糕烘焙");
}
}
public class MouseCake extends AbstractCake {
@Override
protected void shape() {
System.out.println("慕斯蛋糕造型");
}
@Override
protected void apply() {
System.out.println("慕斯蛋糕涂抹");
}
@Override
protected void brake() {
System.out.println("慕斯蛋糕烘焙");
}
}
(3)生产蛋糕
/**
* 类说明:生产蛋糕
*/
public class MakeCake {
public static void main(String[] args) {
AbstractCake cake = new CheeseCake();
AbstractCake cake2 = new CreamCake();
//AbstractCake cake = new MouseCake();
cake2.run();
}
}
1.1.1.2Android模版方法设计模式的应用
(1)Android的View绘制规定了子类必须实现onDraw()与dispatchDraw()方法,具体的实现细节在子类中去完成。
1.1.1.3自己需要实现AQS同步工具类
(1)也可以采用模版方法设计模式
(2)可以实现其中的几个方法
1.1.2了解其中的方法
1.1.3实现一个类似于ReentrantLock的锁
(1)任何一个线程进来,都要先拿到一把锁,拿到一把锁以后,才能执行里面相关的业务代码,这就是常用的独占锁。
(2)要标记某个线程已经拿到了锁,改为1.
(3)lock与unlock()一定要实现
(4)lock与unlock()的实现,就需要借助于AQS,它是一个模版方法模式,而且在同步工具类的内部类中需要继承AbstractQueuedSynchronizer。
1.1.3.1案例
/**
*类说明:实现我们自己独占锁,不可重入
*/
public class SelfLock implements Lock {
// 静态内部类,自定义同步器
private static class Sync extends AbstractQueuedSynchronizer {
/*判断处于占用状态*/
@Override
protected boolean isHeldExclusively() {
return getState()==1;
}
/*获得锁*/
@Override
protected boolean tryAcquire(int arg) {
/*
1.尝试使用compareAndSetState()期望当前这个锁没有人拿,于是将state状态由0改为1
表示我要去拿这个锁。
2.compareAndSetState方法返回true,表示锁已经拿到
3.将当前线程记录一下,告诉别人现在这把锁我独占了,你们都不要用。
4.compareAndSetState方法返回false,表示锁已经被其他人拿到了,则获得锁失败
*/
if(compareAndSetState(0,1)){
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
/*释放锁*/
@Override
protected boolean tryRelease(int arg) {
if(getState()==0){
throw new IllegalMonitorStateException();
}
setExclusiveOwnerThread(null);
/*
1.如果当前线程已经拿到锁,则将线程的state改为0表示释放该锁。
*/
setState(0);
return true;
}
/* 返回一个Condition,每个condition都包含了一个condition队列*/
Condition newCondition() {
return new ConditionObject();
}
}
/* 仅需要将操作代理到Sync上即可*/
private final Sync sync = new Sync();
public void lock() {
System.out.println(Thread.currentThread().getName()+" ready get lock");
sync.acquire(1);
System.out.println(Thread.currentThread().getName()+" already got lock");
}
public boolean tryLock() {
return sync.tryAcquire(1);
}
public void unlock() {
System.out.println(Thread.currentThread().getName()+" ready release lock");
sync.release(1);
System.out.println(Thread.currentThread().getName()+" already released lock");
}
public Condition newCondition() {
return sync.newCondition();
}
public boolean isLocked() {
return sync.isHeldExclusively();
}
public boolean hasQueuedThreads() {
return sync.hasQueuedThreads();
}
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}
}
2.AQS的基本思想CLH队列锁
(1)任一时刻只有一个线程可以拿到这把锁,其他的线程是要在外面进行排队的。
(2)一个线程把任务完成了,把锁释放了,唤醒另外的线程竞争锁,拿到锁之后,又继续执行。凡是没有拿到锁的线程,都要进行排队。
(3)要排队的线程需要打包成一个QNode,包含三个成员
-
当前线程本身
-
myPred :当前线程的前驱节点。
因为有很多个线程都处于等待拿锁阶段,于是就将这些需要拿锁的线程排好队,形成一个链表,链表要么是单向的,要么是双向的。 -
locked:表示标记当前需要获得锁
(4)线程B获取锁的时候,会通过自旋检查链表中其他节点是否释放锁.如果locked字段变成false的时候,表示已经释放了,就可以去拿锁了。
2.1了解ReentrantLock的实现
2.1.1锁的可重入
(1)定义多个同步方法
synchronized a(){
b();
};
synchronized b()
(2)a方法中调用b方法,这种情况是进a方法的时候需要拿一次锁,进入b方法的时候又要拿一次锁,如果说没有实现锁的可重入,就会导致死锁。即自己将自己给锁住了。
(3)改一下tryAcquire方法就可以了,即在获取锁的时候,多做一次判定,判断一下当前拿到锁的是不是自己,是自己则把state做+1操作,因为要释放锁,所以要加1.
(4)每释放一次锁,将持有锁的次数减1,当减到0的时候,表示对锁的持有已经彻底释放了,通过这种方式实现锁的可重入。
2.1.1.1实现可重入锁
/**
*类说明:实现我们自己独占锁,可重入
*/
public class ReenterSelfLock implements Lock {
/* 静态内部类,自定义同步器*/
private static class Sync extends AbstractQueuedSynchronizer {
/* 是否处于占用状态*/
protected boolean isHeldExclusively() {
return getState() > 0;
}
/* 当状态为0的时候获取锁*/
public boolean tryAcquire(int acquires) {
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}else if(getExclusiveOwnerThread()==Thread.currentThread()){
setState(getState()+1);
return true;
}
return false;
}
/* 释放锁,将状态设置为0*/
protected boolean tryRelease(int releases) {
if(getExclusiveOwnerThread()!=Thread.currentThread()){
throw new IllegalMonitorStateException();
}
if (getState() == 0)
throw new IllegalMonitorStateException();
setState(getState()-1);
if(getState()==0){
setExclusiveOwnerThread(null);
}
return true;
}
/* 返回一个Condition,每个condition都包含了一个condition队列*/
Condition newCondition() {
return new ConditionObject();
}
}
/* 仅需要将操作代理到Sync上即可*/
private final Sync sync = new Sync();
public void lock() {
System.out.println(Thread.currentThread().getName()+" ready get lock");
sync.acquire(1);
System.out.println(Thread.currentThread().getName()+" already got lock");
}
public boolean tryLock() {
return sync.tryAcquire(1);
}
public void unlock() {
System.out.println(Thread.currentThread().getName()+" ready release lock");
sync.release(1);
System.out.println(Thread.currentThread().getName()+" already released lock");
}
public Condition newCondition() {
return sync.newCondition();
}
public boolean isLocked() {
return sync.isHeldExclusively();
}
public boolean hasQueuedThreads() {
return sync.hasQueuedThreads();
}
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}
}
2.1.1.2测试可重入锁
public class TestReenterSelfLock {
static final Lock lock = new ReenterSelfLock();
public void reenter(int x){
lock.lock();
try {
System.out.println(Thread.currentThread().getName()+":递归层级:"+x);
int y = x - 1;
if (y==0) return;
else{
reenter(y);
}
} finally {
lock.unlock();
}
}
public void test() {
class Worker extends Thread {
public void run() {
System.out.println(Thread.currentThread().getName());
SleepTools.second(1);
reenter(3);
}
}
// 启动3个子线程
for (int i = 0; i < 3; i++) {
Worker w = new Worker();
w.start();
}
// 主线程每隔1秒换行
for (int i = 0; i < 100; i++) {
SleepTools.second(1);
}
}
public static void main(String[] args) {
TestReenterSelfLock testMyLock = new TestReenterSelfLock();
testMyLock.test();
}
}
2.1.2公平和非公平锁
(1)公平锁
- 任何一个线程想要获取锁,必须将其放到链表的最后一个位置,谁都没有例外。
(2)非公平锁
- 链表上面已经有好几个线程挂到这里,等待着拿锁,外面又有一个线程进来准备拿锁,但是它可以去抢占锁,把锁拿到了,这就称为非公平锁。
2.2将类似于ReentrantLock的锁的实现修正为可重入
(1)可重入锁
3.JMM基础-计算机原理
(1)JMM:Java Memory Model;Java 内存模型。
(2)高速缓存
为了提高应用程序的性能,会在内存与CPU之间加入高速缓存
3.1Java内存模型
3.1.1工作内存
(1)每一个变量的操作只允许在自己的工作内存之中进行操作,而不是操作主内存中的变量。
(2)Java中每个线程的工作内存是自己所独享的,即只准我用,你们都别用。
3.1.2主内存
3.2JMM导致的并发安全问题
(1)两个线程都要执行count=count+1的操作
(2)刚开始count放在主内存
(3)线程A与线程B都不允许直接对主内存中的count进行直接操作。于是将count读到自己的工作内存中。
(4)于是在线程A的工作内存中就有一个count变量为0;线程B同样如此。
(5)线程A与线程B执行count+1操作,线程A与线程B的工作内存内部,count都变成了1。
(6)线程A与线程B执行完成之后,需要将运算结果写入到主内存中,线程A与线程B都写入1.
(7)理论上来说线程A与线程B都对count进行了一次累加,累加之和应该是2,但是结果却为1.
(8)因此在JMM机制下面,count值为1.就产生了并发编程中的数据不安全问题。
3.2.1变量的可见性问题
(1)线程A将count由0变为1,线程B也将count由0变为1,即线程A与线程B互不知道对方对count进行了修改,所以线程A与线程B之间就存在一个可见性问题。
(2)可见性问题如何解决?
- 需要在声明变量的时候,使用volatile关键字,即强迫从主内存中去读取数据。
- 将变量修改之后,强迫将数据刷新到主内存。
- 使用了volatile关键字结果还是不正确,是因为volatile只是强迫读了,算完了强制放回主内存。
- 如果进行多次运算,还是只用volatile进行一个修饰,线程A与线程B,线程A先做,将count值由0变为了1,同步回主内存。线程B将1读到工作内存中,这个count=count+1不是一个原子操作,线程B在累加的过程中,因为它不是一个原子操作,意味着B这个累加过程是可以被打断的,比如说上下文切换,即只算到某一条指令,算到一半的时候,操作系统把B给交换出去了,B不执行了,A没有交换出去,一直做累加操作。A下一次运算,将count变为了2,又同步回去,B又重新切换回来,继续count=count+1,这个时候线程A已经对count进行了修改,线程B上次的count操作还没有完成,它是没有到主内存中去读一次值的过程,它只有下一次运算的时候才会去主内存中去读,所以这时候,即使加了volatile关键字,结果也是错的。因此操作需要保证原子性。
- 加volatile关键字只能保证可见性,不能保证操作的原子性。
3.2.2变量的原子性问题
(1)加锁可以保证运算的原子性。
(2)因为即使运算过程中上下文切换时交换出去了,因为其他线程拿不到锁,就无法对变量进行修改,一定是等这个有锁的线程处理完了,其他线程才能拿到锁,才能对其进行修改。
(3)只加锁,不使用volatile关键字修饰变量也是可以的,因为synchronized关键字的强度要比volatile的强度更强,即volatile是JDK中提供的最轻量级的同步机制。
(4)synchronized同时保证了可见性与原子性。
4.volatile详解
4.1volatile关键字的应用场景
(1)如果只有一个线程写,多个线程读的时候,volatile是完全没有问题的,能够确保线程安全。
(2)写操作之间没有任何的关联,即count值赋预的是完全独立的值
count=5;
count=6;
而不是关联
count=count+1;
(3)其在JDK编程里面大量使用,即实现无锁化编程。
4.2volatile的实现原理
(1)有Volatile变量修饰的共享变量进行写操作的时候会使用CPU提供的Lock前缀指令
- 将当前处理器缓存行的数据写回到系统内存。
- 这个写回内存的操作会使在其他CPU里缓存了该内存地址的数据无效。
5.打赏鼓励
感谢您的细心阅读,您的鼓励是我写作的不竭动力!!!