文章目录
ReentrantLock
1. ReentrantLock使用
- 获取锁lock()/释放锁unlock()
三个线程依次获取锁/释放锁
public class TestReentrantLock {
static ReentrantLock lock = new ReentrantLock();
static void f(){
lock.lock();
System.out.println(Thread.currentThread().getName()+"获取锁");
lock.unlock();
System.out.println(Thread.currentThread().getName()+"释放锁");
}
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(3);
executorService.execute(TestReentrantLock::f);
executorService.execute(TestReentrantLock::f);
executorService.execute(TestReentrantLock::f);
executorService.shutdown();
}
}
- 可中断地获取锁lockInterruptibly
调用后一直阻塞到获得锁 但是接受中断信号
//可中断地获取锁
static void g(){
try {
lock.lockInterruptibly();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws Exception {
Thread thread1 = new Thread(TestReentrantLock::g);
thread1.start();
thread1.interrupt();
}
如果被在获取锁时被中断,就抛出异常,类似sleep
- 尝试获取锁tryLock、tryLock(timeout)
尝试是否能获得锁 如果不能获得立即返回false,不阻塞当前线程,否则,直接获取锁
lock.tryLock();//尝试获取锁,不阻塞当前线程
//阻塞线程,尝试获取锁2秒钟,超时后放弃获取锁。
try {
lock.tryLock(2000, TimeUnit.SECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
- 公平锁/非公平锁
ReentrantLock可以实现公平锁,将线程放入同步队列中,依次获取锁
static ReentrantLock fairLock = new ReentrantLock(true);
//公平锁
static void r() {
fairLock.lock();
System.out.println(Thread.currentThread().getName() + "获取锁");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
fairLock.unlock();
System.out.println(Thread.currentThread().getName() + "释放锁");
}
public static void main(String[] args) throws Exception {
for (int i = 0; i < 100; i++) {
Thread.sleep(10);
new Thread(TestReentrantLock::r).start();
}
System.out.println("线程创建完毕");
}
这里每隔10毫秒创建一个线程,可以保证线程获取锁的顺序,如果是公平锁,那么线程获取锁是有序的,如果是非公平锁,线程获取锁是需要竞争的(注释
:这个例子在正常运行时,不知什么原因公平锁和非公平锁得到了一样的结果
,如果使用线程调试
,那么结果是满足期望的,公平锁起了作用)
- Condition
和wait、notify、notifyAll用法类似,对应await、signal、signalAll方法
public class TestCondition {
public static void main(String[] args) throws InterruptedException {
ExecutorService threadPool = Executors.newFixedThreadPool(3);
Lock lock = new ReentrantLock();
Condition condition1 = lock.newCondition();
Condition condition2 = lock.newCondition();
Runnable awaitRunnable = () -> {
lock.lock();
try {
System.out.println(Thread.currentThread() + "condition1,await");
condition1.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread() + "condition1,notified");
lock.unlock();
};
Runnable signalRunnable = () -> {
lock.lock();
try {
System.out.println(Thread.currentThread() + "Sleep");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
condition1.signal();
System.out.println(Thread.currentThread() + "signal");
lock.unlock();
};
threadPool.execute(awaitRunnable);
threadPool.execute(signalRunnable);
threadPool.shutdown();
threadPool.awaitTermination(2000, TimeUnit.SECONDS);
}
}
和wait、notify、notifyAll用法基本一致,但ReentrantLock可以使用多个
condition监视器,对应多个线程等待队列,Object只有1个监视器。
使用多个Condition可以控制唤醒指定的1个或多个
线程(在这些线程中使用特定的condition的await方法即可)。
2. ReentrantLock和synchronized对比
- 相同点:
- 都是
阻塞式同步
,拥有锁的线程才能够访问同步代码块。 - 都是
重入锁
,拥有锁的线程能够再次加锁而不被阻塞。 - 性能大致相同,Java对synchronized做了很多锁优化。
- 不同点:
- synchronized是
JVM
实现的,ReentrantLock是JDK
实现的。 - 当持有锁的线程长时间不释放锁时,ReentrantLock可以实现线程的
等待可中断
,synchronized不可以。 - ReentrantLock可以实现
公平锁
和非公平锁,synchronized只能是非公平锁。(公平锁:多个线程在等待锁时,按照申请锁的先后顺序来依次获得锁) - 在等待/通知机制中,synchronized使用Object
内置的监视器
,通过wait/notify等方法做等待和唤醒操作,notifyAll会唤醒所有线程;ReentrantLock使用条件监视器
Condition,一个ReentrantLock有多个condition,每个condition维护自己的等待线程队列调用signalAll只会唤醒自己队列内的线程。 - ReentrantLock有
显式
的锁对象,可以由用户决定请求锁和释放锁的时机,甚至可以不在一个代码块内,synchronized没有这么灵活。
3.实现原理
CountDownLatch
1. 使用
- CountDownLatch可以等待
一个或多个线程
等待其他线程完成操作,比join
方法灵活程度更高。CountDownLatch不能重用
。 - 例子
主线程等待两个线程完成工作
public class TestCountDownLatch {
static CountDownLatch cdl = new CountDownLatch(2);
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程1完成工作");
cdl.countDown();
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程2完成工作");
cdl.countDown();
}
});
thread1.start();
thread2.start();
// thread1.join();
// thread2.join();
cdl.await();
System.out.println("回到Main线程");
}
}
主线程中的cdl.await();
将会阻塞主线程,直到计数器为0
。
2. 方法
- countDown()
计数器减一 - await()、await(millis)
阻塞当前线程,等待计数器为0,可以设置超时时间
3. CountDownLatch比join好在哪?
CountDownLatch与join方法功能类似,但比join方法控制能力更高,join只能等待其他线程完成其全部工作
后,才能返回。
而CountDownLatch的计数器只监控数值是否为0,也就是说,该数值与线程数无关,一个线程可以调用countDown()方法多次。
例如一个线程包含多个任务
,我们只需要等待其完成一个任务
即可,使用join只能等待该线程完成全部任务。
4. 实现原理
CyclicBarrier
1. 使用
- CyclicBarrier让一组线程到达屏障时被阻塞,直到最后一个线程到达屏障时,所有线程都被唤醒。
10个线程到达屏障,最后一个线程到达时,10个线程都被唤醒
public class TestCyclicBarrier {
private CyclicBarrier cyclicBarrier = new CyclicBarrier(10);
private Runnable runnable = () -> {
System.out.println(Thread.currentThread()+"到达屏障");
try {
Thread.sleep(1000);
cyclicBarrier.await();
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread()+"继续执行");
};
public static void main(String[] args) {
TestCyclicBarrier testCyclicBarrier = new TestCyclicBarrier();
ExecutorService executorService = Executors.newFixedThreadPool(10);
for(int i = 0 ; i < 10 ; i++){
executorService.execute(testCyclicBarrier.runnable);
}
executorService.shutdown();
}
}
输出:
Thread[pool-1-thread-1,5,main]到达屏障
Thread[pool-1-thread-5,5,main]到达屏障
Thread[pool-1-thread-4,5,main]到达屏障
Thread[pool-1-thread-3,5,main]到达屏障
Thread[pool-1-thread-2,5,main]到达屏障
Thread[pool-1-thread-7,5,main]到达屏障
Thread[pool-1-thread-6,5,main]到达屏障
Thread[pool-1-thread-8,5,main]到达屏障
Thread[pool-1-thread-9,5,main]到达屏障
Thread[pool-1-thread-10,5,main]到达屏障
Thread[pool-1-thread-2,5,main]继续执行
Thread[pool-1-thread-9,5,main]继续执行
Thread[pool-1-thread-4,5,main]继续执行
Thread[pool-1-thread-7,5,main]继续执行
Thread[pool-1-thread-1,5,main]继续执行
Thread[pool-1-thread-5,5,main]继续执行
Thread[pool-1-thread-6,5,main]继续执行
Thread[pool-1-thread-8,5,main]继续执行
Thread[pool-1-thread-10,5,main]继续执行
Thread[pool-1-thread-3,5,main]继续执行
2. 方法
- 构造方法CyclicBarrier(int parties)
parties代表在屏障解除前,需要拦截的线程的数量。如果指定为10,只有小于10的线程执行了await()方法,那么屏障将不会解除。
- 构造方法CyclicBarrier(int parties, Runnable barrierAction)
barrierAction的代码块将会在解除屏障后马上执行
,执行的线程为最后到达屏障的线程。 - await
表示线程到达了屏障 - await(long timeout, TimeUnit unit)
等待指定的时间,如果屏障没有解除,那么抛出TimeoutException
,正在等待屏障解除的线程抛出BrokenBarrierException
。 - reset()
可以重置,重置后可以继续使用
3. 实现原理
Semaphore
1. 使用
- Semaphore是
流量控制
工具,控制某一代码块
同时可以被多少线程同时访问,比线程池的控制更精细化
。
例:线程池有30个线程,而我们只想其中10个线程并发操作数据库
public class TestSemaphore {
Semaphore semaphore = new Semaphore(10);
Runnable runnable = new Runnable() {
@Override
public void run() {
try {
semaphore.acquire();
System.out.println(Thread.currentThread()+"开始数据库操作");
Thread.sleep(2000);
System.out.println(Thread.currentThread()+"完成数据库操作");
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
public static void main(String[] args) {
TestSemaphore testSemaphore = new TestSemaphore();
ExecutorService executorService = Executors.newFixedThreadPool(30);
for(int i = 0 ; i < 30 ;i++){
executorService.execute(testSemaphore.runnable);
}
executorService.shutdown();
}
}
2.主要方法
- 构造方法Semaphore(int permits)
允许并发访问代码块的线程数量 - 构造方法Semaphore(int permits, boolean fair)
fair表示是否公平访问 - acquire()
获取访问权 - release()
释放访问权
3.和线程池的区别
线程池控制的是线程级别
的同时并发数量,而Semaphore控制的是代码块级别
的同时并发数量,细粒度更高,如一个线程可能同时需要进行数据库操作和文件操作等,就需要Semaphore来精细控制。
Exchanger
使用
- Exchanger用于进行线程间的
数据交换
,它提供一个同步点,在这个同步点,两个线程可以交换彼此的数据。这两个线程通过 exchange方法交换数据,如果第一个线程先执行exchange()方法,它会一直等待第二个线程也 执行exchange方法,当两个线程都到达同步点时,这两个线程就可以交换数据,将本线程生产出来的数据传递给对方。
线程1发出10,收到20;线程2发出20,收到10
public class TestExchanger {
static Exchanger<Integer> exchanger = new Exchanger<>();
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
try {
System.out.println(Thread.currentThread()+"发出值:"+10);
Integer exchange = exchanger.exchange(10);
System.out.println(Thread.currentThread()+"收到值:"+exchange);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
try {
System.out.println(Thread.currentThread()+"发出值:"+20);
Integer exchange = exchanger.exchange(20);
System.out.println(Thread.currentThread()+"收到值:"+exchange);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}