java高并发程序设计学习笔记五六JDK并发包

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、工作窃取





发布了64 篇原创文章 · 获赞 45 · 访问量 8万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章