常用的Concurrent同步工具类
CountDownLatch
CyclicBarrier
Semaphore
Exchanger
ReentrantLock
ReentrantReadWriteLock
1.CountDownLatch
线程安全计数器。允许一个或多个线程等待一系列的操作完成。
给一个指定数值初始化,然后通过countDown()方法将计数器减一。
通过await()方法,线程可以阻塞等待直到计数器为零。
由于用法比较简单,就不写DEMO了(懒)
2.CyclicBarrier
循环栅栏,允许一组线程相互等待,直到达到某个公共屏障点(common barrier point)。
在涉及固定大小的线程的程序中,这些线程必须不是的相互等待,这时使用CyclicBarrier可以使等待线程达到common barrier point时得到释放。
使用场景:需要所有子任务都完成时,才执行主任务。在使用时,工作线程数一定要大于CyclicBarrier初始化的数值,否则将永远阻塞下去。
package com.jimmy.concurrent.util;
import java.util.Random;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class CyclicBarrierDemo {
static class Runner implements Runnable {
private CyclicBarrier barrier;
private String name;
public Runner(CyclicBarrier barrier, String name) {
this.barrier = barrier;
this.name = name;
}
@Override
public void run() {
try {
Thread.sleep((new Random().nextInt(8)) * 1000 );
System.out.println(name + " is ready.[" + System.currentTimeMillis() + ']');
// 所有参与者都在此执行await之前,将一直阻塞
barrier.await();
// 所有参与者的await方法都执行后,将同时执行下面动作
System.out.println(name + " run! [" + System.currentTimeMillis() + "]");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
CyclicBarrier barrier = new CyclicBarrier(3);
ExecutorService executor = Executors.newFixedThreadPool(3);
executor.submit(new Thread(new Runner(barrier, "No1 Runner")));
executor.submit(new Thread(new Runner(barrier, "No2 Runner")));
executor.submit(new Thread(new Runner(barrier, "No3 Runner")));
executor.shutdown();
}
}
执行结果:从输出run的执行时间可以看出,当所有的await()方法执行完成之后,后序的动作确实是同时执行的。
3. Semaphore
计数信号量,信号量维护了一个许可集合。
通过acquire()来获取一个许可,只有成功获取了许可,线程才能继续执行。如果无法获取到许可,线程将阻塞。
通过release()来是放一个许可,只有成功释放了许可,其他线程才能去获取新的许可。
DEMO
package com.jimmy.concurrent.util;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
public class SemaphoreDemo {
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
final Semaphore semaphore = new Semaphore(2);
for (int i = 0 ; i < 5; i ++) {
Runnable run = () -> {
try {
System.out.println(Thread.currentThread() + "try to require permit");
// 获取许可
semaphore.acquire();
System.out.println(Thread.currentThread() + "require permit.");
Thread.sleep(1000);
System.out.println(Thread.currentThread() + "release permit");
// 释放许可,如果不释放,所有未获取到许可的线程将被阻塞
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
};
executorService.submit(run);
}
executorService.shutdown();
}
}
正常释放许可的结果:每次有两个线程获取到许可并继续执行。
未正常释放许可的结果:许可耗尽之后,其他线程阻塞
4. Exchanger
交换器,可以在两个线程之间互换对象的汇合点。
必须是两个线程,且都已达到汇合点,才能进行数据交换
package com.jimmy.concurrent.util;
import java.util.Random;
import java.util.concurrent.Exchanger;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ExchangerDemo {
public static void main(String[] args) {
Exchanger<String> exchanger = new Exchanger<>();
ExecutorService executor = Executors.newCachedThreadPool();
executor.submit(new ExchangerRunner(exchanger, "A"));
executor.submit(new ExchangerRunner(exchanger, "B"));
executor.shutdown();
}
}
class ExchangerRunner implements Runnable {
private Exchanger<String> exchanger;
private String data;
public ExchangerRunner(Exchanger<String> exchanger, String data) {
this.exchanger = exchanger;
this.data = data;
}
@Override
public void run() {
try {
System.out.println(Thread.currentThread() + " data before exchange " + this.data);
Thread.sleep(new Random().nextInt(5) * 1000);
// 记录达到汇合点的时间
System.out.println(Thread.currentThread() + " is wait for exchange [" + System.currentTimeMillis() + "]");
// 当两个线程都达到该汇合点时,执行交换。
this.data = this.exchanger.exchange(this.data);
// 从这条记录的时间戳可以看出,达到汇合点的交换是同时执行的。
System.out.println(Thread.currentThread() + " data after exchange " + this.data + "[" + System.currentTimeMillis() + "]");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
可以从时间戳看出,当两个线程都达到汇合点之后,才会执行交换。
5. ReentrantLock
重入锁,可以替代Synchronized关键字,对同步代码块加锁。
与Condition一起使用,实现线程之间的通信。
一个Condition对应一个ReentrantLock
一个ReentrantLock可以创建多个Condition, 不同condition之前的通信不互相影响。
DEMO:一个ReentrantLock对应一个Condition
package com.jimmy.concurrent.util;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockDemo {
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
public void runAwaitTest() {
try {
lock.lock();
System.out.println(Thread.currentThread().getName() + " waiting.");
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + " condition await.");
// 释放锁并阻塞,直到重新获取到锁
condition.await();
System.out.println(Thread.currentThread().getName() + " continue...");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println(Thread.currentThread().getName() + " finish.");
lock.unlock();
}
}
public void runSignalTest() {
try {
lock.lock();
System.out.println(Thread.currentThread().getName() + " start.");
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + " condition signal.");
// 通知其他线程可以竞争锁
condition.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println(Thread.currentThread().getName() + " finish.");
lock.unlock();
}
}
public static void main(String[] args) {
ReentrantLockDemo demo = new ReentrantLockDemo();
Thread t1 = new Thread(() -> {
demo.runAwaitTest();
}, "t1");
Thread t2 = new Thread(() -> {
demo.runSignalTest();
}, "t2");
t1.start();
t2.start();
}
}
执行结果:
DEMO:一个ReentrantLock对应多个Condition
package com.jimmy.concurrent.util;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockWithMultiConditionDemo {
private Lock lock = new ReentrantLock();
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();
public void runAwait1Test() {
try {
lock.lock();
System.out.println(Thread.currentThread().getName() + " waiting." + "[" + System.currentTimeMillis() + "]");
System.out.println(Thread.currentThread().getName() + " condition-1 await." + "[" + System.currentTimeMillis() + "]");
// 释放锁并阻塞,直到重新获取到锁
condition1.await();
System.out.println(Thread.currentThread().getName() + " continue..." + "[" + System.currentTimeMillis() + "]");
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println(Thread.currentThread().getName() + " finish." + "[" + System.currentTimeMillis() + "]");
lock.unlock();
}
}
public void runAwait2Test() {
try {
lock.lock();
System.out.println(Thread.currentThread().getName() + " waiting." + "[" + System.currentTimeMillis() + "]");
System.out.println(Thread.currentThread().getName() + " condition-1 await." + "[" + System.currentTimeMillis() + "]");
// 释放锁并阻塞,直到重新获取到锁
condition1.await();
System.out.println(Thread.currentThread().getName() + " continue..." + "[" + System.currentTimeMillis() + "]");
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println(Thread.currentThread().getName() + " finish." + "[" + System.currentTimeMillis() + "]");
lock.unlock();
}
}
public void runAwait3Test() {
try {
lock.lock();
System.out.println(Thread.currentThread().getName() + " waiting." + "[" + System.currentTimeMillis() + "]");
System.out.println(Thread.currentThread().getName() + " condition-2 await." + "[" + System.currentTimeMillis() + "]");
// 释放锁并阻塞,直到重新获取到锁
condition2.await();
System.out.println(Thread.currentThread().getName() + " continue..." + "[" + System.currentTimeMillis() + "]");
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println(Thread.currentThread().getName() + " finish." + "[" + System.currentTimeMillis() + "]");
lock.unlock();
}
}
public void runSignalAllTest() {
try {
lock.lock();
System.out.println(Thread.currentThread().getName() + " start." + "[" + System.currentTimeMillis() + "]");
System.out.println(Thread.currentThread().getName() + " condition-1 signalAll." + "[" + System.currentTimeMillis() + "]");
// 通知其他线程可以竞争锁
condition1.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println(Thread.currentThread().getName() + " finish." + "[" + System.currentTimeMillis() + "]");
lock.unlock();
}
}
public void runSignalTest() {
try {
lock.lock();
System.out.println(Thread.currentThread().getName() + " start." + "[" + System.currentTimeMillis() + "]");
System.out.println(Thread.currentThread().getName() + " condition-2 signal." + "[" + System.currentTimeMillis() + "]");
// 通知其他线程可以竞争锁
condition2.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println(Thread.currentThread().getName() + " finish." + "[" + System.currentTimeMillis() + "]");
lock.unlock();
}
}
public static void main(String[] args) {
ReentrantLockWithMultiConditionDemo demo = new ReentrantLockWithMultiConditionDemo();
Thread t1 = new Thread(() -> {
demo.runAwait1Test();
}, "t1");
Thread t2 = new Thread(() -> {
demo.runAwait2Test();
}, "t2");
Thread t3 = new Thread(() -> {
demo.runAwait3Test();
}, "t3");
t1.start(); //c1
t2.start(); //c1
t3.start(); //c2
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Thread t4 = new Thread(() -> {
demo.runSignalAllTest();
}, "t4");
t4.start(); // c1 all
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Thread t5 = new Thread(() -> {
demo.runSignalTest();
}, "t5");
t5.start(); // c2
}
}
执行结果:
可以看出,程序执行过程中,不同condition是不会相互影响的。condition-1 signalAll()只对t1、t2线程生效,使其获得锁可以继续执行。t3由于没有获取condition的signal()所以在继续阻塞。
6. ReentrantReadWriteLock
读写锁, 读写分离机制,读读共享,读写互斥、写写互斥。
在读多写少的场景下,效率高于ReentrantLock
公平性问题
所为公平,就是多个线程竞争锁的时候,以先到先得的方式来获得锁。
但是公平性的保证需要额外维护一套竞争锁的线程的顺序,因此效率会比非公平的情况下更低。
Semaphore和ReentrantLock都存在保证公平性的机制