Java AQS

AQS

  • AbstractQueuedSynchronizer (AQS)抽象的队列式的同步器,AQS定义了一套多线程访问共享资源的同步器框架,许多同步类实现都依赖于它,比如ReentrantLock/Semaphore/CountDownLatch
  • 它维护了一个volatile int state(代表共享资源)和一个FIFO线程等待队列(多线程争用资源被阻塞时会进入此队列)。这里volatile是核心关键词,具体volatile的语义
  • state的访问方式有三种: getState() 、setState() 、compareAndSetState()
  • AQS定义两种资源共享方式:Exclusive(独占,只有一个线程能执行,如ReentrantLock)和Share (共享,多个线程可同时执行,例如Semaphore/CountDownLatch)不同的自定义同步器争用共享资源的方式也不同
  • 线程抢占同一份资源,只有被标杆节点选中的才可以访问资源,其余的进入排队队列,如果是公平锁,则按照先后顺序进行对于资源的访问;如果是非公平锁,则当标杆节点释放完之后,大家开始抢占资源,谁抢到算谁的,没有先来后到之分

自定义的同步容器

  • 自定义同步器在实现时只需要实现共享资源state的获取与释放方式即可,至于具体线程等待队列的维护(如获取资源失败入队/唤醒出队等),AQS已经在底层实现好了

主要实现以下几种方法

  • isHeldExclusively():该线程是否正在独占资源。只有用到condition才需要去实现它
  • tryAcquire(int):独占方式。尝试获取资源,成功则返回true,失败则返回false 
  • tryRelease(int):独占方式。尝试释放资源,成功则返回true,失败则返回false
  • tryAcquireShared(int):共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源
  • tryReleaseShared(int):共享方式。尝试释放资源,成功则返回true,失败则返回false

例子

  • 以ReentrantLock为例,state初始化为0,表示未锁定状态。A线程lock()时,会调用tryAcquire()独占该锁并将state+1。此后,其他线程再tryAcquire()时就会失败,直到A线程unlock()到state=0(即释放锁)为止,其它线程才有机会获取该锁。当然,释放锁之前,A线程自己是可以重复获取此锁的(state会累加),这就是可重入的概念。但要注意,获取多少次就要释放多么次,这样才能保证state是能回到零态的
  • 重入锁,在需要进行同步的代码部分加上锁定,但不要忘记最后一定要释放锁定,不然会造成锁永远无法释放其他线程永远进不来的结果
  • t2抢占线程,只有等t2线程结束,线程t1才有资格抢占资源
package com.example.core.aqs;

import java.util.concurrent.locks.ReentrantLock;

public class UseReentrantLock {
    private ReentrantLock reentrantLock = new ReentrantLock();
    public void method(){
        reentrantLock.lock();
        try{
            System.out.println("当前线程:"+Thread.currentThread().getName()+"进入...");
            Thread.sleep(2000);
            System.out.println("当前线程:"+Thread.currentThread().getName()+"退出...");
        }catch(InterruptedException e){
            e.printStackTrace();
        }finally {
            reentrantLock.unlock();
        }
    }

    public static void main(String[] args) {
        UseReentrantLock useLock = new UseReentrantLock();
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                useLock.method();
            }
        },"t1");

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                useLock.method();
            }
        },"t2");

        t1.start();
        t2.start();
    }
}
/*
output:
当前线程:t2进入...
当前线程:t2退出...
当前线程:t1进入...
当前线程:t1退出...
 */

公平锁和非公平锁

  • Lock lock = new ReentrantLock(boolean isFair);

lock用法

  • tryLock():尝试获得锁,获得结果用true/false返回
  • tryLock():在给定的时间内尝试获得锁,获得结果用true/false返回
  • isFair():是否是公平锁
  • isLocked():是否锁定
  • getHoldCount(): 查询当前线程保持此锁的个数,也就是调用lock()次数
  • lockInterruptibly():优先响应中断的锁
  • getQueueLength():返回正在等待获取此锁定的线程数
  • getWaitQueueLength():返回等待与锁定相关的给定条件Condition的线程数
  • hasQueuedThread(Thread thread): 查询指定的线程是否正在等待此锁
  • hasQueuedThreads():查询是否有线程正在等待此锁
  • hasWaiters():查询是否有线程正在等待与此锁定有关的condition条件
  • 再以CountDownLatch以例,任务分为N个子线程去执行,state也初始化为N(注意N要与线程个数一致)。这N个子线程是并行执行的,每个子线程执行完后countDown()一次,state会CAS减1。等到所有子线程都执行完后(即state=0),会unpark()调用线程,然后主调用线程就会从await()函数返回,继续后余动作

AQS Condition

  • 使用synchronized的时候,如果需要多线程间进行协作工作则需要Object的wait()和notify()、notifyAll()方法进行配合工作
  • 那么同样,我们在使用Lock的时候,可以使用一个新的等待/通知的类,它就是Condition。这个Condition一定是针对具体某一把锁的。也就是在只有锁的基础之上才会产生Condition
  • 我们可以通过一个Lock对象产生多个Condition进行多线程间的交互,非常的灵活。可以使得部分需要唤醒的线程唤醒,其他线程则继续等待通知

使用一个条件

  • t1线程先执行,进入等待的时候,释放锁,t2才可以得以执行,t2线程开始执行,对于t1线程发出唤醒通知,t1得以继续执行,最后释放锁
package com.example.core.aqs;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class UseCondition {

	
	//现在有一把锁
	private Lock lock = new ReentrantLock();	
	
	//synchronized  wait ---- notify
	//基于这把锁产生了一个 condition: 作用是对于这把锁的 唤醒 和 等待操作
	private Condition condition = lock.newCondition();
	
	public void method1(){
		lock.lock();
		try {
			System.out.println("当前线程:" + Thread.currentThread().getName() + "进入等待状态..");
			Thread.sleep(3000);
			System.out.println("当前线程:" + Thread.currentThread().getName() + "释放锁..");
			condition.await();
			System.out.println("当前线程:" + Thread.currentThread().getName() +"继续执行...");
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			System.err.println(Thread.currentThread().getName() + " unlock");
			lock.unlock();
		}
	}
	
	public void method2(){
		lock.lock();
		try {
			System.out.println("当前线程:" + Thread.currentThread().getName() + "进入..");
			Thread.sleep(3000);
			System.out.println("当前线程:" + Thread.currentThread().getName() + "发出唤醒..");
			condition.signal();
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			lock.unlock();
		}
	}
	
	public static void main(String[] args) throws Exception {
		final UseCondition uc = new UseCondition();
		Thread t1 = new Thread(new Runnable() {
			@Override
			public void run() {
				uc.method1();
			}
		}, "t1");
		Thread t2 = new Thread(new Runnable() {
			@Override
			public void run() {
				uc.method2();
			}
		}, "t2");
		t1.start();
		Thread.sleep(1);
		t2.start();
	}
}
/*
output
当前线程:t1进入等待状态..
当前线程:t1释放锁..
当前线程:t2进入..
当前线程:t2发出唤醒..
当前线程:t1继续执行...
t1 unlock
 */

使用多个条件

  • 创建两个条件,条件c1和c2,c1条件受制于t1和t2线程,由t3线程进行唤醒;c2条件受制于t3线程,由t5线程进行唤醒
package com.example.core.aqs;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class UseManyCondition {

	private Lock lock = new ReentrantLock();
	private Condition c1 = lock.newCondition();
	private Condition c2 = lock.newCondition();
	
	public void m1(){
		try {
			lock.lock();
			System.out.println("当前线程:" +Thread.currentThread().getName() + "进入方法m1等待..");
			c1.await();c1条件 由m4唤醒
			System.out.println("当前线程:" +Thread.currentThread().getName() + "方法m1继续..");
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			lock.unlock();
		}
	}
	
	public void m2(){
		try {
			lock.lock();
			System.out.println("当前线程:" +Thread.currentThread().getName() + "进入方法m2等待..");
			c1.await();//c1条件 由m4唤醒
			System.out.println("当前线程:" +Thread.currentThread().getName() + "方法m2继续..");
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			lock.unlock();
		}
	}
	
	public void m3(){
		try {
			lock.lock();
			System.out.println("当前线程:" +Thread.currentThread().getName() + "进入方法m3等待..");
			c2.await();c2条件 由m5唤醒
			System.out.println("当前线程:" +Thread.currentThread().getName() + "方法m3继续..");
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			lock.unlock();
		}
	}
	
	public void m4(){
		try {
			lock.lock();
			System.out.println("当前线程:" +Thread.currentThread().getName() + "唤醒..");
			c1.signalAll();
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			lock.unlock();
		}
	}
	
	public void m5(){
		try {
			lock.lock();
			System.out.println("当前线程:" +Thread.currentThread().getName() + "唤醒..");
			c2.signalAll();
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			lock.unlock();
		}
	}
	
	public static void main(String[] args) {
		
		final UseManyCondition umc = new UseManyCondition();
		Thread t1 = new Thread(new Runnable() {
			@Override
			public void run() {
				umc.m1();
			}
		},"t1");
		Thread t2 = new Thread(new Runnable() {
			@Override
			public void run() {
				umc.m2();
			}
		},"t2");
		Thread t3 = new Thread(new Runnable() {
			@Override
			public void run() {
				umc.m3();
			}
		},"t3");
		Thread t4 = new Thread(new Runnable() {
			@Override
			public void run() {
				umc.m4();
			}
		},"t4");
		Thread t5 = new Thread(new Runnable() {
			@Override
			public void run() {
				umc.m5();
			}
		},"t5");
		
		t1.start();
		t2.start();
		t3.start();
		try {
			Thread.sleep(2000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		t4.start();
		try {
			Thread.sleep(2000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		t5.start();
		
	}
}
/*
output
当前线程:t1进入方法m1等待..
当前线程:t3进入方法m3等待..
当前线程:t2进入方法m2等待..
当前线程:t4唤醒..
当前线程:t1方法m1继续..
当前线程:t2方法m2继续..
当前线程:t5唤醒..
当前线程:t3方法m3继续..
 */

AQS-ReentrantReadWriteLock

  • 读写锁ReentrantReadWriteLock,其核心就是实现读写分离的锁。在高并发访问下,尤其是读多写少的情况下,性能要远高于重入锁。
  • 之前学synchronized、ReentrantLock时,我们知道,同一时间内,只能有一个线程进行访问被锁定的代码,那么读写锁则不同,其本质是分成两个锁,即读锁、写锁。在读锁下,多个线程可以并发的进行访问,但是在写锁的时候,只能一个一个的顺序访问
  • 口诀:读读共享,写写互斥,读写互斥
  • t1和t2都是读锁,t3是写锁,t1和t2可以并行执行,他们与t3之间不可以同时执行。读读共享,写写互斥,读写互斥
package com.example.core.aqs;

import java.util.concurrent.locks.ReentrantReadWriteLock;

public class UseReadWriteLock {
    private ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
    private ReentrantReadWriteLock.ReadLock readLock = rwLock.readLock();
    private ReentrantReadWriteLock.WriteLock writeLock = rwLock.writeLock();

    public void read(){
        readLock.lock();
        try{
            System.out.println("当前线程 "+ Thread.currentThread().getName()+ " 进入了读方法");
            Thread.sleep(3000);
            System.out.println("当前线程 "+ Thread.currentThread().getName()+ " 退出了读方法");
        }catch (Exception e){
            e.printStackTrace();
        }finally{
            readLock.unlock();
        }
    }

    public void write(){
        writeLock.lock();
        try{
            System.out.println("当前线程 "+ Thread.currentThread().getName()+ " 进入了写方法");
            Thread.sleep(3000);
            System.out.println("当前线程 "+ Thread.currentThread().getName()+ " 退出了写方法");
        }catch (Exception e){
            e.printStackTrace();
        }finally{
            writeLock.unlock();
        }
    }

    public static void main(String[] args) {
        UseReadWriteLock rwLock = new UseReadWriteLock();

        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                rwLock.read();
            }
        },"t1");

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                rwLock.read();
            }
        },"t2");

        Thread t3 = new Thread(new Runnable() {
            @Override
            public void run() {
                rwLock.write();
            }
        },"t3");

        t1.start();
        t2.start();
        t3.start();
    }
}
/*
output:
当前线程 t3 进入了写方法
当前线程 t3 退出了写方法
当前线程 t1 进入了读方法
当前线程 t2 进入了读方法
当前线程 t1 退出了读方法
当前线程 t2 退出了读方法
 */

LockSupport

  • 提供park()和unpark()方法实现阻塞线程和解除线程阻塞,实现的阻塞和解除阻塞是基于”许可(permit)”作为关联,permit相当于一个信号量(0,1),默认是0. 线程之间不再需要一个Object或者其它变量来存储状态,不再需要关心对方的状态
  • LockSupport不需要在同步代码块里 。所以线程间也不需要维护一个共享的同步对象了,实现了线程间的解耦.
  • unpark函数可以先于park调用,所以不需要担心线程间的执行的先后顺序
package com.example.core.aqs;

import java.util.concurrent.locks.LockSupport;

public class UseLockSupport {

    public static void main(String[] args) throws Exception {
        Thread A = new Thread(new Runnable() {
            @Override
            public void run() {
                int sum = 0;
                for(int i=0;i<10;i++){
                    sum+=i;
                }
               
                try {
					Thread.sleep(3000);
				} catch (Exception e) {
					e.printStackTrace();
				}
				LockSupport.park();	//滞后的

                System.out.println(sum);
            }
        });
        A.start();
        //后阻塞:
        Thread.sleep(1000);
        LockSupport.unpark(A);	//优先的

    }
}
package com.example.core.aqs;

public class UseObjectLock {

    public static void main(String[] args) throws Exception {
    	Object lock = new Object();
        Thread A = new Thread(new Runnable() {
            @Override
            public void run() {
                int sum = 0;
                for(int i=0;i<10;i++){
                    sum+=i;
                }
                synchronized (lock) {
					try {
						lock.wait();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
                System.out.println(sum);
            }
        });
        A.start();
        //后阻塞:
        Thread.sleep(1000);
        synchronized (lock) {
			lock.notify();
		}
    }
}

AQS-锁优化

  • 避免死锁
  • 减小锁的持有时间 
  • 减小锁的粒度 
  • 锁的分离 
  • 尽量使用无锁的操作,如原子操作(Atomic系列类),volatile关键字

acquire(int)

  • 此方法是独占模式下线程获取共享资源的顶层入口。如果获取到资源,线程直接返回,否则进入等待队列,直到获取到资源为止,且整个过程忽略中断的影响。这也正是lock()的语义,当然不仅仅只限于lock()。获取到资源后,线程就可以去执行其临界区代码了

  • tryAcquire()尝试直接去获取资源,如果成功则直接返回
  • addWaiter()将该线程加入等待队列的尾部,并标记为独占模式
  • acquireQueued()使线程在等待队列中获取资源,一直获取到资源后才返回。如果在整个等待过程中被中断过,则返回true,否则返回false
  • 如果线程在等待过程中被中断过,它是不响应的。只是获取资源后才再进行自我中断selfInterrupt(),将中断补上

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值