在多核处理器时代,
使用
public class ForkJoinPoolMain {
public static void main(String[] args) {
var forkJoinPool = new ForkJoinPool();
var sumTask = new SumTask(0, 100000000);
forkJoinPool.submit(sumTask);
System.out.println(sumTask.join());
}
}
class SumTask extends RecursiveTask<Long> {
private final static int TASK_THRESHOLD = 10000;
private final int start;
private final int end;
public SumTask(int start, int end) {
this.start = start;
this.end = end;
}
@Override
protected Long compute() {
if (end - start < TASK_THRESHOLD) {
return LongStream.range(start, end).sum();
} else {
int middle = start + (end - start) / 2;
var sumTask1 = new SumTask(start, middle);
var sumTask2 = new SumTask(middle, end);
return sumTask1.fork().join() + sumTask2.fork().join();
}
}
}
我们知道ReadWriteLock可以实现读写分离,但是读写之间仍旧是需要同步的。当有大量的读线程,那么也会造成写线程的长时间阻塞引发饥饿的问题。有没有一种锁可以针对这种场景做些优化呢?今天我们就来看下这个作为读写锁的升级版:StampedLock
使用
StampedLock
提供了一种乐观的读策略。这种乐观的锁非常类似无锁的操作,使得乐观锁完全不会阻塞写线程。
分析
StampedLock
的内部实现是基于CLH锁的。CLH锁是一种自旋锁,它保证没有饥饿发生,并且可以保证FIFO的服务顺序。
线程是操作系统的资源,频繁的线程创建和销毁势必会影响并发的吞吐量。java 5引入了线程池实现了线程的复用,很好的解决了线程创建和销毁的问题。今天我们就来看下这个池子中究竟有什么
什么是线程池
类似于数据库的连接池,线程池里面保存的是线程而不是数据库连接。在线程池中,会有一个或多个可用的线程。当需要使用线程时,就可以从池子中随便取一个可用的空闲线程。当你完成工作时,不必关闭线程,而是把线程归还到线程池中,方便后续的线程需要。
线程池具有以下 3 点优势:
- 降低资源消耗,重复利用已经创建的线程,避免线程创建与销毁带来的资源消耗
- 提高响应速度,接收任务时,可以通过线程池直接获取线程,避免了创建线程带来的时间消耗
- 便于管理线程,统一管理和分配线程,避免无限制创建线程,另外可以引入线程监控机制
Condition将Object
监视器方法(wait、notify和notifyAll)分解成截然不同的对象,以便通过将这些对象与任意Lock实现组合使用,为每个对象提供多个等待set(wait-set)。其中,Lock替代了synchronized方法和语句的使用,Condition替代了Object监视器方法的使用。今天我们就通过实例来学习一个Condition的用法。
使用
这里列举一个关于装水取水的例子:
运行的结果如下:不固定
p0 t0 t1 p1 p2 p3 p4 t2 t3 p5 p6 p7 t4 t5 p8 t6 p9 p10 t7 p11 t8 p12 t9 t10 t11 t12 t13 p13 p14 p15 t14 p16 t15 p17 t16 p18 t17 p19 t18 t19
线程之间的协作姿势千姿百态,如果两个线程之间的数据想要互换这该如何实现?对头,Exchanger
就是为此而生的。它可以在两个线程之间交换数据,而且只能是2
个线程,线程多了还不行。今天我们就通过实例来学习一下Exchanger
的用法。
使用
Exchanger
是在两个任务之间交换对象的栅栏,当任务进入栅栏时,它们各自拥有一个对象。当他们离开时,它们都拥有之前由对方线程持有的对象。
使用
public class LockSupportMain {
public static void main(String[] args) {
var thread = new Thread(() -> {
for (int i = 0; i < 3; i++) {
System.out.printf("i = %d, time = %s\n", i, LocalDateTime.now());
secondsSleep(1);
if (i == 1) {
LockSupport.park();
}
}
});
thread.start();
secondsSleep(4);
System.out.println("After 4 seconds");
LockSupport.unpark(thread);
}
private static void secondsSleep(int seconds) {
try {
TimeUnit.SECONDS.sleep(seconds);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
使用
public class ReadWriteLockMain {
private static int value = 0;
static void read(Lock lock) {
try {
lock.lock();
TimeUnit.SECONDS.sleep(1);
System.out.printf("Finish read %d, time: %s\n", value, LocalDateTime.now());
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
lock.unlock();
}
}
static void write(Lock lock, int newVal) {
try {
lock.lock();
TimeUnit.SECONDS.sleep(1);
value = newVal;
System.out.printf("Finish write %d, time: %s\n", newVal, LocalDateTime.now());
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
var lock = new ReentrantReadWriteLock();
for (int i = 0; i < 3; i++) new Thread(() -> read(lock.readLock())).start();
for (int i = 0; i < 2; i++) new Thread(() -> write(lock.writeLock(), new Random().nextInt(12))).start();
}
}
之前介绍的synchronized
关键字就是一种最简单的同步控制方法,而在JDK 5引入了重入锁与使用synchronized
具有相同的基本行为,但具有扩展功能。现在我们来学习下重入锁。
重入锁的使用
重入锁使用java.util.concurent.locks.ReentrantLock类来实现。下面是一段最简单的重入锁使用案例:
public class Reenterlock implements Runnable {
public static final Lock LOCK = new ReentrantLock();
public static int i = 0;
@Override
public void run() {
for (int j = 0; j < 1000_000; j++) {
LOCK.lock();
try {
i++;
} finally {
LOCK.unlock();
}
}
}
public static void main(String[] args) throws InterruptedException {
var reenterlock = new Reenterlock();
var t1 = new Thread(reenterlock);
var t2 = new Thread(reenterlock);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(i); // 2000000
}
}
CyclicBarrier
是另外一种多线程并发控制实用工具。和CountDownLatch
非常类似,它也可以实现线程间的计数等待,但它的功能比CountDownLatch
更加复杂且强大。
CyclicBarrier的使用
CyclicBarrier
适用于这样的情况:你希望创建一组任务,它们并行地执行工作,然后在进行下一个步骤之前等待,直至所有任务都完成。类似于电商中的拼团、拼购,先准备购买的人必须等待,直到要购买的人数达到一定值时才开团。