CAS(Compare and Swap)
CAS操作需要输入两个数值,一个旧值和一个新值,在操作期间先比较旧值有没有发生变化,如果没有发生变化才替换为新值,发生了变化则不替换;
但是CAS操作会存在ABA问题:CAS需要在操作值的时候,检查值有没有发生变化,如果有发生变化才更新,但是如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化,但是实际上却变化了;
ABA问题的解决思路就是使用版本号。在变量前面追加版本号,每次变量更新的时候把版本号加1,那么A->B->A就会变成1A->2B->3A;
AQS(AbstractQueuedSynchronizer)
什么是AQS
简称同步器,是用来构建锁或者其他同步组件的基础框架,它使用了一个int成员变量表示同步状态,通过内置的FIFO队列来完成资源获取线程的排队工作;
同步器的主要使用方式是继承,子类通过继承同步器并实现它的抽象方法来管理同步状态,同步器提供了如下3个方法来访问或修改同步状态:
- getState():获取当前同步状态
- setState(int newState):设置当前同步状态
- compareAndSetState(int expect,int update):使用CAS设置当前状态,该方法能够保证状态设置的原子性
同步器可重写的方法如下:
方法名称 | 描述 |
---|---|
protected boolean tryAcquire(int arg) | 独占式获取同步状态,实现该方法需要查询当前状态并判断同步状态是否符合预期,然后再进行CAS设置同步状态 |
protected boolean tryRelease(int arg) | 独占式释放同步状态,等待获取同步状态的线程将有机会获取同步状态 |
protected int tryAcquireShared(int arg) | 共享式获取同步状态,返回大于等于0的值,表示获取成功,反之,获取失败 |
protected boolean tryReleaseShared(int arg) | 共享式释放同步状态 |
protected boolean isHeldExclusively() | 当前同步器是否在独占模式下被线程占用,一般该方法表示是否被当前线程所独占 |
自定义同步组件示例:
public class Mutex implements Lock {
private static class Sync extends AbstractQueuedSynchronizer{
@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 IllegalMonitorStateException();
}
setExclusiveOwnerThread(null);
setState(0);
return true;
}
@Override
protected boolean isHeldExclusively() {
return getState() == 1;
}
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(0);
}
@Override
public Condition newCondition() {
return sync.newCondition();
}
}
使用自定义同步组件不会直接和内部同步器实现打交道;
AQS实现原理
1、同步队列
同步器依赖内部的同步队列(一个FIFO双向队列)来完成同步状态的管理,当前线程获取同步状态失败时,同步器会将当前线程以及等待状态等信息构造成为一个节点(Node)并将其加入同步队列,同时会阻塞当前线程,当同步状态释放时,会把首节点中的线程唤醒,使其再次尝试获取同步状态。
同步队列中的节点(Node)用来保存获取同步状态失败的线程引用、等待状态以及前驱和后继节点,节点的属性类型与名称以及描述如下所示:
属性类型和名称 | 描述 |
---|---|
int waitStatus | 等待状态,CANCELLED=1、SIGNAL=-1、CONDITION=-2、PROPAGATE=-3、INITAL=0 |
Node prev | 前驱节点,当节点加入同步队列时被设置(尾部添加) |
Node next | 后继节点 |
Node nextWaiter | 等待队列中的后继节点。如果当前节点是共享的,那么这个字段将是一个SHARED常量,也就是说节点类型(独占和共享)和等待队列中的后继节点共用同一个字段 |
Thread thread | 获取同步状态的线程 |
节点是构成同步队列(等待队列)的基础,同步器拥有首节点(head)和尾节点(tail),没有成功获取同步状态的线程将会成为节点加入该队列的尾部,同步队列的基本结构如下图所示:
同步器包含了两个节点类型的引用,一个指向头节点,而另一个指向尾节点。试想一下,当一个线程成功地获取了同步状态(或者锁),其他线程将无法获取到同步状态,转而被构造成为节点并加入到同步队列中,过程如下图所示:
同步队列遵循FIFO,首节点是获取同步状态成功的节点,首节点的线程在释放同步状态时,将会唤醒后继节点,而后继节点将会在获取同步状态成功时将自己设置为首节点,该过程如下图所示:
2、独占式同步状态获取与释放
通过调用同步器的acquire(int arg)方法可以获取同步状态,该方法对中断不敏感,也就是由于线程获取同步状态失败后进入同步队列中,后续对线程进行中断操作时,线程不会从同步队列中移出:
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
其主要逻辑是:首先调用自定义同步器实现的tryAcquire(int arg)方法,该方法保证线程安全的获取同步状态,如果同步状态获取失败,则构造同步节点(独占式Node.EXCLUSIVE,同一时刻只能有一个线程成功获取同步状态)并通过addWaiter(Node node)
方法将该节点加入到同步队列的尾部,最后调用acquireQueued(Node node,int arg)方法,使得该节点以“死循环”的方式获取同步状态。如果获取不到则阻塞节点中的线程,而被阻塞线程的唤醒主要依靠前驱节点的出队或阻塞线程被中断来实现;
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
在enq(final Node node)方法中,同步器通过“死循环”来保证节点的正确添加;
节点进入同步队列之后,就进入了一个自旋的过程,每个节点(或者说每个线程)都在自省地观察,当条件满足,获取到了同步状态,就可以从这个自旋过程中退出,否则依旧留在这个自旋过程中(并会阻塞节点的线程),代码如下:
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
//检查自己的前驱是否是头节点,如果是则尝试获取同步状态
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
在方法中,当前线程在“死循环”中尝试获取同步状态,而只有前驱节点是头节点才能够尝试获取同步状态,这是为什么?原因有两个,如下:
- 头节点是成功获取到同步状态的节点,而头节点的线程释放了同步状态之后,将会唤醒其后继节点,后继节点的线程被唤醒后需要检查自己的前驱节点是否是头节点;
- 维护同步队列的FIFO原则;
该方法中,节点自旋获取同步状态的行为如图:
可以看到节点和节点之间在循环检查的过程中基本不相互通信,而是简单地判断自己的前驱是否为头节点,这样就使得节点的释
放规则符合FIFO;
独占式同步状态获取流程,也就是acquire(int arg)方法调用流程,如图:
在上图中,前驱节点为头节点且能够获取同步状态的判断条件和线程进入等待状态就是获取同步状态的自旋过程;
当前线程获取同步状态并执行了相应逻辑之后,就需要释放同步状态,使得后续节点能够继续获取同步状态。通过调用同步器的release(int arg)方法可以释放同步状态,该方法在释放了同步状态之后,会唤醒其后继节点(进而使后继节点重新尝试获取同步状态)。该方法代码如下:
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
该方法执行时,会唤醒头节点的后继节点线程,unparkSuccessor(Node node)方法使用LockSupport来唤醒处于等待状态的线程;
总结:在获取同步状态时,同步器维护一个同步队列,获取状态失败的线程都会被加入到队列中并在队列中进行自旋;移出队列(或停止自旋)的条件是前驱节点为头节点且成功获取了同步状态。在释放同步状态时,同步器调用tryRelease(int arg)方法释放同步状态,然后唤醒头节点的后继节点;
3、共享式同步状态获取与释放
共享式获取与独占式获取最主要的区别在于同一时刻能否有多个线程同时获取到同步状态;通过调用同步器的acquireShared(int arg)方法可以共享式地获取同步状态,该方法代码如下:
public final void acquireShared(int arg) {
//tryAcquireShared(arg)尝试获取同步状态,返回值大于等于0时,表示能够获取到同步状态
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
private void doAcquireShared(int arg) {
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
//如果当前节点的前驱为头节点时,尝试获取同步状态,如果返回值大于等于0,
//表示该次获取同步状态成功并从自旋过程中退出
if (p == head) {
int r = tryAcquireShared(arg);
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
if (interrupted)
selfInterrupt();
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
doAcquireShared(int arg)方法的自旋过程中,如果当前节点的前驱为头节点时,尝试获取同步状态,如果返回值大于等于0,表示该次获取同步状态成功并从自旋过程中退出;
与独占式一样,共享式获取也需要释放同步状态,通过调用releaseShared(int arg)方法可以释放同步状态,该方法代码如下:
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
该方法在释放同步状态之后,将会唤醒后续处于等待状态的节点;
并发工具类
Condition、CountDownLatch、CyclicBarrier、Semaphore、ConcurrentHashMap是在java1.5被引入,都存在于java.util.cucurrent包下。
Lock
在Lock中声明了四个方法来获取锁:
- lock()
- unlock()
- tryLock()
- tryLock(long time, TimeUnit unit)
- lockInterruptibly()
1、lock()、unlock()
lock():获取锁,如果锁已被其它线程获取,则进行等待;
unlock():释放锁;
/**
* @author yangdong
* @date 2021-05-10
* 简单使用lock()方法,由于Lock发生异常不会自动释放锁,因此我们一定要在 finally 中释放锁
*/
public class Test7
{
private static Lock lock = new ReentrantLock();
public static void main(String[] args) {
lock.lock(); //此行不能写在try块中
try {
System.out.println(Thread.currentThread().getName()+"开始执行任务");
}finally {
lock.unlock();
}
}
}
Lock不会像synchronized一样在异常时自动释放锁。因此我们一定要在 finally 中释放锁,以保证发生异常时锁一定被释放;
注意:不要将获取锁的过程写在try块中,因为如果在获取锁时发生了异常(可能还没获取到锁),执行finally块中锁释放时会报错:
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
//当前线程与拥有锁的线程比较肯定是不相等的,将会抛出IllegalMonitorStateException异常
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
/**
* @author yangdong
* @date 2021-05-10
* 验证Lock发生异常不会自动释放锁
*/
public class Test7
{
private static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) {
lock.lock();
try {
throw new RuntimeException();
}catch (Exception e){
e.printStackTrace();
}
//isHeldByCurrentThread可以看出锁是否被当前线程持有
System.out.println(lock.isHeldByCurrentThread());
}
}
运行程序,控制台打印true,证实:Lock发生异常不会自动释放锁;
lock()方法不能被中断,这就会带来很大的问题:一旦陷入死锁,lock()就会陷入永久等待;
2、tryLock()、tryLock(long time, TimeUnit unit),嗅探拿锁
tryLock()用来尝试获取锁,该方法会立即返回,即便在拿不到锁时也不会一直等待,它的返回值是一个布尔值(返回true代表获取成功,返回false代表获取失败);
tryLock(long time, TimeUnit unit)则会进行一段时间等待,如果在此等待时间内拿到了锁则返回true,否则返回false;
相比于lock(),这个方法显然功能更强大了,我们可以根据是否能获取刀锁来决定后续程序的行为;
/**
* @author yangdong
* @date 2021-05-10
* 演示tryLock()、tryLock(long time, TimeUnit unit)的使用
*/
public class Test7 implements Runnable
{
private static ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
if (lock.tryLock()){
try {
System.out.println(Thread.currentThread().getName()+"使用tryLock()获取到锁");
if(lock.tryLock(100,TimeUnit.MILLISECONDS)){
System.out.println(Thread.currentThread().getName()+"使用tryLock(long time, TimeUnit unit)获取到锁");
}
//打印 ReentrantLock 的嵌套加锁数量
System.out.println(lock.getHoldCount());
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//必须显式调用 unlock() 来释放锁
lock.unlock();
lock.unlock();
System.out.println(lock.getHoldCount());
}
}
}
public static void main(String[] args) {
new Thread(new Test7()).start();
}
}
//控制台打印:
Thread-0使用tryLock()获取到锁
Thread-0使用tryLock(long time, TimeUnit unit)获取到锁
2
0
ReentrantLock 具有可重入性,一个线程可以对已被加锁的 ReentrantLock 再次加锁,ReentrantLock 对象会维持一个计数器来追踪 lock() 方法的嵌套调用,线程在每次调用 lock() 加锁后,必须显式调用 unlock() 来释放锁;
3、lockInterruptibly()
相当于tryLock(long time, TimeUnit unit)把超时时间设置为无限。在等待锁的过程中,线程可以被中断,不会抛出InterruptedException异常;
/**
* @author yangdong
* @date 2021-05-10
* 演示线程2等待获取锁时被中断的场景
*/
public class Test7 implements Runnable
{
private static ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
try {
lock.lockInterruptibly();
try {
System.out.println(Thread.currentThread().getName()+"获取到锁");
Thread.sleep(1000);
}finally {
lock.unlock();
System.out.println(Thread.currentThread().getName()+"释放了锁");
}
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName()+"等待获取锁时被中断了");
}
}
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(new Test7());
Thread thread2 = new Thread(new Test7());
thread1.start();
Thread.sleep(50);
thread2.start();
thread2.interrupt();
}
}
4、ReentrantLock公平锁演示
public class FairLock implements Runnable{
// 用true初始化锁就得到公平锁
private Lock lock = new ReentrantLock(true);
@Override
public void run() {
lock.lock();
try {
System.out.println(Thread.currentThread().getName()+"获取到锁");
}finally {
lock.unlock();
}
}
public static void main(String[] args) {
new Thread(new FairLock()).start();
new Thread(new FairLock()).start();
new Thread(new FairLock()).start();
}
}
//控制台打印
Thread-0获取到锁
Thread-1获取到锁
Thread-2获取到锁
5、getHoldCount()、isFair()、isHeldByCurrentThread()、isLocked()
public int getHoldCount():查询当前线程保持此锁的个数(即调用lock()方法的次数,此方法在ReentrantLock 类中)!
public final boolean isFair():判断是不是公平锁(默认情况下,ReentrantLock 使用的非公平锁)!
public boolean isHeldByCurrentThread():查询当前线程是否持有此锁!
public boolean isLocked():查询此锁是否由任意线程持有!
public class Test6{
public static void main(String[] args) throws InterruptedException, NoSuchFieldException, IllegalAccessException {
ReentrantLock lock = new ReentrantLock();
new Thread(()->{
try {
System.out.println(lock.isLocked()); //打印false
System.out.println(lock.isHeldByCurrentThread()); //打印false
lock.lock();
System.out.println(lock.isLocked()); //打印true
System.out.println(lock.isHeldByCurrentThread()); //打印true
System.out.println(lock.getHoldCount()); //打印1
}finally {
lock.unlock();
System.out.println(lock.getHoldCount()); //打印0
}
}).start();
}
System.out.println(lock.isFair()); //打印false
}
6、getQueueLength()
public final int getQueueLength()方法的作用是返回正在等待获取此锁的线程数量!
public class Test6{
static ReentrantLock lock = new ReentrantLock();
public static void method(){
try {
lock.lock();
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException, NoSuchFieldException, IllegalAccessException {
for (int i = 0; i < 5; i++) {
new Thread(()->{ method();}).start();
}
Thread.sleep(50);
System.out.println(lock.getQueueLength()); //打印4
}
}
7、hasWaiters(Condition condition)、getWaitQueueLength(Condition condition)
public boolen hasWaiters(Condition condition)方法的作用是查询是否有线程正在等待与此锁有关的Condition条件(是否有线程执行了condition对象中的await()方法而呈等待状态)!
public int getWaitQueueLength(Condition condition) 方法的作用是返回等待与此锁相关的给定条件Condition的线程数量!
public class Test6{
static ReentrantLock lock = new ReentrantLock();
static Condition condition = lock.newCondition();
public static void method(){
try {
lock.lock();
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public static void signal(){
try {
lock.lock();
System.out.println(lock.getWaitQueueLength(condition)); //打印5
System.out.println(lock.hasWaiters(condition)); //打印true
condition.signal();
}finally {
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException, NoSuchFieldException, IllegalAccessException {
for (int i = 0; i < 5; i++) {
new Thread(()->{ method();}).start();
}
Thread.sleep(1000);
signal();
}
}
8、hasQueuedThread(Thread thread)、hasQueuedThreads()
public final boolean hasQueuedThread(Thread thread)方法的作用是查询指定的线程是否正在等待获取此锁,也就是判断参数中的线程是否在等待队列中;
public final boolean hasQueuedThreads()方法的作用是查询是否有线程正在等待获取此锁,也就是等待队列中是否有等待的线程!
public class Test6{
static ReentrantLock lock = new ReentrantLock();
public static void method(){
try {
lock.lock();
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException, NoSuchFieldException, IllegalAccessException {
new Thread(()->{ method();}).start();
Thread.sleep(50);
Thread thread = new Thread(() -> { method(); });
thread.start();
Thread.sleep(50);
System.out.println(lock.hasQueuedThread(thread)); //打印true
System.out.println(lock.hasQueuedThreads()); //打印true
}
}
LockSupport
LockSupport定义了一组公共静态方法,这些方法提供了最基本的线程阻塞和唤醒功能,而LockSupport也成为构建同步组件的基础方法!
class ParkThread implements Runnable{
@Override
public void run() {
System.out.println( Thread.currentThread().getName() + "开始线程阻塞");
try {
Thread.sleep(1000); //该线程休眠1s,让主线程先执行unpark操作。运行发现程序并没有因此而一直阻塞!
} catch (InterruptedException e) {
e.printStackTrace();
}
LockSupport.park();
System.out.println(Thread.currentThread().getName() + "结束线程阻塞")
}
public static void main(String[] args) {
ParkThread parkThread = new ParkThread();
Thread t1=new Thread(parkThread);
Thread t2=new Thread(parkThread);
t1.start();
LockSupport.unpark(t1);
t2.start();
LockSupport.unpark(t2);
}
}
与wait()、notify()的区别?
- wait()、notify()必须在同步代码块中使用,而LockSupport则可以在任意场合使用;
- wait()、notify()不能颠倒顺序使用,而LockSupport可以颠倒park和unpark的顺序;
- wait()、notify()不能唤醒指定的线程,而LockSupport可以;
相同点:低层都是调用了UNSAFE的对应方法来处理;
Condition
用来替代传统的Object的wait()、notify()实现线程间的协作,相比使用Object的wait()、notify(),使用Condition的await()、signal()这种方式实现线程间协作更加安全和高效;
Condition是个接口,基本的方法就是await()和signal()方法,并且signal()是公平的;Condition依赖于Lock接口,生成一个Condition的基本代码是lock.newCondition();
调用Condition的await()和signal()方法,都必须在lock保护之内,就是说必须在lock.lock()和lock.unlock之间才可以使用
- Conditon中的await()对应Object的wait();
- Condition中的signal()对应Object的notify();
- Condition中的signalAll()对应Object的notifyAll()
public class ConditionDemo {
private ReentrantLock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
void method() throws InterruptedException {
lock.lock();
try {
System.out.println("条件不满足,开始await");
condition.await();
System.out.println("条件满足了,开始执行后续的任务");
}finally {
lock.unlock();
}
}
void method2(){
lock.lock();
try {
System.out.println("准备工作完成,唤醒其它的线程");
condition.signal();
}finally {
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
ConditionDemo demo = new ConditionDemo();
new Thread(() -> {
try {
Thread.sleep(1000);
demo.method2();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
demo.method();
}
}
//控制台打印:
条件不满足,开始await
准备工作完成,唤醒其它的线程
条件满足了,开始执行后续的任务
那么让执行await()方法的线程暂停运行是什么原理呢?
其实并发包源代码内部执行了Unsafe类中的park方法,让当前线程呈暂停状态:
//isAbsolute代表是否为绝对时间,time表示时间值
//如果isAbsolute传入true,则第二个参数时间单位为毫秒;如果传入false,则第二个参数时间单位为纳秒
public native void park(boolean isAbsolute,long time)
示例如下:程序将会等待3S再输出结果!
public class Test6{
public static void main(String[] args) throws InterruptedException, NoSuchFieldException, IllegalAccessException {
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
Unsafe unsafe = (Unsafe) field.get(null);
//unsafe.park(false, 3000000000L); 3s对应纳秒为3000000000L
//unsafe.park(false, 0); 程序将无线等待
//unsafe.park(true, 0); 程序将无线等待
unsafe.park(true,System.currentTimeMillis() + 3000);
System.out.println("-------");
}
}
Condition 源码最终也是调的这个方法:
public static void park(Object blocker) {
Thread t = Thread.currentThread();
setBlocker(t, blocker);
UNSAFE.park(false, 0L);
setBlocker(t, null);
}
并且Condition可创建多个,用来唤醒指定种类的线程:
class myService{
Lock lock = new ReentrantLock(true);
Condition conditionA = lock.newCondition();
Condition conditionB = lock.newCondition();
public void methodA(){
try {
lock.lock();
System.out.println("methodA");
conditionA.await();
System.out.println("methodA被唤醒");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void methodB(){
try {
lock.lock();
System.out.println("methodB");
conditionB.await();
System.out.println("methodB被唤醒");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void signal(){
try {
lock.lock();
conditionA.signal();
}finally {
lock.unlock();
}
}
}
public class Test6{
public static void main(String[] args) throws InterruptedException, NoSuchFieldException, IllegalAccessException {
myService service = new myService();
new Thread(()->{service.methodA();}).start();
new Thread(()->{service.methodB();}).start();
new Thread(()->{service.signal();}).start();
}
}
//控制台打印
methodA
methodB
methodA被唤醒
awaitUntil(Date deadline)
public boolean awaitUntil(Date deadline):在指定的Date结束等待;
public class Test6{
static Lock lock = new ReentrantLock();
static Condition condition = lock.newCondition();
public static void main(String[] args) throws InterruptedException {
new Thread(()->{
try {
lock.lock();
condition.awaitUntil(new Date(System.currentTimeMillis()+3000));
System.out.println("当前时间等待3s自动结束!");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}).start();
}
}
awaitUninterruptibly()
public void awaitUninterruptibly():实现线程在等待的过程中,不允许被中断;
public class Test6{
static Lock lock = new ReentrantLock();
static Condition condition = lock.newCondition();
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
try {
lock.lock();
condition.awaitUninterruptibly();
System.out.println("当前时间等待3s自动结束!");
} finally {
lock.unlock();
}
});
thread.start();
Thread.sleep(100);
thread.interrupt();
System.out.println(thread.isInterrupted()); //打印false
}
}
原子类
在java.util.concurrent.atomic包下,有一系列Atomic开头的类,统称为原子类;
原子类的作用和锁类似,都是为了保证并发情况下线程安全,不过原子类相比于锁,有以下两大优势:
- 粒度更细
- 效率更高
原子类总共分为以下六种:
分类 | 具体 |
---|---|
Atomic*基本类型原子类 | AtomicInteger、AtomicLong、AtomicBoolean |
Atomic*Array数组类型原子类 | AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray |
Atomic*Reference引用类型原子类 | AtomicReference、AtomicStampedReference、AtomicMarkableReference |
Adder累加器 | LongAdder、DoubleAdder |
Accumulator累加器 | LongAccumulator、DoubleAccumulator |
AtomicInteger
//获取当前的值
public final int get()
//获取当前的值,并设置新的值
public final int getAndSet(int newValue)
//获取当前的值,并自增
public final int getAndIncrement()
//获取当前的值,并自减
public final int getAndDecrement()
//获取当前的值,并加上预取的值
public final int getAndAdd(int delta)
//如果输入的数值等于预期值,则以原子方式将该值设置为输入值
public final boolean weakCompareAndSet(int expect, int update)
/**
* @author yangdong
* @date 2021-05-20
* 演示AtomicInteger的基本用法,并对比非原子类的线程安全问题
*/
public class AtomicIntegerDemo implements Runnable{
private static final AtomicInteger atomicInteger = new AtomicInteger();
private static volatile int basicCount = 0;
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
atomicInteger.getAndIncrement();
basicCount++;
}
}
public static void main(String[] args) throws InterruptedException {
AtomicIntegerDemo demo = new AtomicIntegerDemo();
Thread t1 = new Thread(demo);
Thread t2 = new Thread(demo);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("原子类的结果:"+atomicInteger.get());
System.out.println("普通变量的结果:"+basicCount);
}
}
多次运行发现原子类的操作总能返回正确的结果,而非原子类则不行;
AtomicIntegerArray
以整个数组对象为单位,里面的元素操作都是原子性的;
public class AtomicArrayDemo implements Runnable{
static AtomicIntegerArray array = new AtomicIntegerArray(100);
public static void main(String[] args) throws InterruptedException {
AtomicArrayDemo demo = new AtomicArrayDemo();
Thread t1 = new Thread(demo);
Thread t2 = new Thread(demo);
t1.start();
t2.start();
t1.join();
t2.join();
}
@Override
public void run() {
for (int i = 0; i < array.length(); i++) {
array.getAndIncrement(i);
}
}
}
运行程序,因为AtomicIntegerArray属于原子类数组,因此在多线程情况下,数组里面的元素操作都是线程安全的,不会出现结果不正确的情况;
AtomicReference
AtomicReference类的作用和AtomicInteger类似,AtomicInteger可以让一个整数保证原子性,而AtomicReference可以让一个对象保证原子性;
@Data
@AllArgsConstructor
public class User {
private String name;
private Integer age;
}
public static void main( String[] args ) {
User user1 = new User("张三", 23);
User user2 = new User("李四", 25);
User user3 = new User("王五", 20);
//初始化为 user1
AtomicReference<User> atomicReference = new AtomicReference<>();
atomicReference.set(user1);
//把 user2 赋给 atomicReference
atomicReference.compareAndSet(user1, user2);
System.out.println(atomicReference.get());
//把 user3 赋给 atomicReference
atomicReference.compareAndSet(user1, user3);
System.out.println(atomicReference.get());
}
输出结果如下:
User(name=李四, age=25)
User(name=李四, age=25)
atomicReference的初始值是user1,所以调用compareAndSet(user1, user2),由于user1==user1,所以会把user2赋给atomicReference。此时值为“李四”;
第二次调用atomicReference.compareAndSet(user1, user3),由于user2 != user1,所以set失败。atomicReference仍然为“李四”
AtomicReferenceFieldUpdater对普通变量进行升级
public class AtomicReferenceFieldUpdaterDemo implements Runnable{
static Candidate tom;
static Candidate peter;
public static AtomicIntegerFieldUpdater<Candidate> scoreUpdate = AtomicIntegerFieldUpdater.newUpdater(Candidate.class,"score");
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
peter.score++;
scoreUpdate.getAndIncrement(tom);
}
}
public static class Candidate{
volatile int score;
}
public static void main(String[] args) throws InterruptedException {
tom = new Candidate();
peter = new Candidate();
AtomicReferenceFieldUpdaterDemo demo = new AtomicReferenceFieldUpdaterDemo();
Thread t1 = new Thread(demo);
Thread t2 = new Thread(demo);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("升级后的结果:"+tom.score);
System.out.println("普通变量的结果:"+peter.score);
}
}
Adder累加器
Adder是 Java 8 引入的,在高并发下LongAdder 比 AtomicLong效率高,不过本质是空间换时间;
public class AtomicLongDemo{
public static void main(String[] args) throws InterruptedException {
AtomicLong counter = new AtomicLong(0);
ExecutorService service = Executors.newFixedThreadPool(20);
long start = System.currentTimeMillis();
for (int i = 0; i < 1000; i++) {
service.submit(() -> {
for (int j = 0; j < 10000; j++) {
counter.incrementAndGet();
}
});
}
service.shutdown();
while (!service.isTerminated()){
}
long end = System.currentTimeMillis();
System.out.println(counter.get());
System.out.println("AtomicLong耗时:"+(end-start));
}
}
再创建一个类,将AtomicLong 更改为LongAdder,运行程序对比测试,可以发现LongAdder的运行时间更快;
CountDownLatch
CountDownLatch这个类使一个线程等待其他线程各自执行完毕后再执行。它是通过一个计数器来实现的,计数器的初始值是线程的数量。每当一个线程执行完毕后,计数器的值就-1,当计数器的值为0时,表示所有线程都执行完毕,然后在闭锁上等待的线程就可以恢复工作了。
CountDownLatch类中只提供了一个构造器:
//参数count为计数值
public CountDownLatch(int count) { };
类中有三个方法是最重要的:
//调用await()方法的线程会被挂起,它会等待直到count值为0才继续执行
public void await() throws InterruptedException { };
//和await()类似,只不过等待一定的时间后count值还没变为0的话就会继续执行
public boolean await(long timeout, TimeUnit unit) throws InterruptedException { };
//将count值减1
public void countDown() { };
示例:
public class CountDownLatchDemo{
public static void main(String[] args) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(5);
ExecutorService executorService = Executors.newFixedThreadPool(5);
for (int i = 0; i < 5; i++) {
final int no = i + 1 ;
Runnable runnable = () -> {
try {
long time = (long) (Math.random()*10000);
Thread.sleep(time);
System.out.println("No."+no+"完成了检查");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
latch.countDown(); //countDown()调用一次就减1
}
};
executorService.submit(runnable);
}
System.out.println("等待五个人检查完成!");
latch.await(); //await()方法会阻塞当前线程,直到N变成0
System.out.println("全部完成");
}
}
//控制台打印:
等待五个人检查完成!
No.1完成了检查
No.3完成了检查
No.4完成了检查
No.2完成了检查
No.5完成了检查
全部完成
CyclicBarrier循环栅栏
CyclicBarrier可以构造一个集结点,当某一个线程执行完毕,它就会到集结点等待,直到所有线程都到了集结点,那么该栅栏就被册小,所有的线程再同一出发,继续执行剩下的任务;
生活中小例子:所有人都在山脚下集合,都到齐后再一起爬山;
public class CyclicBarrierDemo {
public static void main(String[] args) {
CyclicBarrier cyclicBarrier = new CyclicBarrier(5, new Runnable() {
@Override
public void run() {
System.out.println("所有人都到场了,大家一起出发");
}
});
for (int i = 0; i < 5; i++) {
new Thread(new Task(i,cyclicBarrier)).start();
}
}
static class Task implements Runnable{
private int id;
private CyclicBarrier cyclicBarrier;
public Task(int id,CyclicBarrier cyclicBarrier){
this.id = id;
this.cyclicBarrier = cyclicBarrier;
}
@Override
public void run() {
System.out.println("线程"+id+"现在前往集合地点");
try {
Thread.sleep((long)(Math.random()*10000));
System.out.println("到了集合地点,开始等待其他人到达");
cyclicBarrier.await();
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
}
}
}
CyclicBarrier和CountDownLatch的区别:
- 作用不同:CyclicBarrier要等固定数量的线程都到达了栅栏位置才能继续执行,而CountDownLatch只需等待数字到0,也就是说,CountDownLatch用于事件,但是CyclicBarrier是用于线程的;
- 可重用性不同:CountDownLatch在倒数到0并出发门闩打开后就不能再次使用了;而CyclicBarrier可以重复使用;
Semaphore(信号量)
使用Semaphore可以控制同时访问资源的线程个数;
信号量的作用是维护一个许可证的计数,线程可以获取许可证,那信号量剩余的许可证就减一,线程也可以释放一个许可证,那信号量剩余的许可证就加一,当信号量所拥有的许可证数量为0,那么下一个还想要获取许可证的线程,就需要等待,直到有另外的线程释放了许可证;
构造方法:
//可以设置是否要使用公平的策略,如果传入true,那么Semaphore会把之前等待的线程放入到FIFO的队列里,
//以便于有了新的许可证,可以分发给之前等了最长时间的线程
public Semaphore(int permits, boolean fair)
Semaphore的主要方法摘要:
- void acquire():从此信号量获取一个许可证,在提供一个许可证前一直将线程阻塞,可以响用中断。
- boolean tryAcquire():尝试获取许可证,没获取到也不会进行阻塞
- void release():释放一个许可证,将其返回给信号量。
- int availablePermits():返回此信号量中当前可用的许可数。
- boolean hasQueuedThreads():查询是否有线程正在等待获取。
public class SemaphoreDemo {
static Semaphore semaphore = new Semaphore(2,true);
public static void main(String[] args) {
ExecutorService service = Executors.newFixedThreadPool(10);
for (int i = 0; i < 10; i++) {
service.submit(new Task());
}
}
static class Task implements Runnable{
@Override
public void run() {
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName()+"拿到了许可证");
Thread.sleep(2000);
semaphore.release();
//System.out.println(Thread.currentThread().getName()+"释放了许可证");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}