1、各种同步控制工具的使用
1.1 ReentrantLock
它是synchronized的增强版,sync功能简单,把多余的线程放入等待区,这些线程只能死等;写法上有区别;synchronized也是可重入的(它拥有强制原子性的内置锁机制,是一个重入锁,所以在使用synchronized时,当一个线程请求得到一个对象锁后再次请求此对象锁,可以再次得到该对象锁,就是说在一个synchronized方法/块的内部调用本类的其他synchronized方法/块时,是永远可以拿到锁);
a、可重入:
当线程请求一个由其它线程持有的对象锁时,该线程会阻塞,而当线程请求由自己持有的对象锁时,如果该锁是重入锁,请求就会成功,否则阻塞.
同一个线程可多次进入;多次加锁lock()后,必须多次调用unlock(),它是计数的,记录线程获取的许可数;
b、可中断:
使用lockInterruptibly()方法加锁,用lock()无效;被中断后会抛出异常InterruptedException();找到需要中断的线程(如死锁线程),调用interrupt()方法即可中断
c、可限时:
是一种避免死锁、长期等待的有效措施;使用tryLock(time),判断是否可在有限时间内获取锁;
d、公平锁:
构造函数中ReentrantLock(fair);公平锁可根据线程的等待时间顺序获得锁;
1.2 Condition
Condition和ReentrantLock的关系,类似synchronized与Object.wait()/notify()的关系,一个对应lock,一个对应monitor;
Condition使用前必须获得lock;
接口主要有await(),await(time),singnal(),singnalAll()等
1.3 Semaphore(信号量)
之前的锁都是互斥的、排他的;信号量类似是许可为N的锁,锁是许可为1的信号量;广义上可以认为信号量是共享锁,多个线程可以同时使用,同时进入临界区;
主要接口:acquire(),acquire(n),tryAcquire(),release();
1.4 ReadWriteLock
之前的锁都是部分功能的,只要线程进来必须获得对象锁;但是在读写的场景需要特殊处理,有写操作的时候才需要加锁(有等待的阻塞),读可以不加锁,可以多个线程同时执行(无等待的并发);JDK5中提供的。
读读不互斥、不阻塞;读写互斥、读阻塞写、写也会阻塞读;写写互斥、写写阻塞;
使用ReentrantReadWriteLock(); 获取读锁readLock(); 获取写锁writeLock();
1.5 CountDownLatch
倒数计时器;比如场景:发射火警,前期检查多条件,燃料、天气、温度等多条件;所有线程都达到时,主线程(受控线程)才能执行;
CountDownLatch(N); coutDown();(递减直至清零) await();(清零后返回,不再等待)
1.6 CyclicBarrier
循环栅栏,栅栏和CountDownLatch相像,cyclic是循环工作使用;比如士兵集合,只有所有士兵到达完成之后才是集合完毕,集合完毕之后可以再进行第二次任务,循环使用;
CyclicBarrier(int parties,Runnable n); await();
1.7 LockSupport(系统级的实现)
锁支持,提供的比较底层的操作;
park()(线程挂起); unpark()(线程继续执行); 跟suspend()-resume()有些相似(不建议使用);unpark()若发生在park()之前,不会产生线程冻结;能够响应中断,但不抛出中断异常;中断响应的结果是park(0函数返回,可以使用Thread.interrupted()得到中断标志;、
JDK内部使用广泛,比较底层的原语操作;掉用的是Unsafe.park(false, 0L);
1.8 ReentrantLock的实现原理
比较应用级的东西,是java应用级的实现;AQS: AbstractQueuedSynchronizer,AQS使用int类型的volatile变量维护同步状态(state),使用Node实现FIFO队列来完成线程的排队执行;
CAS状态:0/1判断锁是否被占用,线程重入后是正数;
等待队列:其它线程会进入等待队列;
park():等待队列中使用LockSupport实现;lock() -> sync.lock() -> acquire(1) -> park(this); unlock() -> sync.release(1); -> unpark(s.thread)
2、 并发容器及典型源码分析
2.1 集合包装
2.2 ConcurrentHashMap
HashMap:非线程安全;-> Collections.synchronizedMap(使用synchronized实现同步,并行解决方案,非高并发)
-> ConcurrentHashMap(高并发解决方案,JDK中分多个Segment<K,V>,相当于分多个小HashMap,Segment继承了ReentrantLock,
使用了tryLock()等, 所以有很多Unsafe、CAS实现;
get()/set()是无锁的。size()操作是,需要将多有的Segment调用lock()方法上锁)
2.3 BlockingQueue
阻塞队列,接口;线程安全的,但不是高性能的队列;它是一个多线程环境共享数据的容器;如果队列为空,当有线程去读时会阻塞,直到有数据;;;
poll(); add(); offer(); take(), put() 等; 实现类很多,如ArrayBlockingQueue、LinkedBlockingQueue,ConcurrentLinkedQueue等;
ArrayBlockingQueue /LinkedBlockingQueue:有成员变量ReentrantLock、Condition(notFull / notEmpety, await()/signal())等,有很多lock()等加锁操作;适用于生产者、消费者场景等;
2.4 ConcurrentLinkedQueue
高性能队列,无锁;大量Unsafe/CAS操作;
3、JDK并发包2
3.1 线程池的基本使用
3.1.1 为什么使用线程池
线程的创建和销毁是非常高的,而且跟业务室没关系的;线程池的作用是可以复用线程,减少CPU开销,
3.1.2 JDK为我们提供了哪些支持
Executor( void execute(Runnable command);线程池的本质就是执行任务,比如runnable,怎样调度由具体类实现),ExecutorService(接口;runnable无返回值,callable有返回值),
AbstractExcutorService(抽象类),ThreadPoolExecutor(重要的,是具体的实现);
ScheduledExecutorService(接口);ScheduledThreadPoolExecutor(实现类;其他线程池都是提交和运行一次任务,该类可以持续的多次执行常见的任务;两种任务类型:scheduleAtFixedRate()
和scheduleWithFixedDelay();
);
Executors(工厂方法,返回一个线程池);
3.1.3 线程池的使用
a、线程池的种类
newFixedThreadPool
ExecutorService executor = Executors.newFixedThreadPool(1);
Future<Integer> future = executor.submit(task);
System.out.println("future done? " + future.isDone());
Integer result = future.get();
System.out.println("future done? " + future.isDone());
System.out.print("result: " + result);
调用isDone()
来检查这个future是否已经完成执行;在调用get()
方法时,当前线程会阻塞等待,直到callable在返回实际的结果123之前执行完成。
newSingleThreadExecutor(new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());)newCachedThreadPool(new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>());)
newScheduledThreadPool(new ScheduledThreadPoolExecutor(corePoolSize);)
newWorkStealingPool()(return new ForkJoinPool(Runtime.getRuntime().availableProcessors(), ForkJoinPool.defaultForkJoinWorkerThreadFactory, null, true);;
jdk8引入,返回一个
ForkJoinPool
类型的executor,与其他execuotr稍有不同。
与使用一个固定大小的线程池不同,
ForkJoinPools
使用一个并行因子数来创建,默认值为主机CPU的可用核心数。)
ExecutorService executor = Executors.newWorkStealingPool();
List<Callable<String>> callables = Arrays.asList(
() -> "task1",
() -> "task2",
() -> "task3");
executor.invokeAll(callables)
.stream()
.map(future -> {
try {
return future.get();
} catch (Exception e) {
throw new IllegalStateException(e);
}
})
.forEach(System.out::println);
b、不同线程池的共同性
都是由ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue)实现的,
3.1.4 线程池使用的小例子
a、简单线程池
b、ScheduledThreadPool
3.2 扩展和增强线程池
3.2.1 回调接口
线程池提供了一些扩展操作,回调API等,比如:ThreadPoolExecutor()的beforeExecute(), afterExecuter(); terminated()等;捕获线程池执行线程时的细节信息,查看等;
3.2.2 拒绝策略
ThreadPoolExecutor的构造函数中的参数:RejectedExecutionHandler;设置任务不能执行了,应该怎么做;
策略有AbortPolicy、DiscardPolicy、DiscardOldestPolicy、CallerRunsPolicy等
3.2.3 自定义ThreadFactory
线程工厂ThreadFactory,创建线程,给线程起名字分配组,优先级等;
3.3 线程池及其核心代码分析
分析Executor的Executo()核心方法的执行逻辑,将线程池的存活状态及活跃线程数放到了一个原子类中维护,AtomicInteger ,ctl,runStateOf,workerCountOf;
3.4 Fork Join
3.4.1 思想(ForkJoinPool继承了AbstractExcutorService,JDK7和8有些差别)
ForkJoinPool,比较新的线程池;大任务分割成多个小任务,执行完后进行整合;线程的栈使用链表实现;
分而治之的思想;分解任务、提交任务、收集结果、最终结果;
比如:从1加到2千万等任务;
成员变量ctl,是一个包装变量,包括五个子变量:AC/TC/ST/EC/TD,保证多线程内五个变量同时的原子性,CAS更快;
多线程执行任务期间相互帮助,工作窃取,共同完成任务;
3.4.2 使用接口
a、RecursiveAction 无返回值;具体任务继承之;
b、RecursiveTask 有返回值;具体任务继承之; subTask.fork(), list(task).join();
3.4.3 简单例子
ForkJoinPool实现代码的原理挺复杂!
3.4.4 实现要素
a、工作窃取