Thread和JUC整理

来源:互联网 发布:淘宝司法拍卖房产过户 编辑:程序博客网 时间:2024/06/08 02:39

代码简单实现:https://github.com/chunericli/common-wise-utils

实现原理解析:http://blog.csdn.net/lichunericli

链接参考:http://tutorials.jenkov.com/java-util-concurrent/index.html

#Thread

通过使用Thread类可以启动,停止,中断一个线程。在同一个时间片里,可能会有多个线程在执行,每个线程都拥有其自己的方法调用堆栈,参数和变量。每个应用至少会有一个线程:主线程(main thread)。

#线程的创建

1.继承Thread类,实现run方法

2.实现Runnable接口,实现run方法,Thread是Runnable的子类

3.实现Callable接口,实现call方法

4.使用ExecutorService和Executors来创建线程

5.使用线程工厂ThreadFactory的newThread方法创建线程

Note:方法4和5持保留意见,因为其底层实现是使用方法1,2,3。使用方法1创建线程,可以在run方法中使用this直接调用线程的方法,比如this.getId()来获取线程的id。使用方法2创建线程,在run方法中只能用Thread.cuurentThread()静态方法获取该线程,继而获得相关信息。通常采用实现Runnable接口来创建线程以符合面向对象的思想,Thread负责线程的启动,停止,中断等操作,Runnable负责线程具体的任务执行。

#线程的休眠

调用线程的Thread.sleep()方法来让线程进行休眠。休眠的线程会被挂起,然后将CPU的时间片转移给其它线程,让其它线程获得执行的机会,此处类似Thread.yield()的功能实现。Thread.sleep()可接收毫秒值做参数,并抛出InterruptedException异常。

Note:Thread.yield()方法会暗示当前线程调度者,当前线程愿意让出CPU时间片,让其它线程先执行,但CPU是否让其它线程优先执行,视情况而定。

#线程的终止

大多数情况下,当run方法中的任务在执行完毕后线程会自动停止,除非是被设置成为守护线程以及使用线程池创建线程。Thread类中提供了stop,destroy等废弃方法来终止线程的执行,同时在线程池中也提供了shutdown和shutdownNow来终止线程。在实际应用中可以配合线程的中断来终止线程。

调用线程的interrupt()方法,标识中断状态为true,在抛出InterruptedException异常后会清空中断位。同时可以使用Thread.currentThread().isInterrupted()来判断是否中断,如果中断则终止线程的执行。

Note:守护线程不见得会及时关闭,线程池需要手动来进行关闭。interrupt()方法在Object.wait()和Thread.join()等过程中受阻会清空中断状态,在I/O操作或者Selector操作中受阻会重置中断状态为其它。Thread类的interrupted()【会清除中断状态】和isInterrupted()【中断状态不受该方法的影响】方法都是用来测试当前线程是否已经中断。

Note:Thread.join()需要等到某线程执行完毕后才开始执行,这种等待可以设置指定的超时时间。

#线程的属性

id:通过Thread.getId()可以获取线程的id,线程的id是自增长的long且不能修改

name:通过Thread.getName()可以获取线程的名称,线程的名称是用字符串来标识的,可通过构造函数或者使用Thread.setName()来重置线程的名称

priority:默认的优先级为5,最小优先级为1,最大优先级为10。优先级大的线程更有机会获得CPU的时间片来进行任务的执行,但具体的选择无法预测

state:线程的状态

  Thread.State.NEW:当前线程已经被创建但还没有调用start()方法执行,可能已调用start()方法但是没有获得CPU的时间片执行

   Thread.State.RUNNABLE:当前线程已经在JVM中执行

  Thread.State.BLOCKED:当前线程在等待进入同步块或同步方法,例如I/O阻塞等

  Thread.State.WAITING:当前线程在调用Object.wait(),Thread.join(),LockSupport.park()等方法后的状态

  Thread.State.TIMED_WAITING:类似存在时限的Thread.State.WAITING,例如Object.wait(long)等

  Thread.State.TERMINATED:线程执行完毕后的状态

可以通过getState()方法返回线程的状态,该方法用于监视系统状态,不用于同步控制

Daemon:用来标识线程为守护线程或非守护线程的,默认创建的线程都是非守护线程

#线程的异常

JVM会为当前执行的线程打印出未被捕获的错误异常堆栈信息,可以通过try{...}catch(Exception e){...}进行定制化的处理,也可以通过Thread.setUncaughtExceptionHandler进行统一的处理。

thread.setUncaughtExceptionHandler(newThread.UncaughtExceptionHandler() {

  @Override

  publicvoid uncaughtException(Thread t, Throwable e) {

     System.out.println(String.format("%s发生异常%s", t.getName(), e.getMessage()));

  }

});

#线程的同步

同步关键字synchronized可以用来保证数据的同步,此处的保证是指当前服务器,并非是跨服务器的分布式锁的保证。同步关键字synchronized可以在方法体内或者方法上使用,例如synchronized(this){方法体}或者synchronized(Object){方法体}。

在类的静态方法中加synchronized等效于synchronized(类.class){方法体}。

Note:在实际运用中通常使用JUC中提供的方法来实现线程的同步。

#Object的wait…notify

Object.wait()与Object.wait(0)的行为等同,它们都会导致当前线程一直等待,直到其它线程调动相同对象锁的Object.notify()或Object.notifyAll()方法来进行唤醒。Wait在等待过程中会释放资源和当前对象锁,Thread.sleep()在等待过程中会释放资源但是不会释放锁。Notify会随机选择一个线程进行唤醒,但是notifyAll会唤醒所有等待的线程。无论是使用notify或是notifyAll来唤醒等待的线程,被唤醒的线程都会使用竞争的方式去获取对象的锁,同一个时间点只能有一个线程拥有对象的锁。

为防止中断或虚假唤醒线程,所以通常在调用wait时会使用循环:

synchronized(obj){

  while(当条件不满足时)

obj.wait();

}

虚假唤醒:多线程环境下结合Condition来操作队列时,发生在获得队列锁但是数据已经被消费掉的情形。

#线程的副本:ThreadLocal

ThreadLocal应用于多线程间共享变量但是互相不影响的情形,例如session的保存以及数据库连接的存放。ThreadLocal对数据的设置与获取是由内部类ThreadLocalMap实现的,ThreadLocalMap以ThreadLocal为键,value为值进行存储的。在调用ThreadLocal.get()和ThreadLocal.set()的时候,需要获取当前线程来执行相应的操作。

Note:ThreadLocalMap的功能实现是使用内部类Entry来完成的,Entry是弱引用。由弱引用的特性可知,弱引用中虽然对内存的回收很及时,但是也有可能会存在内存泄漏,因为键的回收不会导致值的回收。

阻塞队列BlockingQueue

BlockingQueue通常用于生产者和消费者模式的场景。线程在达到临界点的队列中插入数据可能会导致阻塞,从空队列中获取数据同样也可能会导致阻塞。往队列中插入null元素会抛出NullPointerException,通过remove(Object o)可访问队列中的任何元素。BlockingQueue针对插入,移除和对队列中的元素的获取,提供了不同的实现:

 

抛异常

特定值

阻塞

超时

插入

add(o)

offer(o)

put(o)

offer(o, timeout, timeunit)

移除

remove(o)

poll(o)

take(o)

poll(timeout, timeunit)

检查

element(o)

peek(o)

 

 

1.  抛异常:如果试图的操作无法立即执行,抛一个异常。

2.  特定值:如果试图的操作无法立即执行,返回一个特定的值(true/false)。

3.  阻塞:如果试图的操作无法立即执行,该方法调用将会发生阻塞,直到能够执行。

4.  超时:如果试图的操作无法立即执行,该方法调用将会发生阻塞,直到能够执行,但等待时间不会超过给定值。返回一个特定值以告知该操作是否成功(true/false)。BlockingQueue的实现

·        ArrayBlockingQueue

·        DelayQueue

·        LinkedBlockingQueue

·        PriorityBlockingQueue

·        SynchronousQueue

数组阻塞队列ArrayBlockingQueue

ArrayBlockingQueue是有界的阻塞队列,其内部实现是将对象放到一个数组里。有界指明存储的元素数量存在上限,可以在初始化的时候设定上限,但之后就无法对上限进行修改(数组特性:初始化后大小无法修改)。ArrayBlockingQueue内部以FIFO(先进先出)的顺序对元素进行存储。队列中的头元素是所有元素之中保存时间最久的,而尾元素则是最短的。

实现原理:ArrayBlockingQueue底层由数组Object[],ReentrantLock,Condition【empty和full】以及相关索引项putIndex和takeIndex共同实现的。

延迟队列DelayQueue

DelayQueue对元素进行持有直到特定的延迟到期,其元素必须实现java.util.concurrent.Delayed接口: public interface Delayed extends Comparable<Delayed>{

public long getDelay(TimeUnit timeUnit);

}

Delayed接口继承了java.lang.Comparable接口,这说明Delayed对象之间可以进行对比。这个可能在对DelayQueue队列中的元素进行排序时有用,因此它们可以根据过期时间进行有序释放。DelayQueue会在每个元素的getDelay()方法返回的值的时间间隔之后才释放掉该元素。如果返回的是0或者负值,延迟将被认为过期,该元素将会在DelayQueue的下一次take被调用的时候被释放掉。

传递给getDelay方法的getDelay实例是一个枚举类型,它表明了将要延迟的时间段。TimeUnit枚举值:DAYS,HOURS,MINUTES,SECONDS,MILLISECONDS,MICROSECONDS,NANOSECONDS。

链阻塞队列LinkedBlockingQueue

LinkedBlockingQueue内部以链式结构(链接节点)对其元素进行存储。这链式结构可以定义上限,如果没有定义上限,将使用Integer.MAX_VALUE作为上限。LinkedBlockingQueue内部以FIFO(先进先出)的顺序对元素进行存储。队列中的头元素在所有元素之中是放入时间最久的那个,而尾元素则是最短的那个。

优先级阻塞队列PriorityBlockingQueue
PriorityBlockingQueue是无界的并发队列。它使用了和类java.util.PriorityQueue同样的排序规则。无法向这个队列中插入null值,所有插入到PriorityBlockingQueue的元素必须实现Comparable接口,因此该队列中元素的排序取决于Comparable实现。
注意:PriorityBlockingQueue对于具有相等优先级(compare()==0)的元素并不强制任何特定行为;如果从PriorityBlockingQueue中获得Iterator,该Iterator并不能保证它对元素的遍历是以优先级为序的。

同步队列SynchronousQueue

SynchronousQueue是特殊的队列,它的内部同时只能够容纳单个元素。如果该队列已有元素的话,试图向队列中插入新元素的线程将会阻塞,直到其它线程将该元素从队列中移除。同样如果该队列为空,试图向队列中获取元素的线程将会阻塞,直到其它线程向队列中插入了新的元素。

阻塞双端队列BlockingDeque

Deque(双端队列)是"Double Ended Queue"的缩写,线程在双端队列的两端都可以插入和提取元素。在线程既是队列的生产者又是这个队列的消费者时;生产者线程需要在队列的两端都可以插入数据,消费者线程需要在队列的两端都可以移除数据时使用BlockingDeque,阻塞的情形类似BlockingQueue。

BlockingDeque继承自BlockingQueue接口,故可以像使用BlockingQueue那样使用BlockingDeque。
BlockingDeque针对插入,移除和对队列中的元素的获取,提供了不同的实现:

 

抛异常

特定值

阻塞

超时

插入

addFirst(o)

offerFirst(o)

putFirst(o)

offerFirst(o, timeout, timeunit)

移除

removeFirst(o)

pollFirst(o)

takeFirst(o)

pollFirst(timeout, timeunit)

检查

getFirst(o)

peekFirst(o)

 

 

 

 

抛异常

特定值

阻塞

超时

插入

addLast(o)

offerLast(o)

putLast(o)

offerLast(o, timeout, timeunit)

移除

removeLast(o)

pollLast(o)

takeLast(o)

pollLast(timeout, timeunit)

检查

getLast(o)

peekLast(o)

 

 

1.  抛异常:如果试图的操作无法立即执行,抛一个异常。

2.  特定值:如果试图的操作无法立即执行,返回一个特定的值(true/false)。

3.  阻塞:如果试图的操作无法立即执行,该方法调用将会发生阻塞,直到能够执行。

4.  超时:如果试图的操作无法立即执行,该方法调用将会发生阻塞,直到能够执行,但等待时间不会超过给定值。返回一个特定值以告知该操作是否成功(true/false)。

BlockingDeque对BlockingQueue接口的方法的具体内部实现:

BlockingQueue

BlockingDeque

add()

addLast()

offer() x 2

offerLast() x 2

put()

putLast()

remove()

removeFirst()

poll() x 2

pollFirst()

take()

takeFirst()

element()

getFirst()

peek()

peekFirst()

BlockingDeque的实现

·        LinkedBlockingDeque

链阻塞双端队列LinkedBlockingDeque

试图从空队列中获取数据的线程将会阻塞,无论该线程是试图从哪一端获取数据。

ConcurrentHashMap

java.util.concurrent.ConcurrentMap是对插入和获取元素进行并发处理的Map。ConcurrentHashMap是ConcurrentMap的实现。ConcurrentHashMap和java.util.HashTable类很相似,但ConcurrentHashMap能够提供比HashTable更好的并发性能。当获取对象的时候ConcurrentHashMap并不会把整个Map锁住,当向其中写入对象的时候ConcurrentHashMap也不会锁住整个Map。它的内部只是把Map中正在被写入的部分进行锁定。尽管Iterator的设计不是为多个线程的同时使用,但是在遍历时,即使ConcurrentHashMap被改动,它也不会抛出ConcurrentModificationException。

并发导航ConcurrentNavigableMap

java.util.concurrent.ConcurrentNavigableMap是一个支持并发访问的java.util.NavigableMap,它能让它的子map,如headMap(),subMap(),tailMap()之类的方法返回的map具备并发访问的能力。对原始map里的元素做了改动,这些改动将影响到子map中的元素(map集合持有的其实只是对象的引用)。

headMap(T toKey)方法返回一个包含了小于给定toKey的key的子map。

tailMap(TfromKey)方法返回一个包含了不小于给定fromKey的key的子map。
subMap()方法返回原始map中,键介于from(包含)和to(不包含)之间的子map。

ConcurrentNavigableMap 接口还有其它方法可供使用,比如:

·        descendingKeySet()

·        descendingMap()

·        navigableKeySet()

闭锁CountDownLatch

java.util.concurrent.CountDownLatch是并发构造的同步辅助类,通过它可以完成类似于阻塞当前线程的功能,即它允许一个或多个线程等待一系列指定操作的完成。CountDownLatch在初始化的时候需要设置给定的计数器count参数,该计数器的操作是原子操作,即同时只能有一个线程去操作该计数器。调用该类await方法的线程会永久处于阻塞状态,直到以下情况发生:

+ 其它线程调用countDown,如果当前的count大于0,则count减1直到count清空为0

+ 当前线程被其它线程中断

+ 指定的时间超时

当计数器值减至零时,表示所有等待的线程重新获得执行机会,通常所有等待中的线程会被释放并且后面继续调用await的线程会立即返回。因为计数器不能被重置,所以这是一次性操作,如果业务上需要可以重置计数次数的版本,可以考虑使用CycliBarrier。

实现原理:CountDownLatch的功能实现是由内部类Sync类完成,Sync继承自AQS,计数器值初始化为AQS的state值,AQS内部存在CLH队列,CLH是通过节点状态waitStatus和CAS交换来实现相关功能的。

栅栏CyclicBarrier

java.util.concurrent.CyclicBarrier是并发构造的同步辅助类,它允许一组线程必须不时地互相等待,直到到达某个公共屏障点 (common barrier point)。

CyclicBarrier也同样维护着count计数器参数,当某个线程调用await方法时,该线程进入等待状态且计数器加1,当计数器的值达到设置的初始值时,所有因调用await进入等待状态的线程被唤醒,继续执行后续操作。因为CycliBarrier在释放等待线程后可以重用,所以称为循环barrier。CycliBarrier支持可选的Runnable命令,在计数器的值到达设定值后(但在释放所有线程之前),该Runnable在屏障点只运行一次。

对于失败的同步尝试,CyclicBarrier使用的是要么全部要么全不(all-or-none)的破坏模式:如果因为中断、失败或者超时等原因,导致线程过早地离开了屏障点,那么在该屏障点等待的其它所有线程也将通过BrokenBarrierException(如果它们几乎同时被中断,则用InterruptedException)以反常的方式离开。

满足以下任何条件都可以让等待CyclicBarrier的线程释放:

·        最后一个线程也到达CyclicBarrier(调用 await())

·        当前线程被其它线程打断(其它线程调用了当前线程的interrupt()方法)

·        其它等待栅栏的线程被打断

·        其它等待栅栏的线程因超时而被释放

·        外部线程调用了栅栏的CyclicBarrier.reset()方法

实现原理:CyclicBarrier由ReentrantLock,Condition【阻塞直到tripped】,Runnable【tripped时执行】,Generation,parties/count【线程数】实现;Generation是内部类,其维护broken来实现重置功能;每个CyclicBarrier实例中也有一个Generation域,表示当前的屏障是否被打破。

CyclicBarrier与CountDownLatch的区别:

CountDownLatch主要是实现了1个或N个线程需要等待其它线程完成某项操作之后才能继续往下执行操作,描述的是1个线程或N个线程等待其它线程的关系。CyclicBarrier主要是实现了多个线程之间相互等待,直到所有的线程都满足了条件之后各自才能继续执行后续的操作,描述的多个线程内部相互等待的关系。

CountDownLatch是一次性的,而CyclicBarrier则可以被重置而重复使用。

交换机Exchanger

java.util.concurrent.Exchanger是同步工具类,通常用来处理两个线程的数据交换。使用exchange(V x)方法同其它线程交换数据x,如果其它线程的数据准备好,那么当前线程会立刻返回,并获得另外一个线程的数据,否则当前线程会进入等待状态,可以为这种等待设置超时时间。

信号量Semaphore

java.util.concurrent.Semaphore类是计数信号量,计数信号量由指定数量的"许可"初始化。每调用一次acquire(),一个许可会被调用线程取走,每调用一次release(),一个许可会被返还给信号量。因此在没有任何release()调用时,最多有N个线程能够通过acquire()方法,N是该信号量初始化时的许可的指定数量,这些许可只是一个简单的计数器。Semaphore可设置是否需要公平,强制公平会影响到并发性能,所以除非需要否则不要启用。信号量主要有两种用途:

1.  保护重要代码部分,防止一次超过N个线程进入,执行前首先得尝试获得一个许可,执行完之后,再把许可释放掉。

2.  在两个线程之间发送信号,通常应该用一个线程调用acquire()方法,而另一个线程调用release()方法。

如果没有可用的许可,acquire()调用将会阻塞,直到一个许可被另一个线程释放出来;如果无法往信号量释放更多许可时,一个release()调用也会阻塞。通过这个可以对多个线程进行协调。例如线程1将一个对象插入到了一个共享列表(list)之后之后调用了acquire(),而线程2则在从该列表中获取一个对象之前调用了release(),这时其实已经创建了一个阻塞队列。信号量中可用的许可的数量也就等同于该阻塞队列能够持有的元素个数。

执行器服务ExecutorService

java.util.concurrent.ExecutorService接口表示的是在后台执行任务的异步机制,实际上这是一种任务委托方式,也是线程池的实现方式。ExecutorService的创建依赖于使用的具体实现,可以使用Executors工厂类来创建 ExecutorService实例,例如ExecutorServiceservices=Executors.xxx()。

Executors是无法实例化的线程池工具类,它包含的都是静态方法或静态类:

newFixedThreadPool(int nThreads):创建指定线程数量的线程池

newSingleThreadExecutor():创建只有一个线程的线程池

newCachedThreadPool():创建可以缓存的线程池

newSingleThreadScheduledExecutor():创建只有一个线程的任务调度线程池

newScheduledThreadPool(int corePoolSize):创建指定线程数量的任务调度线程池

Executors还提供了让Runnable转化为Callable的适配器。

ExecutorService实现:

·        ThreadPoolExecutor

·        ScheduledThreadPoolExecutor

有几种不同的方式来将任务委托给ExecutorService去执行:

·        execute(Runnable)

·        submit(Runnable)

·        submit(Callable)

·        invokeAny(...)

·        invokeAll(...)

execute(Runnable)方法要求一个java.lang.Runnable对象,然后对它进行异步执行,没有办法得知被执行的Runnable的执行结果,如果有需要的话得使用Callable。

submit(Runnable)方法也要求一个Runnable实现类,但它返回一个Future对象,这个Future对象可以用来检查Runnable是否已经执行完毕。

submit(Callable)方法类似于submit(Runnable)方法,除了它所要求的参数类型之外,Callable实例除了它的call()方法能够返回一个结果之外和一个Runnable很相像,Runnable.run()不能够返回一个结果。
Callable的结果可以通过submit(Callable)方法返回的Future对象进行获取。

invokeAny()方法要求一系列的Callable或者其子接口的实例对象,调用这个方法并不会返回一个Future,但它返回其中一个Callable对象的结果,无法保证返回的是哪个Callable的结果-只能表明其中一个已执行结束,如果其中一个任务执行结束(或者抛了一个异常),其它Callable将被取消。
invokeAll()方法将调用你在集合中传给ExecutorService的所有Callable对象,invokeAll()返回一系列的Future对象,通过它们可以获取每个Callable的执行结果,一个任务可能会由于一个异常而结束,因此它可能没有"成功",无法通过一个Future对象来告知是两种结束中的哪种。

使用完ExecutorService之后应该将其关闭,以使其中的线程不再运行。要终止ExecutorService里的线程需要调用ExecutorService的shutdown()方法,ExecutorService并不会立即关闭,但它将不再接受新的任务,而且一旦所有线程都完成了当前任务的时候,ExecutorServic将会关闭,在shutdown()被调用之前所有提交给 ExecutorService的任务都被执行。

如果要立即关闭ExecutorService,可以调用shutdownNow()方法,这样会立即尝试停止所有执行中的任务,并忽略掉那些已提交但尚未开始处理的任务,无法担保执行任务的正确执行,可能它们被停止了,也可能已经执行结束。

线程池执行者ThreadPoolExecutor

ThreadPoolExecutor使用其内部池中的线程执行给定任务(Callable或者Runnable),ThreadPoolExecutor包含的线程池能够包含不同数量的线程,池中线程的数量由以下变量决定:

·        corePoolSize

·        maximumPoolSize

当一个任务委托给线程池时,如果池中线程数量低于corePoolSize,新的线程将被创建,即使池中可能尚有空闲线程。如果内部任务队列已满,而且有至少corePoolSize正在运行,但是运行线程的数量低于 maximumPoolSize,新的线程将被创建去执行该任务。
ThreadPoolExecutor构造器:

ThreadPoolExecutor(intcorePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit, BlockingQueue<Runnable>workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler):

*corePoolSize:线程的核心线程数,即使这些线程是空闲,除非设置了allowCoreThreadTimeOut

* maximumPoolSize:线程池最大允许的线程数

* keepAliveTime:当前的线程数大于核心线程数,多余的空闲的线程在被终止之前能等待新任务的时间

* unit:keepAliveTime时间的单位

* workQueue:用来保留将要执行的工作队列

* threadFactory:用于创建新线程的工厂

* handler:如果工作队列(workQueue)满了,那么这个handler将会被执行

DefaultThreadFactory:

创建出的工厂对象主要是在创建新的线程的时候修改了线程名为:pool-全局线程池递增数编号-thread-当前线程池线程递增编号,让线程改为非守护线程,并设置线程的优先级为NORM_PRIORITY。

handler默认值:

* java.util.concurrent.ThreadPoolExecutor.AbortPolicy是默认使用的拒绝策略,如果有要执行的任务队列已满且还有任务提交,则直接抛出异常信息

* java.util.concurrent.ThreadPoolExecutor.DiscardPolicy是忽略策略,如果有要执行的任务队列已满且还有任务提交,则直接忽略掉这个任务,即不抛出异常也不做任何处理

* java.util.concurrent.ThreadPoolExecutor.DiscardOldestPolicy忽略最早提交的任务,如果有要执行的任务队列已满,此时若还有任务提交且线程池还没有停止,则把队列中最早提交的任务抛弃掉,然后把当前任务加入队列中

* java.util.concurrent.ThreadPoolExecutor.CallerRunsPolicy这个是来者不拒策略,如果有要执行的任务队列已满,此时若还有任务提交且线程池还没有停止,则直接运行任务的run方法

* 使用自定义的拒绝策略

RejectedExecutionHandlerhandler = new RejectedExecutionHandler() {

  @Override

  public void rejectedExecution(Runnable r,ThreadPoolExecutor executor) {

     System.out.println(“使用自定义的拒绝策略”);

  }

};

Callable和Future概述

Callable同Runnable的区别在于Callable既能抛出错误也有返回类型

Future是执行异步任务后的返回值

FutureTask是Runnable和Future的子类,获取异步任务的返回值的Runnable版本

FutureTasktask = new FutureTask(Runnable),如果存在异常抛出,抛出的异常是在调用task.get()时获取的。

定时执行者服务ScheduledExecutorService

java.util.concurrent.ScheduledExecutorService是一个ExecutorService,它能够将任务延后执行或者间隔固定时间多次执行。任务由工作者线程异步执行,而不是由提交任务给ScheduledExecutorService的那个线程执行。ScheduledExecutorService 实现:

·        ScheduledThreadPoolExecutor

如何创建ScheduledExecutorService取决于采用的它的实现类,但是也可以使用 Executors工厂类来创建ScheduledExecutorService实例,创建ScheduledExecutorService实例后可以调用它的以下方法:

·        schedule(Callable task, long delay, TimeUnit timeunit)

·        schedule(Runnable task, long delay, TimeUnit timeunit)

·        scheduleAtFixedRate(Runnable, long initialDelay, long period, TimeUnit timeunit)

·        scheduleWithFixedDelay(Runnable, long initialDelay, long period, TimeUnit timeunit)

schedule(Callabletask, long delay, TimeUnit timeunit)

这个方法计划指定的Callable在给定的延迟之后执行,这个方法返回一个ScheduledFuture,通过它可以在它被执行之前对它进行取消,或者在它执行之后获取结果。

schedule(Runnabletask, long delay, TimeUnit timeunit)

除了Runnable无法返回一个结果之外,这一方法工作起来就像以一个Callable作为一个参数的那个版本的方法一样,因此ScheduledFuture.get()在任务执行结束之后返回null。

scheduleAtFixedRate(Runnable,long initialDelay, long period, TimeUnit timeunit)

这方法规划一个任务将被定期执行,该任务将会在首个initialDelay之后得到执行,然后每个period时间之后重复执行,如果给定任务的执行抛出了异常,该任务将不再执行;如果没有任何异常的话,这个任务将会持续循环执行到ScheduledExecutorService被关闭。如果一个任务占用了比计划的时间间隔更长的时候,下一次执行将在当前执行结束执行才开始,计划任务在同一时间不会有多个线程同时执行。

scheduleWithFixedDelay(Runnable,long initialDelay, long period, TimeUnit timeunit)

除了period有不同的解释之外这个方法和scheduleAtFixedRate()非常类似,scheduleAtFixedRate()方法中,period被解释为前一个执行的开始和下一个执行的开始之间的间隔时间。而在本方法中,period则被解释为前一个执行的结束和下一个执行的结束之间的间隔。因此这个延迟是执行结束之间的间隔,而不是执行开始之间的间隔。

在使用结束之后需要把ScheduledExecutorService关闭掉,否则将导致JVM继续运行,即使所有其它线程已经全被关闭。可以使用从ExecutorService接口继承来的shutdown()或shutdownNow()方法将ScheduledExecutorService关闭。

java提供的线程池还有任务调度线程池ScheduledThreadPoolExecutor,它是ThreadPoolExecutor的子类。

ScheduledThreadPoolExecutor的构造器都是调用父类的构造器,只是它使用的工作队列是延时工作队列,是java.util.concurrent.ScheduledThreadPoolExecutor.DelayedWorkQueue。

因为ScheduledThreadPoolExecutor的最大线程是Integer.MAX_VALUE,execute和submit都是调用schedule这个方法,而且延时时间都是指定为0,所以调用execute和submit的任务都直接被执行。

ForkJoinPool进行分叉和合并

ForkJoinPool可以把任务分裂成更小的任务,这些分裂出来的任务也将提交给ForkJoinPool,任务可以继续分割成更小的子任务如果有必要。通过分割成多个子任务,每个子任务可以由不同的CPU并行执行,或者被同一个CPU上的不同线程并发的执行。当任务将分割成若干子任务之后,该任务将进入等待所有子任务的结束之中,一旦子任务执行结束,该任务可以把所有结果合并到同一个结果。但是并非所有类型的任务都会返回一个结果。如果这个任务并不返回一个结果,它只需等待所有子任务执行完毕即可。

java.util.concurrent.ForkJoinPool是一个特殊的线程池,它的设计是为了更好的配合分叉-和-合并任务分割的工作。可以通过其构造创建一个ForkJoinPool,作为传递给ForkJoinPool构造的一个参数,可以定义期望的并行级别,并行级别表示想要传递给ForkJoinPool的任务所需的线程或CPU数量。

可以提交两种类型的任务:没有任何返回值的("RecursiveAction行动")和有返回值的("RecursiveTask任务")。RecursiveAction是没有任何返回值的任务,它只是做一些工作,比如写数据到磁盘,然后就退出。RecursiveAction 可以把自己的工作分割成更小的几块,这样它们可以由独立的线程或者CPU执行。可以通过继承来实现一个RecursiveAction。RecursiveTask是种会返回结果的任务。它可以将自己的工作分割为若干更小任务,并将这些子任务的执行结果合并到一个集体结果,可以有几个水平的分割和合并。

除了有结果返回之外,RecursiveTask同RecursiveAction很像,RecursiveTask也会将工作分割为子任务,并通过fork()方法对这些子任务计划执行。RecursiveTask还通过调用每个子任务的join()方法收集它们返回的结果。子任务的结果随后被合并到一个更大的结果,并最终将其返回。对于不同级别的递归,这种子任务的结果合并可能会发生递归。

通过ForkJoinPool.invoke()方法的调用来获取最终执行结果的。

Fork/Join框架主要由以下两个类组成:

ForkJoinPool这个类实现了ExecutorService接口和工作窃取算法(Work-StealingAlgorithm)。它管理工作者线程,并提供任务的状态信息,以及任务的执行信息。ForkJoinTask这个类是一个将在ForkJoinPool执行的任务的基类。Fork/Join框架提供了在一个任务里执行fork()和join()操作的机制和控制任务状态的方法。通常为了实现Fork/Join任务,需要实现一个以下两个类之一的子类:

*RecursiveAction用于任务没有返回值的场景

*RecursiveTask用于任务有返回值的场景.

锁Lock

java.util.concurrent.locks.Lock是类似于synchronized块的线程同步机制。但是Lock比synchronized块更加灵活、精细。Lock可以配合Condition类提供多样的组合。
java.util.concurrent.locks包提供了以下对Lock接口的实现类:

·        ReentrantLock

Lock对象和synchronized代码块之间的主要不同点:

·        synchronized代码块不能够保证进入访问等待的线程的先后顺序

·        不能够传递任何参数给synchronized代码块的入口,因此对于synchronized代码块的访问等待设置超时时间是不可能的事情

·        synchronized块必须被完整地包含在单个方法里,而Lock对象可以把它的lock()和unlock()方法的调用放在不同的方法里。

Lock 接口具有以下主要方法:

·        lock()

·        lockInterruptibly()

·        tryLock()

·        tryLock(longtimeout, TimeUnit timeUnit)

·        unlock()

lock()将Lock实例锁定,如果该Lock实例已被锁定,调用lock()方法的线程将会阻塞,直到Lock实例解锁。
lockInterruptibly()方法将会被调用线程锁定,除非该线程被中断。此外如果线程在通过这个方法来锁定Lock对象时进入阻塞等待,而它被中断了的话,该线程将会退出这个方法调用。
tryLock()方法试图立即锁定Lock实例,如果锁定成功则返回true,如果Lock实例已被锁定该方法返回false,这方法永不阻塞。
tryLock(long timeout, TimeUnit timeUnit)的工作类似于tryLock()方法,除了它在放弃锁定Lock之前等待一个给定的超时时间之外。

unlock()方法对Lock实例解锁。Lock实现将只允许锁定了该对象的线程来调用此方法。其它(没有锁定该Lock对象的线程)线程对unlock()方法的调用将会抛一个未检查异常(RuntimeException)。

读写锁ReadWriteLock

java.util.concurrent.locks.ReadWriteLock读写锁是一种先进的线程锁机制。它能够允许多个线程在同一时间对某特定资源进行读取,但同一时间内只能有一个线程对其进行写入。读写锁的理念在于多个线程能够对一个共享资源进行读取,而不会导致并发问题。并发问题的发生场景在于对一个共享资源的读和写操作的同时进行,或者多个写操作并发进行。

ReadWriteLock锁规则

一个线程在对受保护资源在读或者写之前对ReadWriteLock锁定的规则如下:

·        读锁:如果没有任何写操作线程锁定ReadWriteLock,并且没有任何写操作线程要求一个写锁(但还没有获得该锁)。因此可以有多个读操作线程对该锁进行锁定。

·        写锁:如果没有任何读操作或者写操作。因此在写操作的时候,只能有一个线程对该锁进行锁定。

ReadWriteLock实现

·        ReentrantReadWriteLock

ReentrantLock在同一个时间点,只能用一个线程能够访问共享资源。读写锁ReadWriteLock,读写锁提供了在同一个时间点,可以有多个线程共享去读取共享资源,当只有有一个线程去写共享资源。ReadWriteLock主要是Lock锁使用的,提供一个读和一个写的操作,readLock可以在有多个线程同时操作直到没有写的操作,writeLock却在同一个时间点只能有一个线程在操作。

所有ReadWriteLock的实现类必须在writeLock写操作时保证内存同步,同时还必须和readLock相关联,比如当一个线程获取到读锁时,这必须保证当前线程能在写锁释放后看到所有的更新操作。读写操作是互不干扰的,每次写操作释放完锁,读操作都可以把之前写的数据全部读取出来。

Condition概述

Lock.newCondition()会返回Condition,让多线程在不同状态切换其它线程时执行,类似Object.wait和Object.notify。

原子性布尔AtomicBoolean

java.util.concurrent.atomic.AtomicBoolean类提供了一个可以用原子方式进行读和写的布尔值,它还拥有一些先进的原子性操作,比如compareAndSet()。

创建AtomicBoolean:AtomicBoolean atomicBoolean=new AtomicBoolean(true);  

获取 AtomicBoolean的值:boolean value=atomicBoolean.get();  

设置 AtomicBoolean 的值:atomicBoolean.set(false);  

交换 AtomicBoolean 的值

getAndSet()方法将返回 AtomicBoolean当前的值,并将为AtomicBoolean设置一个新值。

比较并设置AtomicBoolean的值

compareAndSet()方法对AtomicBoolean的当前值与一个期望值进行比较,如果当前值等于期望值的话,将会对AtomicBoolean设定一个新值。compareAndSet()方法是原子性的,因此在同一时间之内有单个线程执行它。因此compareAndSet()方法可被用于一些类似于锁的同步的简单实现。

原子性整型AtomicInteger

java.util.concurrent.atomic.AtomicInteger类提供了一个可以进行原子性读和写操作的int变量,它还包含一系列先进的原子性操作,比如compareAndSet()。

创建AtomicInteger:AtomicInteger atomicInteger=new AtomicInteger(168);  

获取AtomicInteger的值:int theValue=atomicInteger.get();  

设置AtomicInteger的值:atomicInteger.set(888);  

比较并设置 AtomicInteger 的值

AtomicInteger类也通过了一个原子性的compareAndSet()方法。AtomicInteger实例的当前值与期望值进行比较,如果相等,为AtomicInteger实例设置一个新值。

增加 AtomicInteger 值

AtomicInteger类包含有一些方法,通过它们可以增加AtomicInteger的值,并获取其值。

·        addAndGet()

·        getAndAdd()

·        getAndIncrement()

·        incrementAndGet()

addAndGet()方法给AtomicInteger增加了一个值,然后返回增加后的值。

getAndAdd()方法为AtomicInteger增加了一个值,但返回的是增加以前的值。

减小 AtomicInteger 的值

AtomicInteger类还提供了一些减小 AtomicInteger 的值的原子性方法。

·        decrementAndGet()

·        getAndDecrement()

decrementAndGet()将AtomicInteger的值减一,并返回减一后的值。

getAndDecrement()将AtomicInteger的值减一,但它返回的是减一之前的值。

原子性长整型AtomicLong

java.util.concurrent.atomic.AtomicLong类提供了一个可以进行原子性读和写操作的long变量,它还包含一系列先进的原子性操作,比如compareAndSet()。

创建AtomicLong :AtomicLong atomicLong=new AtomicLong(168);

获取AtomicLong的值:long theValue=atomicLong.get();

设置AtomicLong的值:atomicLong.set(234);

比较并设置AtomicLong的值

AtomicLong类也有一个原子性的compareAndSet() 方法。将AtomicLong实例的当前值与期望值进行比较,如果两种相等,为AtomicLong 实例设置一个新值。atomicLong.compareAndSet(expectedValue, newValue);

AtomicLong 具备一些能够增加 AtomicLong 的值并返回自身值的方法。

·        addAndGet()

·        getAndAdd()

·        getAndIncrement()

·        incrementAndGet()

方法addAndGet()将AtomicLong的值加一个数字,并返回增加后的值。

方法getAndAdd()将AtomicLong的值加一个数字,但返回的是增加前的AtomicLong的值。

getAndIncrement()和incrementAndGet()方法类似于getAndAdd()和addAndGet(),但每次只将 AtomicLong 的值加1。

AtomicLong 类还提供了一些减小AtomicLong的值的原子性方法。这些方法是:

·        decrementAndGet()

·        getAndDecrement()

decrementAndGet()将AtomicLong的值减一,并返回减一后的值。

getAndDecrement()将AtomicLong的值减一,但它返回的是减一之前的值。

原子性引用型AtomicReference

AtomicReference提供了一个可以被原子性读和写的对象引用变量。原子性的意思是多个想要改变同一个AtomicReference的线程不会导致AtomicReference处于不一致的状态。AtomicReference 还有一个compareAndSet() 方法,通过它你可以将当前引用于一个期望值(引用)进行比较,如果相等,在该AtomicReference对象内部设置一个新的引用。
创建AtomicReference:AtomicReference atomicReference=new AtomicReference();

String initialReference="the initially referenced string";  

AtomicReference atomicReference=new AtomicReference(initialReference);  

创建泛型 AtomicReference

AtomicReference<String> reference=new AtomicReference<String>(initialValue);

获取 AtomicReference 引用

可以通过AtomicReference的get()方法来获取保存在AtomicReference里的引用。如果AtomicReference是非泛型的,get()方法将返回一个Object类型的引用。如果是泛型化的,get()将返回创建AtomicReference时声明的那个类型。
AtomicReference atomicReference=new AtomicReference("referenced");

String reference=(String)atomicReference.get();  
注意如何对get()方法返回的引用强制转换为String。
AtomicReference<String> reference=new AtomicReference<String>("value");  

String referenceValue=reference.get();  

编译器知道引用的类型,所以无需对get()返回的引用进行强制转换。

设置AtomicReference引用

可以使用set()方法对AtomicReference里边保存的引用进行设置。如果定义的是非泛型 AtomicReference,set()将会以一个Object引用作为参数。如果是泛型化的 AtomicReference,set()方法将只接受定义给定的类型。
AtomicReference atomicReference=new AtomicReference();  

atomicReference.set("New object referenced");  

非泛型和泛型化的区别在于编译器将对能够设置给一个泛型化的AtomicReference参数类型进行限制。

比较并设置AtomicReference引用

compareAndSet()可以将保存在AtomicReference里的引用与期望引用进行比较,如果两个引用是一样的(非equals()的相等,而是==的相等),将会给AtomicReference实例设置新的引用。如果compareAndSet()为AtomicReference设置了新的引用,compareAndSet()将返回true。否则compareAndSet()返回false。
String initialValue="initial value referenced";  

AtomicReference<String> reference=new AtomicReference<String>(initialValue);

String newValue="new value referenced";  

boolean exchanged=atomicStringReference.compareAndSet(initialValue,newValue);

exchanged=atomicStringReference.compareAndSet(initialValue,newValue);

移相器Phaser

移相器Phaser是在JDK7中引入了的可重复使用的同步屏障。

Phaser拥有与CyclicBarrier和CountDownLatch类似的功能,CountDownLatch和CyclicBarrier都是只适用于固定数量的参与者,移相器适用于可变数目的屏障,在这个意义上,可以在任何时间注册新的参与者,并且在抵达屏障是可以注销已经注册的参与者。因此注册到同步移相器的参与者的数目可能会随着时间的推移而变化,如CyclicBarrier一样,移相器可以重复使用,这意味着当前参与者到达移相器后,可以再一次注册自己并等待另一次到达。因此移相器会有多代,一旦为某个特定相位注册的所有参与者都到达移相器就增加相数。相数从零开始,在达到Integer.MAX_VALUE后再次绕回0。当移相器发生变化时,通过重写onAdvance方法,可以自行可选操作,这个方法也可用于终止移相器。移相器一旦被终止,所有的同步方法就会立即返回,并尝试注册新的失败的参与者。

移相器可能是分层的,这允许你以树形结构来安排移相器以减少竞争。很明显,更小的组将拥有更少的竞争同步的参与者。因此将大量的参与者分成较小的组可以减少竞争。虽然创建移相器能增加中和吞吐量,但是这需要更多的开销。最后移相器的另一个重要的特征在于监控功能,使用独立的对象可以监视移相器的当前状态。监视器可以查询注册到移相器的参与者的数量,以及已经到达和还没有到达某个特定相数的参与者的数量。

Timer

*schedule(TimerTask task, long delay) 延时delay ms后执行定时任务task

*schedule(TimerTask task, Date time) 到达这个time时间点执行定时任务task

*schedule(TimerTask task, long delay, long period) 延时delay ms后执行定时任务task,之后以period ms为周期重复执行task

* schedule(TimerTasktask, Date firstTime, long period) 到达这个time时间点执行定时任务task,之后以period ms为周期重复执行task

*scheduleAtFixedRate(TimerTask task, long delay, long period) 延时delay ms后执行定时任务task,之后以period ms为周期重复执行task

*scheduleAtFixedRate(TimerTask task, Date firstTime, long period) 到达这个time时间点执行定时任务task,之后以period ms为周期重复执行task

带参数period的schedule和scheduleAtFixedRate的区别:

> 如果周期是30s,任务执行时间是8s,那么两者的执行效果是一样的

> 但是如果任务执行时间大于周期时间,scheduleAtFixedRate会按照周期时间来,即不管任务执行多久,都是周期一到就重新执行task。而schedule的下一次开始执行时间是取决与上一次结束时间,如果任务执行时间大于周期时间,那么它会按照执行时间为周期执行任务task。

死锁

* 要有两个或两个以上的线程

* 至少有两个共享资源的锁

* 至少存在两个线程各自拥有一个锁

* 现在这两个线程在等待获取彼此的锁,这就出现死锁了

后记:待处理

原创粉丝点击