Java 基础(四)并发类

1、并发类

有以下几种重要的并发:ConcurrentHashMap,ReentrantLock(可重入锁)、ReentrantReadAndWriteLock(可重入读写锁)

1.1 ConcurrentHashMap

jdk1.7 中 ConcurrentHashMap 实现原理

采用了段锁 + 二次哈希的方式提供并发访问,但是由于其还是以数组+链表的形式,查询效率低,并发度也不高,这种实现机制在 JDK1.8 中被替换了

JDK 1.8 中 ConcurrentHashMap 实现原理

使用了 volatile + synchronized + CAS 来进行实现

扩容使用了多线程扩容机制

扩容数量和 HashMap 保持一致,每次扩容为原来的两倍

ConcurrentHashMap#computeIfAbsent 方法存在死循环的 bug 风险

 

参考:

https://www.cnblogs.com/huangjuncong/p/9478505.html

1.2 ReentrantLock(可重入锁)

ReentrantLock 是 Lock 接口的子类。

关于 Lock 接口

Lock 是整个 JDK 锁相关的父接口,定义了锁的操作原理和注意事项。

Lock.lockInterruptibly() 说明:

当两个线程 A、B 同时使用 lock.lockInterruptibly() 方法去获取锁的时候,如果 A 获得锁,则 B 进入等待停止状态,此时其他线程调用 B 线程的 interrupt 方法可以使线程 B 中断,从而运行终止。(如果是 B 获取了锁,则调用 B.interrupt 无效)

示例代码如下:

查看代码
 package com.test.root.base.thread;

import java.io.IOException;
import java.util.HashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ReentrantLockTest {

    public static Lock lock = new ReentrantLock();

    public static void main(String[] args) {
        Thread a = new Thread(() -> {
            try {
                lock.lockInterruptibly();
                for (;;);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }, "Thread-A");
        a.start();

        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

        Thread b = new Thread(() -> {
            try {
                try {
                    lock.lockInterruptibly();
                    for (;;);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }, "Thread-B");
        b.start();

        HashMap<String, Thread> map = new HashMap<>();
        map.put("A", a);
        map.put("B", b);

        try {
            while (true) {
                byte[] bytes = new byte[1024];
                int l = System.in.read(bytes);

                printThreadState(map.get("B"));
                printThreadState(map.get("A"));

                map.get("B").interrupt();
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public static void printThreadState(Thread thread) {
        System.out.println(thread.getName() + ": " + thread.getState());
    }
}

在命令行随意输入两次 1,可以看到运行结果如下,第一次:当 B 处于了等待状态,然后主线程调用了 B.interrupt() 方法,使得 B线程从等待状态变成了终止状态,同时抛出了 InterruptedException 异常。

 1
Thread-B: WAITING
Thread-A: RUNNABLE
java.lang.InterruptedException
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchronizer.java:898)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1222)
	at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:335)
	at com.test.root.base.thread.ReentrantLockTest$1.run(ReentrantLockTest.java:36)
	at java.lang.Thread.run(Thread.java:748)
Exception in thread "Thread-B" java.lang.IllegalMonitorStateException
	at java.util.concurrent.locks.ReentrantLock$Sync.tryRelease(ReentrantLock.java:151)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.release(AbstractQueuedSynchronizer.java:1261)
	at java.util.concurrent.locks.ReentrantLock.unlock(ReentrantLock.java:457)
	at com.test.root.base.thread.ReentrantLockTest$1.run(ReentrantLockTest.java:44)
	at java.lang.Thread.run(Thread.java:748)
1
Thread-B: TERMINATED
Thread-A: RUNNABLE

Lock 接口各个方法说明:

  • void lock():获取锁,如果没有获取到锁,则该线程会进入 停止等待状态(WAITING)。此处于使用 synchronized 关键词不同,使用 synchronized 未获取到锁时候线程进入阻塞(BLOCK)状态
  • void lockInterruptibly() throws InterruptedException:获取锁,如果没有获取到锁,则该线程会进入 停止等待状态(WAITING),此时其他线程可以使用 interrupt 方法终止获取锁同时在获取锁方法处抛出异常,这个异常需要自己去捕获处理,不然的话会造成线程终止。
  • void tryLock():尝试获取锁,如果成功获取的话,则返回 true,如果没有获取到锁返回 false,不会阻塞线程
  • boolean tryLock(long time, TimeUnit unit) throws InterruptedException:尝试获取锁,并且设置最大等待时间。此方法可以被中断(即其他线程调用该线程的 interrupt 方法)。
  • void unlock():解锁。如果锁不存在则会抛出 IllegalMonitorStateException 异常。
  • Condition newCondition():适用于线程间的协同,可以主动释放锁,等待后续重新获取锁之后继续执行,因此,获取锁的线程可以执行所有代码逻辑或者在某一处按条件停止等待。

 Condition 代码示例:

查看代码
 package com.test.root.base.thread;

import cn.hutool.core.date.DateUtil;

import java.io.IOException;
import java.util.Date;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class ConditionTest {
    public static ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) {
        Thread a = new Thread(() -> {
            print("尝试获取锁");
            if (lock.tryLock()) {
                print("成功获取锁");
                try {
                    sleep(5);
                    Condition condition = lock.newCondition();
                    try {
                        print("主动进入等待,等待 5 秒");
                        // 等待的含义:
                        // 如果重新获取到线程,耗时超过设置值返回 false,没有超过返回 true,如果没有重新获取锁,会一直停止等待
                        // 上述是 JDK 的文档解释,不过此处我没有复现,返回值一直是 false
                        boolean flag = condition.await(5, TimeUnit.SECONDS);
                        if (!flag) {
                            print("重新获取锁超过时间了");
                        }
                        print("重新获取到了锁,继续执行");
                        while (true) ;

                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }

                } finally {
                    lock.unlock();
                }
            } else {
                print("获取锁失败");
            }
        }, "Thread-A");
        a.start();

        sleep(1000);

        Thread b = new Thread(() -> {
            print("尝试获取锁");
            lock.lock();
            print("成功获取锁");
            try {
                sleep(10);
//                sleep(3000);

            } finally {
                print("释放锁");
                lock.unlock();
            }
        }, "Thread-B");
        b.start();


        while (true) {
            try {
                System.in.read(new byte[1024]);

                b.interrupt();

                printThreadState(a);
                printThreadState(b);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }


    public static void printThreadState(Thread thread) {
        System.out.println(thread.getName() + ": " + thread.getState());
    }

    public static void print(String msg) {
        String time = DateUtil.format(new Date(), "yyyy-MM-dd HH:mm:ss:SSS");
        System.out.println(time + " " + Thread.currentThread().getName() + ": " + msg);
    }

    public static void sleep(long l) {
        try {
            Thread.sleep(l);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

}
代码中用到的 Jar 包依赖
 <dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.2.0</version>
</dependency>

ReentrantLock 类图:

内部(分为公平锁和非公平锁)加锁:主要还是使用 CAS + 同步队列(ABS)来实现的,大致流程如下(没有细化到源码级别,只要是用于理解):

 

公平锁和非公平锁的区别在于:公平锁先检查同步队列中是否有等待线程,非公平锁会先去尝试竞争锁,如果竞争失败,则走公平锁相同逻辑。

提示:ReentrantLock 不是自旋锁

 

参考:

AQS与ReentrantLock详解:https://juejin.cn/post/7031527716159488007

公平锁和非公平锁:https://cloud.tencent.com/developer/article/2165891

posted @ 2024-11-19 13:51  lenbkan  阅读(2)  评论(0编辑  收藏  举报