java多线程工具_几个常见的多线程工具类

重入锁

java.util.concurrent.locks.ReentrantLock

重入锁的作用和synchronized关键字一样,为代码块加锁。但与synchronized关键字原理不一样,synchronized关键字是根据对象头的锁标志判断当前线程是否可以获得锁,而重入锁是基于AbstractQueuedSynchronizer,底层是CAS,是一种乐观锁(无锁)。

重入锁的基本使用如下:

public static class HasLock implements Runnable {

private ReentrantLock lock = new ReentrantLock();

private int i = 0;

@Override

public void run() {

try {

Thread.sleep(5);

lock.lock();

i++;

lock.unlock();

} catch (InterruptedException e) {

e.printStackTrace();

}

}

public int getI() {

return i;

}

}

在成员变量声明一个ReentrantLock对象,在需要同步的代码块中调用lock();unlock();方法。

在声明重入锁的时候,构造方法有个boolean 参数 fair可以传,如果fair为true,表示是一个公平锁,先申请锁的线程,先执行。为false,则为非公平锁,不能保证线程获得锁的顺序。

正常来说,一个线程在等待synchronized锁时,是没法取消的,只能等目标线程释放锁之后,代码继续执行,如果此时需要中断线程,只能等线程获取锁之后,中断操作才会进行。重入锁提供了lockInterruptibly()方法,使线程在等待锁的过程中,也可以中断,抛出InterruptedException。见下例子:

public static class TwoLock implements Runnable {

private Integer i;

private ReentrantLock lock1;

private ReentrantLock lock2;

public TwoLock(Integer i, ReentrantLock lock1, ReentrantLock lock2) {

this.i = i;

this.lock1 = lock1;

this.lock2 = lock2;

}

@Override

public void run() {

try {

if (i == 1) {

//如果用lock,线程没法中断

// lock1.lock();

//使用lockInterruptibly,可以随时中断线程

lock1.lockInterruptibly();

System.out.println("i=1, 1上锁");

Thread.sleep(500);

lock2.lockInterruptibly();

System.out.println("i=1, 2上锁");

} else {

lock2.lockInterruptibly();

System.out.println("i=2, 2上锁");

Thread.sleep(500);

lock1.lockInterruptibly();

System.out.println("i=2, 1上锁");

}

} catch (InterruptedException e) {

if (lock1.isHeldByCurrentThread()) {

lock1.unlock();

}

if (lock2.isHeldByCurrentThread()) {

lock2.unlock();

}

System.out.println(Thread.currentThread().getName() + "中断了");

}

}

}

private static void interrupt() throws Exception {

ReentrantLock lock1 = new ReentrantLock();

ReentrantLock lock2 = new ReentrantLock();

TwoLock l1 = new TwoLock(1, lock1, lock2);

TwoLock l2 = new TwoLock(2, lock1, lock2);

Thread t1 = new Thread(l1);

Thread t2 = new Thread(l2);

t1.start();

t2.start();

Thread.sleep(5000);

System.out.println("hello world");

t1.interrupt();

}

t1线程先获取锁1,500ms后获取锁2,与此同时,t2线程获取锁2,500ms后获取锁1。

由于500ms后,由于两把锁分别被两个线程占用,导致死锁。但是上锁使用的是lockInterruptibly()方法,在5000ms后,发现代码还没执行完毕,中断线程1,并在exception中释放锁1,这样线程2就可以拿到锁1了,线程2顺利执行。如果使用的lock()和synchronized锁,那么这两个线程永远无法停止

重入锁还提供了tryAcquire()方法,用于判断当前线程是否获得锁,返回的是个boolean值,有点像我们状态机里的redis锁,如果获取锁失败,执行xxx逻辑,tryAcquire()如果无参,立即返回结果,如果有参,则等待指定时间后,返回结果。

重入锁有个搭档,Condition,作用相当于Object里面的wait(),notify()方法,用法如下:

private static void condition() throws InterruptedException {

ReentrantLock lock = new ReentrantLock();

Condition condition = lock.newCondition();

ExecutorService es = Executors.newFixedThreadPool(3);

for (int i = 0; i < 3; i++) {

es.execute(() -> {

System.out.println(Thread.currentThread().getName() + "开始执行");

try {

lock.lock();

condition.await();

System.out.println(Thread.currentThread().getName() + "继续执行");

} catch (InterruptedException e) {

e.printStackTrace();

} finally {

lock.unlock();

}

});

}

Thread.sleep(5000);

lock.lock();

condition.signalAll();

lock.unlock();

}

当调用await()方法时,当前线程阻塞,直到有其他线程调用signal()方法或者signalAll()方法,线程继续执行。

读写锁

java.util.concurrent.locks.ReadWriteLock

相当于数据库的读写锁,同一把锁一分为二,分为读锁和写锁,读读不互斥,读写互斥,写写互斥,用法如下:

public static void main(String[] args) {

ReadWriteLock readWriteLock = new ReentrantReadWriteLock();

new Thread(() -> {

readWriteLock.readLock().lock();

try {

System.out.println("读线程1获取读 ");

try {

Thread.sleep(2000);

} catch (Exception ex) {

}

System.out.println("线程1结束");

} finally {

readWriteLock.readLock().unlock();

}

}).start();

new Thread(() -> {

readWriteLock.readLock().lock();

try {

System.out.println("读线程2获取读 ");

try {

Thread.sleep(2000);

} catch (Exception ex) {

}

System.out.println("线程2结束");

} finally {

readWriteLock.readLock().unlock();

}

}).start();

new Thread(() -> {

Lock lock = readWriteLock.writeLock();

lock.lock();

try {

System.out.println("写线程3获取写 ");

try {

Thread.sleep(2000);

} catch (Exception ex) {

ex.printStackTrace();

}

System.out.println("线程3结束");

} finally {

lock.unlock();

}

}).start();

}

运行结果

线程1和线程2同时进行,线程3需要等待线程1和线程2完成,且释放读锁后才会执行

信号量

java.util.concurrent.Semaphore

一般来说,需要同步的代码块,一次只能一个线程执行,读写锁的话,只能多个读线程同时执行。使用信号量,可以让指定线程数量,同时访问同一个资源。比方说,5个售票窗,100个人买票,那么同一时间,最多也就只能卖出5张票,信号量就是售票窗,如果只有一个售票窗,那么可以理解为这个售票窗就是锁,只有前面的人买完了,后面的人才可以买票。信号量的使用如下:

public static void main(String[] args) {

Runnable rb = new Runnable() {

private Semaphore semaphore = new Semaphore(5);

@Override

public void run() {

try {

semaphore.acquire();

System.out.println(Thread.currentThread().getName() + "开始");

Thread.sleep(5000);

System.out.println(Thread.currentThread().getName() + "结束");

semaphore.release();

} catch (InterruptedException e) {

e.printStackTrace();

}

}

};

ExecutorService es = Executors.newFixedThreadPool(5);

for (int i = 0; i < 20; i++) {

es.submit(rb);

}

ThreadPoolUtils.stopPool(es);

}

以上代码,一次最多5个线程访问run()里面的内容

倒计时器

java.util.concurrent.CountDownLatch

一个任务,分成3个线程执行,所有线程执行完了之后,才执行下一步,正常写代码我们会使用join()在主线程阻塞3个线程,当3个线程都完成后,主线程继续执行,CountDownLatch可以帮我们完成这件事情,使用如下:

public static void main(String[] args) throws InterruptedException {

CountDownLatch countDownLatch = new CountDownLatch(10);

ExecutorService executorService = Executors.newFixedThreadPool(3);

for (int i = 0; i < 15; i++) {

final Integer j = i;

executorService.execute(() -> {

try {

countDownLatch.countDown();

System.out.println(j + ":准备就绪");

Thread.sleep(500);

} catch (InterruptedException e) {

e.printStackTrace();

}

});

}

System.out.println("等待所有线程就绪");

countDownLatch.await();

System.out.println("十个线程已经就绪");

ThreadPoolUtils.stopPool(executorService);

}

调用countDown()方法,通知计时器-1,await()方法等待所有线程执行完毕,当计时器为0时,await()之后的代码继续执行。相当于3个任务全部完成。

循环栅栏

java.util.concurrent.CyclicBarrier

功能与倒计时器类似,不过它可以重复统计,如果CountDownLatch是一辆车,人满车开,那么CyclicBarrier是一批车,前一辆车开走后,当等待乘车的人满后,会有新的车来将这些人接走,如果人没满,那么等待的人一直等待,直到车来为止。而如果是CountDownLatch,车只会开一次,后面不管有多少人在等车,都不在会有车到来。用法如下:

public static void main(String[] args) {

CyclicBarrier cb = new CyclicBarrier(2);

for (int i = 0; i < 5; i++) {

new Thread(() -> {

try {

Thread.sleep(1000);

cb.await();

System.out.println("每2个线程执行完就会打印这句话");

} catch (InterruptedException e) {

e.printStackTrace();

} catch (BrokenBarrierException e) {

e.printStackTrace();

}

}).start();

}

}

以上语句只会打印4次,第五个线程会一直等待第六个线程的出现,因为CyclicBarrier设置的值为2,每2个线程执行完,才会打印。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值