《Java高并发程序设计》读书笔记(第三章)

同步控制

synchronized关键字是最简单的一种同步控制方法,它决定了一个线程能否访问临界区资源。

synchronized的功能扩展:重入锁

重入锁可完全代替synchronized关键字,通过java.util.concurrent.locks.ReentrantLock类来实现,如果同一个线程多次获得锁,那么在释放锁 的时候,也必须释放相同次数,如果释放的次数多,那就会得到java.lang,IllegalMonitorStateException异常,反则次数少了就相当于这个线程还持有锁,其他线程无法进入临界区。

  • 中断响应
    对synchronized来说,如果一个线程在等待锁,那么只能有两种可能,一种是继续等待,另一种是获得锁执行。
    而使用重入锁,会提供另外一种可能就是线程可以中断,在等待过程中可以随时取消对锁的需求。
  • 锁申请等待限时
    除了等待外部通知,还可以通过限时等待来避免死锁。tryLock();
  • 公平锁
    按照时间顺序,先来的先得。他最大的特点就是不会产生饥饿现象
    构造函数:
public ReentrantLock(boolean fair)

fair为true表示锁是公平的,因为实现公平锁就要系统维护一个有序队列,所以公平锁实现成本较高,性能低下,非必要条件下不会使用它。
非公平锁:一个线程会倾向于再次获取已持有的锁,这种分配方式高效但不公平。

ReentrantLock的重要方法:

lock():获得锁,若锁被占用则等待
lockInterruptibly():获得锁,但优先响应中断
tryLock():尝试获得锁成功返回true,失败返回false,该方法不等待,立即返回
tryLock(long time,TimeUnit nuit):在给定时间内尝试获得锁
unlock():释放锁

重入锁的三个要素:
1.原子状态。使用CAS操作来存储当前锁的状态,判断锁是否被别的线程持有
2.等待队列。所有没有请求到锁的线程都会进入等待队列,释放锁后系统从等待队列中唤醒一个线程继续工作。
3.阻塞原语park()和unpark()。挂起和恢复线程。没有得到锁的将会被挂起。

condition条件

1.await():使当前线程等待,同时释放当前锁,其他线程中使用singal()或者是singalAll()方法时,线程会重新获得锁并继续执行。线程被中断时跳出等待。类似于Object.wait();
2.awaitUninterruptibly():与await()类似,但不会在等待过程中响应中断
3.singal():唤醒一个等待中的线程,singalAll()唤醒所有,类似于Object.notify();

信号量Semaphore

信号量是对锁的扩展,无论是内部锁synchronized还是重入锁ReentrantLock,一次都只允许一个线程访问一个资源,而信号量却可以指定多个线程,同时访问某一个资源。

public Semaphore(int permits)
public Semaphore(int permits,boolean fair)//第二个参数指定是否公平

信号量的准入数,即同时能申请多少个许可。就是相当于同时有多少个线程可以访问某一个资源。
申请信号量使用acquire()操作,离开时务必使用release()释放信号量。

ReadWriteLock读写锁

读写分离锁可以有效地帮助减少锁竞争,提升系统性能。读写锁允许多个线程同时读,使线程们真正并行,但由于数据完整性,写写操作和读写操作之间依然需要互相等待和持有锁。
读读不互斥:读读之间不阻塞
读写互斥:读阻塞写,写阻塞读
写写互斥:写写阻塞

倒计时器 CountDownLatch

它可以让某一个线程等待直到倒计时结束再执行。

循环栅栏 CyclicBarrier

阻止线程继续执行,要求线程在栅栏处等待,

public CyclicBarrier(int parties,Runnable barrierAction)
parties表示计数总数,barrierAction表示计数器一次计数完成后,系统会执行的动作。

CyclicBarrier会抛出的两个异常:
InterruptedException:等待过程中线程被中断。
BrokenBarrierException:当前的CyclicBarrier已经破损,系统无法等待所有线程到齐。

线程阻塞工具类 LockSupport

它可以在线程内任意位置让线程阻塞。
park()方法可以阻塞当前线程。
LockSupport类使用类似信号量机制,为每个线程都准备了一个许可,如果许可可用,那么park()函数就会立刻返回并消费这个许可,如果许可不可用,就会阻塞。unpark()将许可变为可用。
unpark()操作在park()操作之前,它也可以使得下一次的park()操作立即返回。

线程复用:线程池

在使用线程池后,创建线程变成了从线程池中获得空闲线程,关闭线程变成了向线程池中归还线程。
1.固定大小的线程池

ExecutorService es=Executors.newFixedThreadPool(5);//创建一个内有5个线程的线程池

2.计划任务
根据时间对需要的线程进行调度
newScheduledThreadPool()
schedule():在给定时间对任务进行一次调度。
scheduleAtFixedRate():对任务进行周期性的调度,任务开始于给定的初始延时,后续任务按给定周期进行:后续第一个在initialDelay+period时进行,第二个在initialDelay+2*period时进行,以此类推。
scheduleWithFixedDelay():按给定时间差进行

核心线程池的内部实现

newFixedThreadPool()、new SingleThreadExecutor()、newCachedThreadPool()内部均使用了ThreadPoolExecutor实现,他们都是ThreadPoolExecutor类的封装。

public ThreadPoolExecutor(int corePoolSize,//线程池中线程数量
                          int maximumPoolSize,//最大线程数量
                          long keepAliveTime,//线程池数量超过corePoolSize时,多余空闲线程的寻获时间
                          TimeUnit unit,//keepAliveTime的单位
                          BlockingQueue<Runnable> workQueue,//任务队列,被提交但尚未被执行的任务  
                          ThreadFactory threadFactory,//线程工厂
                          RejectedExecutionHandler handler//拒绝策略,任务太多处理不过来的时候如何拒绝任务)

workQueue:指被提交但未被执行的任务队列,他是一个BlockingQueue接口的对象,用于存放Runnable对象。
分为以下几种:
1.直接提交的队列:由SynchronousQueue对象提供,没有容量,每一个插入操作都要等待一个删除操作,提交的任务不会真实的保存,而总是将新的任务交给线程执行,如果没有空闲进程则会创建新的进程,进程数量达到最大,那么会执行决绝策略。
2.有界的任务队列:使用ArrayBlockingQueue实现
3.无界的任务队列:通过LinkedBlockingQueue类实现
4.优先任务队列:通过PriorityBlockingQueue实现,可以控制任务的执行先后顺序

这里可以说明ThreadPoolExecutor任务调度逻辑
《Java高并发程序设计》读书笔记(第三章)_第1张图片

你可能感兴趣的:(《Java高并发程序设计》读书笔记(第三章))