Runnable 不可返回值
Callable 可以返回值,返回Future(Java8可用CompletableFuture)
Callable.call()必须有返回时,FutureTask.get()方法才会执行,否则一直阻塞
Java8新提供CompletableFuture,可以传入回调对象,当异步任务完成或者发生异常时,自动调用回调对象的回调方法
1 | /** |
也可以并行执行,逻辑流程为:
其中CompletableFuture.anyOf()
含义为:只要任意一个成功,即可执行下面的逻辑,CompletableFuture.allOf()
含义为:全部成功之后执行下面逻辑
三个线程轮流输出1-100,不能重复
可以用JUC中LockSupport类中park()方法进行阻塞,unpark()方法进行解除阻塞
1 | static Thread t1 = null; |
测试接口并发承受量
1 | /** |
线程可见性
volatile 关键字
- CPU指令重排序
当cpu写缓存时发现缓存区块被其他cpu占用,可能将后面的读缓存命令优先执行,但是不管怎么重排序,(单线程)程序的执行结果不能被改变,runtime和处理器需遵守as-if-serial
语义
告诉JVM被修饰的变量不要缓存,直接写入内存,所以也可以防止程序被JVM重排序,防止另外的线程拿到错误的数据
比如A线程将i变量加1,B线程紧接着要去对i这个共享资源进行操作。
synchronized 关键字
被synchronized修饰的代码,在获取锁前,在释放锁后都能保证线程可见性
Integer类型多线程值的变更
推荐使用AtomicInteger工具类
1 | AtomicInteger atomicInteger = new AtomicInteger(); |
火车票超卖案例
1 | // 测试100张火车票 |
读多写少时推荐使用读写锁
ReentrantLock虽然可以解决高并发的问题,但是效率不高,关于读取其实我们没有必要上锁,我们只需要保证任意时刻,只有一个线程进行修改即可
这时候推荐使用ReadWriteLock/StampedLock(Java8新增)读写锁
- 只允许一个线程写入
- 没有写入时,多个线程允许同时读
1 | public class Counter { |
ReadWriteLock和StampedLock区别
ReadWriteLock :如果有线程正在读,写线程需要等待读线程释放锁后才能获取写锁,即读的过程中不允许写,这是一种悲观的读锁。
StampedLock和ReadWriteLock相比,改进之处在于:读的过程中也允许获取写锁后写入!这样一来,我们读的数据就可能不一致,所以,需要一点额外的代码来判断读的过程中是否有写入,这种读锁是一种乐观锁。
定时执行线程
1 | ScheduledExecutorService ses = Executors.newScheduledThreadPool(4); |
- 注意FixedRate和FixedDelay的区别。FixedRate是指任务总是以固定时间间隔触发,不管任务执行多长时间
- 而FixedDelay是指,上一次任务执行完毕后,等待固定的时间间隔,再执行下一次任务
并发限流测试
1 | RateLimiter limiter = RateLimiter.create(50); |