JUC
Java.util.concurrent:Executor框架就是一个任务的执行和调度框架。
最顶层是Executor接口,它的定义很简单,一个用于执行任务的execute方法.interface继承关系:executor->executorservice->ScheduledExecutorService。对应抽象类:
优点:
任务的提交过程与执行过程解耦,用户只需定义好任务提交,具体如何执行,什么时候执行不需要关心;
典型步骤:
定义好任务(如Callable对象),把它提交给ExecutorService(如线程池)去执行,得到Future对象,然后调用Future的get方法等待执行结果即可。
任务:实现Callable接口或Runnable接口的类,其实例就可以成为一个任务提交给ExecutorService去执行;其中Callable任务可以返回执行结果,Runnable任务无返回结果。
什么是线程池
通过Executors工具类可以创建各种类型的线程池,直接通过类名创建。如下为常见的四种:
newCachedThreadPool :大小不受限,当线程释放时,可重用该线程;
newFixedThreadPool :大小固定,无可用线程时,任务需等待,直到有可用线程;
newSingleThreadExecutor :创建一个单线程,任务会按顺序依次执行;
newScheduledThreadPool:创建一个定长线程池,支持定时及周期性任务执行
流程举例:1
2
3
4ExecutorService executor = Executors.newCachedThreadPool();//创建线程池
Task task = new Task(); //创建Callable任务
Future<Integer> result = executor.submit(task);//提交任务给线程池执行
result.get();//等待执行结果; 可以传入等待时间参数,指定时间内没返回的话,直接结束
定时周期性执行任务1
2
3
4
5
6
7
8// Timeout each entry and remove them when needed
Runnable runnable = new Runnable() {
@Override
public void run() {
}
}
ScheduledExecutorService resservice = Executors.newSingleThreadScheduledExecutor();
resservice.scheduleAtFixedRate(runnable, 0, 10, TimeUnit.SECONDS);
scheduleAtFixedRate:peroid周期执行
schedukedWithFixedDelay:一个任务结束之后才周期性执行下一个任务;
AQS框架AbstractQueuedSynchronizer
AQS框架是J.U.C中实现锁及同步机制的基础,其底层是通过调用LockSupport .unpark()和 LockSupport .park()实现线程的阻塞和唤醒。
LockSupport:java.util.concurrent.locks.LockSupport:可用则立即返回;否则阻塞直到unpark.
A call to park will return immediately if the permit is available, consuming it in the process; otherwise it may block. A call to unpark makes the permit available, if it was not already available. (Unlike with Semaphores though, permits do not accumulate. There is at most one.)
static Methods:都是通过类名调用
LockSupport.park():Disables the current thread for thread scheduling purposes unless the permit is available.
LockSupport.unpark(Thread thread):Makes available the permit for the given thread, if it was not already available.
具体实现
AbstractQueuedSynchronizer是一个抽象类,主要是维护了一个int类型的state属性和一个非阻塞、先进先出的线程等待队列;其中state是用volatile修饰的,保证线程之间的可见性,队列的入队和出对操作都是无锁操作,基于自旋锁和CAS实现;另外AQS分为两种模式:独占模式和共享模式,像ReentrantLock是基于独占模式模式实现的,CountDownLatch、CyclicBarrier等是基于共享模式。
如果获取锁失败,则调用AQS的acquire方法,在该方法内部会调用tryAcquire方法再次尝试获取锁以及是否可重入判断,如果失败,则挂起当前线程并加入到等待队列;
Java锁的分类
1.AQS是独占锁(例如,ReentrantLock)和共享锁(例如,Semaphore)的公共父类。
(01) 独占锁 – 锁在一个时间点只能被一个线程锁占有。根据锁的获取机制,它又划分为“公平锁”和“非公平锁”。公平锁,是按照通过CLH等待线程按照先来先得的规则,公平的获取锁;而非公平锁,则当线程要获取锁时,它会无视CLH等待队列而直接获取锁。独占锁的典型实例子是ReentrantLock,此外,ReentrantReadWriteLock.WriteLock也是独占锁。
(02) 共享锁 – 能被多个线程同时拥有,能被共享的锁。JUC包中的ReentrantReadWriteLock.ReadLock,CyclicBarrier, CountDownLatch和Semaphore都是共享锁。
CLH队列 – Craig, Landin, and Hagersten lock queue
CLH队列是AQS中“等待锁”的线程队列。在多线程中,为了保护竞争资源不被多个线程同时操作而起来错误,我们常常需要通过锁来保护这些资源。在独占锁中,竞争资源在一个时间点只能被一个线程锁访问;而其它线程则需要等待。CLH就是管理这些“等待锁”的线程的队列。
CLH是一个非阻塞的 FIFO 队列。也就是说往里面插入或移除一个节点的时候,在并发条件下不会阻塞,而是通过自旋锁和 CAS 保证节点插入和移除的原子性。
Node是CLH队列的节点,代表“等待锁的线程队列”。
(01) 每个Node都会一个线程对应。
(02) 每个Node会通过prev和next分别指向上一个节点和下一个节点,这分别代表上一个等待线程和下一个等待线程。
(03) Node通过waitStatus保存线程的等待状态。
(04) Node通过nextWaiter来区分线程是“独占锁”线程还是“共享锁”线程。如果是“独占锁”线程,则nextWaiter的值为EXCLUSIVE;如果是“共享锁”线程,则nextWaiter的值是SHARED。
4.CAS函数 – Compare And Swap
CAS函数,是比较并交换函数,它是原子操作函数;即,通过CAS操作的数据都是以原子方式进行的。例如,compareAndSetHead(), compareAndSetTail(), compareAndSetNext()等函数。它们共同的特点是,这些函数所执行的动作是以原子的方式进行的。
1.同步锁:synchoronized: 即通过synchronized关键字来进行同步,实现对竞争资源的互斥访问的锁。Java 1.0版本中就已经支持同步锁了。
同步锁的原理是,对于每一个对象,有且仅有一个同步锁;不同的线程能共同访问该同步锁。但是,在同一个时间点,该同步锁能且只能被一个线程获取到。这样,获取到同步锁的线程就能进行CPU调度,从而在CPU上执行;而没有获取到同步锁的线程,必须进行等待,直到获取到同步锁之后才能继续运行。这就是,多线程通过同步锁进行同步的原理.
ReentraantLock是通过一个FIFO的等待队列来管理获取该锁所有线程的。在“公平锁”的机制下,线程依次排队获取锁;而“非公平锁”在锁是可获取状态时,不管自己是不是在队列的开头都会获取锁。
默认是非公平锁,使用set true设置公平锁可能极大地降低效率和吞吐量。
unlock()是解锁函数,它是通过AQS的release()函数来实现的。
在这里,“1”的含义和“获取锁的函数acquire(1)的含义”一样,它是设置“释放锁的状态”的参数。由于“公平锁”是可重入的,所以对于同一个线程,每释放锁一次,锁的状态-1。
公平锁和非公平锁的区别,是在获取锁的机制上的区别。表现在,在尝试获取锁时 —— 公平锁,只有在当前线程是CLH等待队列的表头时,才获取锁;而非公平锁,只要当前锁处于空闲状态,则直接获取锁,而不管CLH等待队列中的顺序。
只有当非公平锁尝试获取锁失败的时候,它才会像公平锁一样,进入CLH等待队列排序等待。
Programs using fair locks accessed by many threads may display lower overall throughput (i.e., are slower; often much slower) than those using the default setting, but have smaller variances in times to obtain locks and guarantee lack of starvation. Note however, that fairness of locks does not guarantee fairness of thread scheduling. Thus, one of many threads using a fair lock may obtain it multiple times in succession while other active threads are not progressing and not currently holding the lock. Also note that the untimed tryLock method does not honor the fairness setting. It will succeed if the lock is available even if other threads are waiting.