(1)、重入锁(ReentrantLock)
- 重入锁使用java.util.concurrent.locks.ReentrantLock,下面是简单的使用案例
public class ReenterLock implements Runnable {
public static ReentrantLock lock = new ReentrantLock();
public static int i = 0;
@Override
public void run() {
for(int j = 0; j < 100000; j++) {
lock.lock();
try {
i++;
}finally {
lock.unlock();
}
}
}
public static void main(String[] args) throws InterruptedException {
ReenterLock t1 = new ReenterLock();
Thread thread1 = new Thread(t1);
Thread thread2 = new Thread(t1);
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println(i);
}
}
- 之所以叫重入锁,是因为这种锁是可以反复进入的。这里的反复进入只局限于同一个线程。
lock.lock();
lock.lock();
try{
i++;
}finally {
lock.unlock();
lock.unlock();
}
- 中断响应:重入锁提供可以使用中断响应来取消对锁的请求(lockInterruptibly()可以中断申请)
public class IntLock implements Runnable {
public static ReentrantLock lock1 = new ReentrantLock();
public static ReentrantLock lock2 = new ReentrantLock();
public int lock;
public IntLock(int lock) {
this.lock = lock;
}
@Override
public void run() {
try {
if (lock == 1) {
lock1.lockInterruptibly(); //请求lock1
try {
Thread.sleep(500);
} catch (InterruptedException e) {
}
lock2.lockInterruptibly(); //请求lock2
}else {
lock2.lockInterruptibly(); //请求lock2
try {
Thread.sleep(500);
} catch (InterruptedException e) {}
lock1.lockInterruptibly(); //请求lock1
}
}catch (InterruptedException e) {
e.printStackTrace();
}finally {
if(lock1.isHeldByCurrentThread()) { //查看当前线程是否被锁
lock1.unlock();
}
if(lock2.isHeldByCurrentThread()) { //查看当前线程是否被锁
lock2.unlock();
}
System.out.println(Thread.currentThread().getName() + "线程结束");
}
}
public static void main(String[] args) throws InterruptedException {
IntLock intLock1 = new IntLock(1);
IntLock intLock2 = new IntLock(2);
Thread thread1 = new Thread(intLock1,"线程1");
Thread thread2 = new Thread(intLock2,"线程2");
thread1.start();
thread2.start();
Thread.sleep(1000);
thread2.interrupt(); //中断线程2,取消线程2对lock1的请求
}
}
- 锁申请等待限时
避免死锁其中一个方法就是限时等待。使用trylock()方法。该方法接收两个参数,一个表示等待时长,另外一个代表计时单位。
public class TimeLock implements Runnable {
public static ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
try {
if(lock.tryLock(5, TimeUnit.SECONDS)) { //尝试5秒获得锁
Thread.sleep(6000);
}else {
System.out.println(Thread.currentThread().getName() + "get lock failed");
}
}catch (InterruptedException e) {
e.printStackTrace();
}finally {
if(lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
public static void main(String[] args) {
TimeLock time = new TimeLock();
Thread thread1 = new Thread(time);
Thread thread2 = new Thread(time);
thread1.start();
thread2.start();
}
}
- 公平锁:设置为公平锁,它会按照时间的先后顺序,保证先到者先得,后到者后得。不会产生饥饿现象,只要你排队就可以获得资源
一般锁的申请都是非公平的,没有特殊必要就不要设置为公平锁,因为需要产生有序队列,性能较低。
public class FairLock implements Runnable {
public static ReentrantLock fairLock = new ReentrantLock(true); //设置为公平锁
@Override
public void run() {
while(true) {
try {
fairLock.lock();
System.out.println(Thread.currentThread().getName() + "获得锁");
}finally {
fairLock.unlock();
}
}
}
public static void main(String[] args) {
FairLock f1 = new FairLock();
Thread t1 = new Thread(f1, "线程1");
Thread t2 = new Thread(f1, "线程2");
t1.start();
t2.start();
}
}
(2)、重入锁的好搭档:Condition
- Condition接口的基本方法如下
//使线程进入等待,释放当前锁,等待其他线程调用signal()方法唤醒,线程被中断也能唤醒
void await();
//与await()方法基本相似,但是不会被线程中断唤醒
void awaitUninterruptibly()
boolean await(long time, TimeUnit unit);
boolean await(Date deadline);
//唤醒线程
void signal();
void signalAll();
public class ReenterLockCondition implements Runnable {
public static ReentrantLock lock = new ReentrantLock();
public static Condition condition = lock.newCondition();//lock生成一个与之绑定的Condition对象
@Override
public void run() {
try {
lock.lock();
condition.await(); //释放锁,进入等待状态
System.out.println("Thread is going on");
}catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
ReenterLockCondition t = new ReenterLockCondition();
Thread t1 = new Thread(t);
t1.start();
Thread.sleep(2000);
lock.lock();
condition.signal(); //唤醒线程
lock.unlock();
}
}
(3)、允许多个线程同时访问:信号量(Samephore)
- 信号量主要提供了以下构造函数:
public Semaphore(int permits);
public Semaphore(int permits,boolean fair) //第二个参数指定是否为公平
-
在构造信号量对象时,必须要指定信号量的准入数,即同时能申请多少个许可。使用acquire()方法申请许可证,一个线程可以申请一个或者多个许可证
-
信号量的主要逻辑方法有:
//获取一个准时许可和获取多个准入许可
public void acquire() throws InterruptedException
public void acquire(int permits)
//与前两个方法相同,不过不接受中断
public void acquireUninterruptibly()
//尝试获得许可证
public boolean tryAcquire()
public boolean tryAcquire(long timeout, TimeUnit unit)
//释放资源
public void release()
public class SemapDemp implements Runnable {
final Semaphore semp = new Semaphore(5); //初始化许可证个数
@Override
public void run() {
try {
semp.acquire(); //尝试获得一个许可证
Thread.sleep(2000);
System.out.println(Thread.currentThread().getId()+":done");
}catch (InterruptedException e) {
e.printStackTrace();
}finally {
semp.release(); //释放资源
}
}
public static void main(String[] args) {
ExecutorService exec = Executors.newFixedThreadPool(20); //初始化线程池
final SemapDemp demo = new SemapDemp();
for(int i = 0; i < 20 ; i++) {
exec.submit(demo); //执行线程
}
}
}
- 线程获取许可证之后必须记得释放,否则会使许可证个数越来越少。
(4)、ReadWriteLock读写锁
- ReadWriteLock是JDK5中提供的读写分离锁。读写分离锁可以有效的减少锁竞争,提升性能。
- 读-读不互斥:读读之间不阻塞
- 读-写互斥:读阻塞写,写也会阻塞读
- 写-写互斥:写写阻塞
public class ReadWriteLockDemo {
private static Lock lock = new ReentrantLock();
private static ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private static Lock readLock = readWriteLock.readLock();
private static Lock writeLock = readWriteLock.writeLock();
private int value;
public Object handleRead(Lock lock) throws InterruptedException {
try {
lock.lock(); //模拟读操作
Thread.sleep(1000);
return value;
}finally {
lock.unlock();
}
}
public void handleWrite(Lock lock, int index) throws InterruptedException {
try {
lock.lock();
Thread.sleep(1000);
value=index; //模拟写操作
}finally {
lock.unlock();
}
}
public static void main(String[] args) {
final ReadWriteLockDemo demo = new ReadWriteLockDemo();
Runnable readRunnable = new Runnable() {
@Override
public void run() {
try {
demo.handleRead(readLock); //读,传入读锁
}catch (InterruptedException e) {
e.printStackTrace();
}
}
};
Runnable writeRunnable = new Runnable() {
@Override
public void run() {
try {
demo.handleWrite(writeLock,new Random().nextInt()); //写,传入写锁
}catch (InterruptedException e) {
e.printStackTrace();
}
}
};
for(int i = 0; i < 18; i++) {
new Thread(readRunnable).start(); //读线程之间不互相干扰
}
for(int i = 18; i < 20; i++) {
new Thread(writeRunnable).start(); //写线程会相互阻塞
}
}
}
(5)、倒计数器:CountDownLatch
- CountDownLatch相当于设置了一个计数器,awit()方法用于等待CountDownLatch的值减为0,才能继续向下执行。countDown()方法用于减少CountDownLatch的值
public class CountDownLatchDemo implements Runnable {
static final CountDownLatch end =new CountDownLatch(10); //初始化CountDownLatch
static final CountDownLatchDemo demo = new CountDownLatchDemo();
@Override
public void run() {
try {
Thread.sleep(1000); //模拟程序
System.out.println("check complete");
end.countDown(); //countDownLatch减1
}catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws InterruptedException {
ExecutorService service = Executors.newFixedThreadPool(10); //初始化线程池
for(int i = 0; i < 10; i++) {
service.submit(demo); //执行线程
}
end.await(); //等待CountDownLatch减为0,才能继续工作
System.out.println("finish");
service.shutdown(); //关闭线程池
}
}
(6)、循环栅栏:CyclicBarrier
- CyclicBarrier可以理解为循环栅栏。栅栏就是一种障碍物。
- CyclicBarrier可以用来阻止线程继续执行,要求线程在栅栏外等待。
- 前面Cyclic意为循环,开始,计数器设置为10,那么凑齐第一批10个线程等待了。计数器会归零,接着凑下一批10个线程 。
- Cyclic常用的构造函数如下:
public CyclicBarrier(int parties, Runnable barrierAction)
parties代表计数器的值,barrierAction是当一次计数完成后,系统会执行的动作。
public class CyclicBarrierDemo {
public static class Solder implements Runnable {
private String solider;
private final CyclicBarrier cyclicBarrier;
Solder(CyclicBarrier cyclicBarrier, String soliderName) { //构造函数
this.cyclicBarrier = cyclicBarrier;
this.solider = soliderName;
}
@Override
public void run() {
try {
cyclicBarrier.await(); //栅栏外等候,第一次计数
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(solider + "任务完成");
cyclicBarrier.await(); //栅栏外等候,第二次计数
}catch (InterruptedException e) {
e.printStackTrace();
}catch (BrokenBarrierException e) {
e.printStackTrace();
}
}
}
public static class BarrierRun implements Runnable {
boolean flag;
int N;
public BarrierRun(boolean flag, int N) {
this.flag = flag;
this.N = N;
}
@Override
public void run() {
if(flag) {
System.out.println("司令:[士兵" + N +"个,任务完成");
}else {
System.out.println("司令:[士兵" + N +"个,集合完毕");
flag = true;
}
}
}
public static void main(String[] args) {
final int N = 10; //计数器的值,线程总数
Thread[] allSolider = new Thread[N];
boolean flag = false; //标志位
CyclicBarrier cyclicBarrier = new CyclicBarrier(N, new BarrierRun(flag,N)); //初始化CyclicBarrier
System.out.println("集合队伍");
for(int i = 0; i < N; i++) {
System.out.println("士兵"+ i + "报道");
allSolider[i] = new Thread(new Solder(cyclicBarrier,"士兵"+i)); //创建线程
allSolider[i].start(); //运行
}
}
}
InterruptedException:线程等待过程中,线程被中断
BrokenBarrierException:有线程被中断了,无法达到计数器的值,无论如何无法达到预期值,避免无谓的等待。
(7)、线程阻塞工具类:LockSupport
- LockSupport是一个非常方便的实用的线程阻塞工具 ,它可以在线程内任意位置让线程阻塞。
- 注意:即使unpark()方法发生在park()方法之前,也不会导致线程永远挂起
- 因为LockSupport使用类似于信号量的机制。它为线程准备了一个许可,如果许可可用,那么park()方法立即返回,并且将许可变为不可用。如果许可不可用,线程就会阻塞。而unpark()方法则会将许可变为可用。
- LockSupport.park()方法还能支持中断影响。但是不会抛出InterruptedException。但是可以通过Thread.interrupted()等方法获得中断标记
public class LockSupportIntDemo {
public static Object u = new Object();
static ChangeObjectThread t1 = new ChangeObjectThread("t1");
static ChangeObjectThread t2 = new ChangeObjectThread("t2");
public static class ChangeObjectThread extends Thread {
public ChangeObjectThread(String name) {
super.setName(name);
}
@Override
public void run() {
synchronized (u) {
System.out.println("in " + getName());
LockSupport.park();
if(Thread.interrupted()) {
System.out.println(getName() + "被中断了");
}
}
System.out.println(getName() + "执行结束");
}
}
public static void main(String[] args) throws InterruptedException {
t1.start();
Thread.sleep(100);
t2.start();
t1.interrupt();
LockSupport.unpark(t2);
}
}
(8)、Guava和RateLimiter限流
- Guava是Google下的一个核心库,提供了一大批设计精良、使用方便的工具类。Guava是JDK标准库的重要补充
- RateLimiter采用了令牌桶算法
public class RateLimiterDemo {
static RateLimiter limiter = RateLimiter.create(2);
public static class Task implements Runnable {
@Override
public void run() {
System.out.println(System.currentTimeMillis());
}
}
public static void main(String[] args) {
for(int i = 0; i < 50; i++) {
limiter.acquire();
new Thread(new Task()).start();
}
}
}