介绍几种Java并发包(juc)中常用的类及其用法。
1.ReentrantLock
ReentrantLock可重入锁,与synchronized的区别:
1.ReentrantLock需要自己手动加锁解锁,synchronized的不需要
2.ReentrantLock可以tryLock尝试获取锁(指定时间内),锁未被占用则获取锁(锁会在tryLock返回true后被调用线程获取),则返回true,否则返回false
3.ReentrantLock可以设定公平锁和非公平锁,synchronized只有非公平锁
代码演示:
public class ReentrantLockDemo {
private static ReentrantLock lock = new ReentrantLock();
private void testLock() {
try {
lock.lock();
// todo 原子操作
} finally {
lock.unlock();
}
}
private void testTryLock() {
boolean isLocked = false;
try {
// 尝试获取锁,规定时间内获取到锁则返回true,超时未获取锁则返回false
isLocked = lock.tryLock(5, TimeUnit.SECONDS);
if (isLocked) {
for (int i = 0; i < 10; i++) {
TimeUnit.SECONDS.sleep(1);
System.out.println(Thread.currentThread().getName() + ": " + i);
}
} else {
System.out.println(Thread.currentThread().getName() + "获取锁超时");
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if (isLocked) {
lock.unlock();
}
}
}
public static void main(String[] args) {
ReentrantLockDemo reentrantLockDemo = new ReentrantLockDemo();
// 线程1
new Thread(reentrantLockDemo::testTryLock).start();
// 线程2
new Thread(reentrantLockDemo::testTryLock).start();
}
}
2.CountDownLatch
CountDownLatch:门闩。在一个线程中设定一个预定值(大于0),然后调用await方法等待。其他线程每调用一次countDown方法,预定值就减一,当预定值为0时,await线程就会被唤醒。就像一扇门,被n个门闩拴住,当门闩被一个个的抽掉,门就打开了
代码演示:
public class CountDownLatchDemo {
public static void main(String[] args) {
CountDownLatch latch = new CountDownLatch(3);
for (int i = 0; i < 3; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "开始");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "结束");
latch.countDown();
}).start();
}
try {
latch.await(); // 主线程等待
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("结束");
}
}
3.CyclicBarrier
CyclicBarrier:栅栏,分界线。例如大家一起坐大巴车,每个人上车以后等待(await,线程等待),等到所有人(CyclicBarrier的初始参数parties)都上车后,车就出发(所有线程唤醒)
代码演示:
public class CyclicBarrierDemo {
public static void main(String[] args) {
// param1: 等待线程数;param2:所有线程都到齐后触发行为
CyclicBarrier barrier = new CyclicBarrier(3, ()-> System.out.println("人满,出发"));
for (int i = 0; i < 3; i++) {
new Thread(()->{
System.out.println(Thread.currentThread().getName() + "上车");
try {
barrier.await();
// System.out.println(Thread.currentThread().getName() + "dosomething");
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
// 每1s启动一个线程
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
4.Semaphore
Semaphore:信号量。这个锁用于控制对某组资源的访问权限,即同时只有指定数量的线程可以获得执行权。例如银行大厅有3个人工窗口,同时只有三个人能在窗口办公,其他人都需要等待
使用场景:限流
代码演示:
public class SemaphoreDemo {
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(3);
for (int i = 0; i < 10; i++) {
new Thread(() -> {
try {
semaphore.acquire();// 获取锁
System.out.println(Thread.currentThread().getName() + "正在执行...");
TimeUnit.SECONDS.sleep(3);
semaphore.release();// 释放锁
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
}
5.ReadWriteLock
ReadWriteLock: 读写锁。写锁是互斥锁,写的时候其他线程不能写,也不能读;读锁是共享锁,读的时候其他线程也可以读,但是不能写。由于读锁是共享锁,多个线程可以同时读数据,所以效率会比直接加一个synchronized效率高
代码演示:
public class ReadWriteLockDemo {
static ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
static Lock readLock = readWriteLock.readLock();// 读锁
static Lock writeLock = readWriteLock.writeLock(); // 写锁
static int value = 0;
public static void read(Lock lock) {
try {
lock.lock();
System.out.println(Thread.currentThread().getName() + "开始读...");
TimeUnit.SECONDS.sleep(1);
System.out.println(Thread.currentThread().getName() + "读结束,value = " + value);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public static void write(Lock lock, int v) {
try {
lock.lock();
System.out.println(Thread.currentThread().getName() + "开始写...");
TimeUnit.SECONDS.sleep(1);
value = value + v;
System.out.println(Thread.currentThread().getName() + "写完成");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
// 模拟10个线程读
for (int i = 0; i < 10; i++) {
new Thread(() -> ReadWriteLockDemo.read(readLock)).start();
}
// 模拟3个线程写
for (int i = 0; i < 3; i++) {
new Thread(() -> ReadWriteLockDemo.write(writeLock, 1)).start();
}
}
}
6.LockSupport
LockSupport用于控制一个线程的停止与唤醒,调用park方法则线程阻塞,调用unpark方法则线程重新运行。与wait/notify的区别:
1.wait/notify必须在同步代码中使用,多个线程竞争一把锁的情况下,线程调用wait让出锁,notify唤醒其他线程来竞争锁
2.unpark可以先于park调用,成对出现则线程不会停止
public class LockSupportDemo {
public static void main(String[] args) {
Thread t = new Thread(()->{
for (int i = 0; i < 10; i++) {
System.out.println(i);
if (i == 5) {
LockSupport.park(); // 线程停止
}
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();
// LockSupport.unpark(t);
try {
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
LockSupport.unpark(t); // 解除线程停止状态
System.out.println("线程继续运行...");
}
}
7.Condition
Condition:用于线程间通信,其重要方法await/signal/signalAll,作用类似于Object的wait/notify/notifyAll
区别:
1.Condition的使用需要依赖ReentrantLock
2.每个Condition对象都相当于一个队列,线程调用了Condition对象的await方法,就会到相应的队列中等待
例如有两个Condition对象:producer,consumer,线程调用producer.await(),则线程会在producer队列中等待,线程调用consumer.await(),则线程会在consumer队列中等待.此时调用producer.signalAll(),会唤醒producer队列中等待的所有线程去竞争锁,consumer同理
代码演示:
/**
* demo:
* 定义一个固定容量的容器,容量为10,能够支持2个生产者put和10个消费者get。
* 容器元素为最大容量时,生产者停止put,需通知消费者get;容器元素为0时,消费者停止get,需通知生产者put
* created by it_hushuai
* 2020/12/5 15:43
*/
public class ConditionDemo {
public static void main(String[] args) {
ContainerDemo<String> container = new ContainerDemo<>();
// 启动消费者线程
for (int i = 0; i < 10; i++) {
new Thread(()->{
for (int j = 0; j < 5; j++) {
System.out.println(Thread.currentThread().getName() + "正在消费元素:" + container.get());
}
}).start();
}
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 启动生产者线程
for (int i = 0; i < 2; i++) {
new Thread(()->{
for (int j = 0; j < 25; j++) {
String item = Thread.currentThread().getName() + "-" + j;
System.out.println(Thread.currentThread().getName() + "正在生产:" + item);
container.put(item);
}
}).start();
}
}
}
class ContainerDemo<T> {
final private LinkedList<T> list = new LinkedList<>();
final private int MAX = 10; // 容器容量
private int count = 0;
private ReentrantLock lock = new ReentrantLock();
private Condition producer = lock.newCondition();
private Condition consumer = lock.newCondition();
public /*synchronized*/ void put(T t) {
try {
lock.lock();
// 这里使用while而不使用if:当线程被唤醒后,仍需判断当前元素个数是否达到最大值
while (list.size() == MAX) { // 只要当前list的大小为容量值10,则不能添加元素,添加元素线程阻塞
try {
producer.await(); // 生产者线程等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 未到达容器容量
list.add(t);
++count;
// 通知消费线程
consumer.signalAll();
} finally {
lock.unlock();
}
}
/**
* 每次只能由一个线程取出一个元素
*
* @return
*/
public /*synchronized*/ Object get() {
T t = null;
try {
lock.lock();
while (list.size() == 0) { // 当容器元素为0时,停止消费
try {
consumer.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
t = list.removeFirst();
count--;
producer.signalAll(); // 通知生产线程
} finally {
lock.unlock();
}
return t;
}
}