四. JUC工具包和Executor框架
4.1 Atomic数据类型
4.1.1 Atomic Integer
线程安全
- volatile关键字保证可见性和禁止重排序
- unsafe保证原子性
public static void main(String[] args){ final AtomicInteger value = new AtomicInteger(); Thread t1 = new Thread(){ @Override public void run(){ int x = 0; while(x < 500){ int v = value.getAndIncrement(); System.out.println(Thread.currentThread().getName() + ":" + v); x++ } } }; Thread t2 = new Thread(){ @Override public void run(){ int x = 0; while(x < 500){ int v = value.getAndIncrement(); System.out.println(Thread.currentThread().getName() + ":" + v); x++ } } }; t1.start(); t2.start(); t1.join(); t2.join(); }
AtomicInteger的CAS原理
- AtomicInteger通过compare and swap(CAS)算法实现线程安全机制---最快失败策略
for(;;){ int current = get(); int next = current + delta; if(compareAndSet(current,next)){ return current; } } }
- 当存储的预期值current与内存值value相同时,才将新值next赋值给value
CAS
- CompareAndSwap:JUC包中借助CAS实现了区别于synchronized同步锁的一种乐观锁,其实CAS也算是有锁的操作,只不过由CPU来触发,比Synchronized性能更好
- 使用CAS自定义一个不阻塞的显示锁
public static void main(String[] args){ final CompareAndSetLock lock = new CompareAndSetLock(); for(int i=0;i < 2;i++){ new Thread(){ @Override public void run(){ try{ lock.tryLock(); System.out.println(Thread.currentThread().getName() + "get lock") Thread.sleep(10_000L); }catch(InterruptedException e1){ e.printStackTrace(); }catch(GetLockException e2){ e.printStackTrace(); }finally{ lock.unlock(); } } }.start(); } } ----------------------- public class CompareAndSetLock{ prvivate final AtomicInteger value = new AtomicInteger(0); private Thread currentThread; public void tryLock() throw GetLockException{ boolean success = value.compareAndSet(0,1); if(!success){ throw new GetLockException("try to get lock failed"); } currentThread = Thread.currentThread(); } public void unlock(){ if(0 == value.get()){ return; } if(lockThread == Thread.currentThread()){ value.compareAndSet(1,0); } } }
4.1.2 Atomic Boolean
AtomicBoolean就是只有0,1的AtomicInteger
- AtomicBoolean用于优雅的去停止线程或在多线程下去操作一个开关,就不需要给flag添加volatile关键字了
private final static AtomicBoolean flag = new AtomicBoolean(true); public static void main(String[] args) throw InterruptedException{ new Thread(){ @Override public void run(){ while(this.flag.get()){ try{ Thread.sleep(1000L); System.out.println("working..."); }catch(InterruptedException e){ e.printStackTrace(); } } System.out.println("finished..."); } }.start(); Thread.sleep(5000L); this.flag.set(false); }
4.1.3 Atomic Long
- AtomicLong和AtomicInteger的区别在于,Long是64位的,而CPU可能是32位的,就需要分高低位区分数据,所以根据支持的不同所调用的方法也不同
public final boolean compareAndSet(long expect,long update){ return unsafe.compareAndSwapLong(this.valueOffSet,expect,update); }
4.1.4 Atomic Reference
CAS锁的优点: 1.原子性 2.有序性 3.可见性
- volatile修饰的变量,能保证后两者
- CAS算法,也就是CPU级别的同步指令,相当于乐观锁,可以测到其他线程对共享数据的变化情况
CAS锁的弊端
- 会引入ABA的问题,尤其是Reference数据类型
解决方案
- 像数据库一样可以增加一个乐观锁,打一个版本标签,根据版本标签来判断
- 使用AtomicStampedReference来添加一个版本戳
4.1.5 Atomic Stamped Reference
其原理也是增加一个版本的标记
public class AtomicStampedReference<V>{ private static class ReferenceIntegerPair<T>{ private final T reference; private final int integer; ReferenceIntegerPair(T r,int i){ reference = r; integer = i; } } }
private static AtomicStampedReference<Integer> atomicRef = new AtomicStampedReference<>(100,0); public static void main(String[] args){ Thread t1 = new Thread(){ @Override public void run(){ try{ TimeUnit.SECONDS.sleep(1); boolean success = atomicRef.compareAndSet( 100,101,atomicRef.getStamp(),atomicRef.getStamp() + 1); System.out.println(success);//ture success = atomicRef.compareAndSet( 101,100,atomicRef.getStamp(),atomicRef.getStamp() + 1); System.out.println(success);//ture } }catch(InterruptedException e){ e.printStackTrace(); } }.start(); Thread t2 = new Thread(){ @Override public void run(){ try{ int stamp = atomicRef.getStamp(); System.out.println("Before:" + stamp); TimeUnit.SECONDS.sleep(2); boolean success = atomicRef.compareAndSet(100,101,stamp,stamp + 1); System.out.println(success);//false }catch(InterruptedException e){ e.printStackTrace(); } } }.start(); }
4.1.6 Atomic xxx Field Updater
当一个类没有进行AtomicReference封装,并且其Field也不是Atomic时,如果想要对其属性进行原子性操作,就可以使用
- AtomicIntegerFieldUpdater<TestMe> updater = AtomicIntegerFieldUpdater.newUpdater(TestMe.class,"i");
public class AIFUTest{ private volatile int i; //private AtomicInteger j = new AtomicInteger(); private AtomicIntegerFieldUpdater<AIFUTest> updater = AtomicIntegerFieldUpdater.newUpdater(AIFUTest.class,"i"); public void update(int newValue){ updater.compareAndSet(this,i,newValue); } public void get(){ return i; } public static void main(String[] args){ AIFUTest test = new AIFUTest(); test.update(10); System.out.println(test.get()); } }
应用场景:可以解决AtomicStampedReference占资源大的问题
想让类的属性操作具备原子性
- volatile
- 非private,protected(如果是当前类也可以是private和protected)
- 类型必须一致
- 不想使用显示锁或synchronized
- 大量需要原子类型修饰的对象
4.1.7 Unsafe
- Unsafe类不能直接调用,但是可以通过反射来调用类中的方法和获取属性
public static void main(String[] args){ //Unsafe unsafe = Unsafe.getUnsafe(); Unsafe unsafe = getUnsafe(); System.out.println(unsafe); } private static Unsafe getUnsafe(){ try{ Field f = Unsafe.class.getDeclaredField("theUnsafe"); f.setAccessible(true); return (Unsafe)f.get(null); }catch(Exception e){ e.printStackTrace(); } }
使用Unsafe自定义CASCounter
- ReetrantLock底层也是使用Unsafe实现的
public class CASCounter{ private volatile long counter = 0; private Unsafe unsafe; private long offset; CASCounter() throws Exception{ unsafe = getUnsafe(); offset = unsafe.objectFieldOffset(CASCounter.class.getDeclaredField("counter")); } public void increment(){ long current = counter; while(!unsafe.compareAndSwapLong(this,offset,counter,counter + 1)){ current = counter; } } }
Unsafe的其他操作---不建议使用
Unsafe#allcateInstance(Class clazz):能够跳过构造方法进行实例化,对于一般获取实例独享,如:new,反射等都会进行实例化,实例化的同时会调用构造方法
public static void main(String[] args){ Unsafe unsafe = getUnsafe(); Simple simple = (Simple)unsafe.allocateInstance(Simple.class); } class Simple{ public Simple(){ System.out.println("---------"); } }
Unsafe#putInt():方法能对内存中的数据进行修改
public static void main(String[] args){ Guard guard = new Guard(); guard.word(); Field f = guard.getClass().getDeclaredField("ACCESS_ALLOWED"); unsafe.putInt(guard,unsafe.objectFieldOffset(f),42); guard.work(); } class Guard{ private int ACCESS_ALLOWED = 1; private boolean allow(){ return 42 == ACCESS_ALLOWED; } public void work(){ if(allow()){ System.out.println("allowed...") } } }
Unsafe#defineClass():可以将从class文件中加载读取的字节数组转换位Class对象,从而通过反射调用字节码对象中的成员属性和方法----即拥有类加载器的功能
public static void main(String[] args){ byte[] bytes = loadClassContent(); Class clazz = Unsafe.defineClass(null,bytes,0,bytes.length); int v = (Integet)clazz.getMethod("get").invoke(clazz.newInstance(),null); System.out.println(v); }
4.2 Java并发包工具
4.2.1 CountDownLatch
- 简单的线程同步工具包,一个或多个线程去等待多个线程完成
- 在一系列串行化过程中,如果中间某些部分是可并行化的,那就选择并行化去做,提高运行速度,然后再串行化----其实这就可以看作是wait-notify的工具包
中断await()的三种方式
- CountDown减到0
- await(1000,TimeUnit.MILLSECONDS)计时结束
- 对wait线程调用interrupted()方法
public class CountDownLatchExample{ public static void main(String[] args){ final CountDownLatch latch = new CountDownLatch(1); final Thread main = Thread.currentThread(); new Thread(){ @Override public void run(){ try{ Thread.sleep(10_000L); }catch(InterruptedException e){ e.printStackTrace(); } //latch.countDown(); 方式一 main.interrupt(); ///方式三 } }.start(); latch.await(); //latch.await(1000,TimeUnit.MILLSECONDS); 方式二 System.out.println("--------------"); } }
案例1:当使用线程池时,不能获取每个线程对象对其中的单个线程使用join()方法,所以做不到让main线程在其他线程执行完后再结束的操作
解决方法一:使用ExecutorService#shutdown()和ExecutorService#awaitTermination()
shutdown和awaitTermination为接口ExecutorService定义的两个方法,一般情况配合使用来关闭线程池
- shutdown方法:平滑的关闭ExecutorService,当此方法被调用时,ExecutorService停止接收到新的任务并且等待已提交的任务(包含正在执行的和提交未执行的)执行完成,当所有提交的任务执行完毕,线程池即被关闭
- awaitTermination方法:接收timeout和TimeUnit两个参数,用于设定超时时间及单位,当超时时间到会检测ExecutorService是否已经关闭,若关闭返回true,否则返回false,一般与shutdown组合使用
public static void main(String[] args){ final ExecutorService executor = Executors.newFixedThreadPool(2); for(int i=0;i < 10;i++){ executor.execute(new SimpleRunnable()); } executor.shutdown(); executor.awaitTermination(1,TimeUnit.HOURS); System.out.print("all of work finish...") }
解决方法二:使用CountDownLatch,当计数器减小到0时解除阻塞
public static void main(String[] args){ final ExecutorService executor = Executor.newFixedThreadPool(2); final CountDownLatch latch = new CountDownLatch(10); for(int i=0;i < 10;i++){ executor.execute(new SimpleRunnable(latch)); } latch.await(); System.out.println("all of work finish..."); executor.shutdown() } ------------------------------------- static class SimpleRunnable implement Runnable{ private final CountDownLatch latch; private final Random random = new Random(System.currentTimeMills()); public SimpleRunnable(CountDownLatch latch){ this.latch = latch; } @Override public void run(){ try{ Thread.sleep(Random.nextInt(1000)); }catch(InterruptedException e){ e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "woking"); latch.countDown(); } }
案例二:
- 有一个任务分为ABC三个步骤,原本ABC串行化执行,但是AB可以同时执行,而C需要B执行完后才能开始执行,那么就可以使用CountDownLatch来作为一个wait-notify机制,A执行完后wait(),等B执行完后CountDown()来notifyA,从而接着执行C,时间由1+2+1变为2+1
同时我们也可以通过线程间通信来完成,两个线程共享一个shareData,当A线程执行完后去查看变量状态,若未完成则进入wait,等待B线程完成后notify
public static void main(String[] args){ final CountDownLatch latch = new CountDownLatch(1); new Thread(){ @Override public void run(){ try{ System.out.println("begin task a..."); Thread.sleep(1000L); latch.await(); System.out.println("begin task c..."); }catch(InterruptedException e){ e.printStackTrace(); } } }.start(); new Thread(){ @Override public void run(){ try{ System.out.println("begin task b..."); Thread.sleep(1000L); }catch(InterruptedException e){ e.printStackTrace(); }finally{ latch.countDown(); } } }.start(); }
4.2.2 Cyclic Barrier
- 在CountDownLatch上添加reset功能-----多个线程互相等待完成
- 与CountDownLatch有点类似,但是CyclicBarrier是每个线程执行一个完阶段后互相自主等待,待全部完成后继续执行,而CountDownLatch是进入阻塞后,当全部完成一个阶段后,由CountDown减少到0后发出解除阻塞的指令
- CountDownLatch不能reset,而CyclicBarrier可以循环使用,reset状态=begin状态=finish状态
案例一:两个线程,执行过程等待同步结束,且同步后回调通知
public static void main(String[] args){ final CyclicBarrier barrier = new CyclicBarrier(3); new Thread(){ @Override public void run(){ try{ TimeUnit.SECONDS.sleep(10); System.out.println(Thread.currentThread().getName() + "finished..."); barrier.await(); System.out.println(Thread.currentThread().getName() + "all..."); }catch(InterruptedException e){ e.printStackTrace(); } } }.start(); new Thread(){ @Override public void run(){ try{ TimeUnit.SECONDS.sleep(5); System.out.println(Thread.currentThread().getName() + "finish"); barrier.await(); System.out.println(Thread.currentThread().getName() + "all..."); } } }.start(); barrier.await(); System.out.println(Thread.currentThread().getName() + "all..."); }
案例二:使用CountDownLatch模仿CyclicBarrier实现CountDown到0后自动回调
public static void main(String[] args){ final MyCountDownLatch latch = new MyCountDownLatch(2,new Runnable(){ @Override public void run(){ System.out.println("call back behind finished..."); } }); new Thread(){ @Override public void run(){ try{ TimeUnit.SECONDS.sleep(1); }catch(InterruptedException e){ e.printStackTrace(); } latch.countDown(); System.out.println(Thread.currentThread().getName() + "finished."); } }.start(); new Thread(){ @Override public void run(){ try{ TimeUnit.SECONDS.sleep(2); }catch(InterruptedException e){ e.printStackTrace(); } latch.countDown(); System.out.println(Thread.currentThread().getName() + "finished."); } }.start(); } ------------------------------------------ public class MyCountDownLatch extends CountDownLatch{ private final Runnable runnable; public MyCountDownLatch(int count,Runnable runnable){ super(count); this.runnable = runnable; } public void countDown(){ super.countDown(); if(getCount == 0){ this.runnable.run(); } } }
4.1.3 Exchanger
- 两个线程之间的数据信息传输
用于两个线程之间的数据信息交换,只成对的使用,并且会阻塞到两个线程同时达到exchange()方法执行,否则会有线程一直处于blocked状态
public static void main(String[] args){ final Exchanger<String> exchanger = new Exchanger<String>(); new Thread(new Runnable(){ @Override public void run(){ System.out.println(Thread.currentThread().getName() + "start..."); try{ String result = exchanger.exchange("From A",5,TimeUnit.SECONDS); System.out.println(Thread.currentThread().getName() + result); }catch(InterruptedException e){ e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "finish"); } },"Thread A").start(); new Thread(new Runnable(){ @Override public void run(){ System.out.println(Thread.currentThread().getName() + "start..."); try{ String result = exchanger.exchange("From B"); System.out.println(Thread.currentThread().getName() + result); }catch(InterruptedException e){ e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "finish"); } },"Thread B").start(); }
线程之间发送和接收的数据是同一内存地址的数据,有线程安全问题,所以数据使用Atomic来封装
public static void main(String[] args){ final Exchanger<Integer> exchanger = new Exchanger<Integer>(); new Thread(){ @Override public void run(){ AtomicReference<Integer> value = new AtomicReference<>(1); try{ while(true){ value.set(exchanger.exchange(value.get())); System.out.println(Thread.currentThread().getName() + value.get()); TimeUnit.SECONDS.sleep(2); }catch(InterruptedException e){ e.printStackTrace(); } } } }.start(); new Thread(){ @Override public void run(){ AtomicReference<Integer> value = new AtomicReference<>(2); try{ while(true){ value.set(exchanger.exchange(value.get())); System.out.println(Thread.currentThread().getName() + value.get()); TimeUnit.SECONDS.sleep(3); }catch(InterruptedException e){ e.printStackTrace(); } } } }.start(); }
4.1.4 Semaphore
- 可以同时控制进入同步的线程数,自定义锁不止同时进入一个线程
- 就是维护了一堆的许可证,只有拿到剩余的许可证时才能通过,否则访问的话就会被blocked住
案例一:使用Semaphore来制作自定义Lock
Semaphore自定义锁和Synchronized关键字区别
- 在lock()阻塞过程中,synchronized每次只能通过一个线程,而Semaphore可以通过构造方法指定同时拿到锁的线程数
public static void main(String[] args){ final SemaphoreLock lock = new SemaphoreLock(2); for(int i=0;i < 3;i++){ new Thread(){ @Override public void run(){ try{ lock.lock(); System.out.println(Thread.currentThread().getName() + "- working"); TimeUnit.SECONDS(1).sleep(); }catch(InterruptedException e){ e.printStackTrace(); }finally{ lock.unlock(); } } }.start(); } } ----------------------------------------------------------- public class SemaphoreLock{ private final Semaphore semaphore; public SemaphoreLock(int number){ semaphore = new SemaphoreLock(number); } public void lock(){ semaphore.acquire(); } public void unlock(){ semaphore.release(); } }
案例二:设置线程每次获取多个permit,当剩余permit数小于获取数时进入blocked状态
- 当将new Semaphore构造方法改为2时,可以允许多个线程同时拿到锁,可以运用在控制多个固定的连接访问的情景
- 当将acquire()方法的参数设置为2时,就表示每个线程的访问都会获取2个permit
public static void main(String[] args){ final Semaphore semaphore = new Semaphore(3); for(int i=0;i < 2;i++){ new Thread(){ @Override public void run(){ System.out.println(Thread.currentThread().getName() + "in"); try{ semaphore.acquire(2); System.out.println(Thread.currentThread().getName() + "working"); TimeUnit.SECONDS.sleep(1); }catch(InterruptedException e){ e.printStackTrace(); }finally{ semaphore.release(2); } System.out.println(Thread.currentThread().getName() + "out"); } } }.start(); }
4.1.5 ReentrantLock
- 效率很高的自旋锁,显示锁
显示锁和synchronized
private static final Lock lock = new ReentrantLock(); public static void main(String[] args){ //... } public static void needLock(){ try{ lock.lock(); System.out.println(Thread.currentThread().getName() + "get lock"); TimeUnit.SECONDS.sleep(1); }catch(InterruptedException e){//此中断异常是sleep()方法的,而不是lock()方法 e.printStackTrace(); }finally{ lock.unlock(); } } public static void needLockBySyn(){ synchronized(this){ try{ TimeUnit.SECONDS.sleep(1); System.out.println(Thread.currentThread().getName() + "into syn"); }catch(InterruptedException e){ e.printStackTrace(); } } }
Lock接口中的API
- lock():Lock接口中的lock()方法进入阻塞时是不能被interrupt的----synchronized关键字就是不能被打断的
- lockInterruptibly():此lock方法是可以被打断的
public static void main(String[] args){ Thread t1 = new Thread(() -> lockInterruptibly()); t1.start(); TimeUnit.SECONDS.sleep(1); Thread t2 = new Thread(() -> lockInterruptiblu()); t2.start(); TimeUnit.SECONDS.sleep(1); t2.interrupt(); System.out.println("==========="); } public static void lockInterrupibly(){ try{ lock.interruptibly(); System.out.println("lock"); while(true){ //empty... } }catch(InterruptedException e){ e.printStackTrace(); }finally{ lock.unlock() } }
- tryLock():此方法会立即返回是否拿到锁,可以对其结果判断来做出没有拿到锁后的操作
public static void testTryLock(){ try{ if(!lock.tryLock()){ return; } System.out.println(Thread.currentThread().getName() + "get lock"); while(true){ //empty... } }finally{ lock.unlock(); } }
实现类ReentrantLock的API
- getQueueLength():获取在waiting队列中的线程数
- hasQueueThreads():waiting队列中是否存在线程
- getHoldCount():查看当前线程获取锁的个数
4.1.6 ReadWriteLock
- 读写分离锁,但是读悲观,读的过程写操作不能抢锁
读写分离锁,在只有读的操作时是并行操作,在有写的操作时为串行,分为读锁和写锁
private final static ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock(true); private final static Lock readLock = readWriteLock.readLock(); private final static Lock writeLock = readWriteLock.writeLock(); private final static List<Long> data = new ArrayList<>(); public static void main(String[] args){ Thread t1 = new Thread(() -> read(),"readLock"); t1.start(); TimeUnit.SECONDS.sleep(1); Thread t2 = new Thread(() -> write(),"writeLock"); t2.start(); } public void read(){ try{ readLock.lock(); data.forEach(System.out::println); TimeUnit.SECONDS.sleep(5); System.out.println(Thread.currentThread().getName() + "finished..."); }catch(InterruptedException e){ e.printStackTrace(); }finally{ readLock.unlock(); } } public void write(){ try{ writeLock.write(); data.add(System.currentTimeMillis()); TimeUnit.SECONDS.sleep(5); System.out.println(Thread.currentThread().getName() + "finished..."); }catch(InterruptedException e){ e.printStackTrace(); }finally{ writeLock.unlock(); } }
4.1.7 Condition
- 使用显示锁Lock时候的wait-notify机制
当使用synchronized来同步时,使用当前的monitor对象来进行wait-notifyAll进行线程间的通信,当使用显示锁Lock时,则通过Lock构造出来的Condition对象进行线程间的通信
private final static ReentrantLock lock = new ReentrantLock(); private final static Condition condition = lock.newCondition(); prviate static int data = 0; private static volatile boolean noUse = true; public static void produce(){ try{ lock.lock(); while(noUse){ condition.await(); } data++; Optional.of("P:" + data).ifPresent(System.out::println); TimeUnit.SECONDS.sleep(1); noUse = true; condition.signalAll(); }catch(IterruptedException e){ e.printStackTrace(); }finally{ lock.unlock(); } } public static void consumer(){ try{ lock.lock(); while(!noUse){ condition.await(); } Optional.of("C:" + data).ifPresent(System.out::println); TimeUnit.SECONDS.sleep(1); noUse = false; condition.signalAll(); }catch(InterruptedException e){ e.printStackTrace(); }finally{ lock.unlock(); } }
其中Condition#await()方法和synchronized中wait()一样,在wait时会释放锁,并且Condition#Await()必须和lock()一起使用,若没有lock()就会没有存储blocked对象的monitor
private static void produce(){ synchronized(MONITOR){ while(noUser){ try{ MONITOR.wait(); }catch(InterruptedException e){ e.printStackTrace(); } } data++; System.out.println("P:" + data); noUse = true; MONITOR.notifyAll(); } }
4.1.8 StampedLock
- 可以在ReadWriteLock基础上实现乐观读操作
可以替代ReentrantLock和ReadWriteLock
- 但是它并没有继承Lock接口,使用的是LockSupport
- 它可以使得读写锁优先写操作
- 在StampedLock中对于读操作有一个乐观的机制,一般来说在拿到读锁的时候,写操作是blocked住的,这对于读是悲观的,而在StampedLock中允许在读的过程中去进行写操作,并使用戳来记录是否发生改变,若读的过程中发生改变则通过CAS进行回滚
- 在ReadWriteLock中write锁被抢到的时候,read锁也是无法获取的,只有进行read操作时才允许并行,并write锁也是无法获取的,而在StampedLock中,read锁被获取的时候,write锁也是可以被获取的
案例一:悲观读---在读的过程中只能拿到读锁,不能拿到写锁
private final static StampedLock lock = new StampedLock(); private final static List<Long> DATA = new ArrayList<>(); public static void read(){ long stamp = -1; try{ stamp = lock.readLock(); System.out.println(Thread.currentThread().getName() + DATA); TimeUnit.SECONDS.sleep(1) }catch(InterruptedException e){ e.printStackTrace(); }finally{ lock.unlockRead(); } } public static void write(){ long stamp = -1; try{ stamp = lock.writeLock(); DATA.add(System.currentTimeMillis()); TimeUnit.SECONDS.sleep(1); }catch(InterruptedException e){ e.printStackTrace(); }finally{ lock.unlockWrite(); } }
案例二:乐观读---使用StampedLock来实现乐观读,在读的过程中其他线程也可以拿到写锁
public static void read(){ long stamp = lock.tryOptimisticRead(); if(lock.validate(stamp)){ try{ stamp = lock.readLock(); System.err.println(stamp + "-" + DATA); TimeUnit.SECONDS.sleep(1); }catch(InterruptedException e){ e.printStackTrace(); }finally{ lock.unlockRead(stamp); } } }
4.1.9 ForkJoin
- 分而治之,把线程的任务递归到子线程去完成
- 一个人完成不了就把任务递归分解给多个人完成,一个线程完成不了就递归分解给多个任务
案例:使用forkJoin完成多个数字累加,如果数字个数大于设定值,就向下递归
有返回值---RecursiveTask
private final static int MAX = 3; public static void main(String[] args){ final ForkJoinPool pool = new ForkJoinPool(); ForkJoinTask<Integert> future = pool.submit(new CalculatedRecursiveTask(0,10)); try{ Integer result = future.get(); System.out.println(result); }catch(InterruptedException e){ e.printStackTrace() }catch(ExecutionException e){ e.printStackTrace(); } } ---------------------------------------------------- public class CalCulatedRecursiveTask extends RecursiveTask<Integet>{ private final int start; private final int end; CalculatedRecursiveTask(int start,int end){ this.start = start; this.end = end; } @Override protected Integer compute(){ if(end - start <= MAX){ return intStream.rangeClosed(start,end).sum(); }else{ int mid = (start + end) / 2; CalculatedRecursiveTask leftTask = new CalculatedRecursiveTask(start,mid); CalculatedRecursiveTask rightTask = new CalculatedRecursiveTask(mid+1,end); leftTask.fork(); rightTask.fork(); return leftTask.join() + rightTask.join(); } } }
无返回值---RecursiveAction:其中要获取数据需等待其计算完才能获取
private final static int MAX = 3; private final static AtomicInteger sum = new AtomicInteger(0); public static void main(String[] args){ final ForkJoinPool pool = new ForkJoinPool(); pool.submit(new CalculatedRecursiveAction(0,10,sum)); pool.awaitTermination(10,TimeUnit.SECONDS); Optional.of(SUM).ifPresent(System.out::println); } ------------------------------------------------------------- public class CalculatedRecursiveAction extemds RecursiveAction<Integer>{ private final int start; private final int end; private final static AtomicInteger sum; public CalculatedRecursiveAction(int start,int end){ this.start = start; this.end = end; this.sum = sum; } @Override protected void compute(){ if((end - start) < MAX){ sum.addAndGet(IntStream.rangeClosed(start,end).sum()); }else{ int mid = (start + end) / 2; CalculatedRecursiveTask leftTask = new CalculatedRecursiveAction(start,mid); CalculatedRecursiveTask rightTask = new CalculatedRecursiveAction(mid+1,end); leftTask.fork(); rightTask.fork(); } } }
4.1.10 Phaser
- CountDownLatch和Cyclic Barrier的加强版本,是一个阶段器,能进行reset和动态操作parties
- Phaser拥有CountDownLatch和CyclicBarrier的功能,其中CountDownLatch数据是不能重复使用的,而CyclicBarrier是可以reset复位的,但是CyclicBarrier中的parties是不能改变的,而Phaser是可以动态变化的,其中arriveAndAwaitOnAdvance()是要等待所有注册的parties完成任务
案例一:类似同步屏障CyclicBarrier#await()功能,并且动态注册parties
private final static Random random = new Random(System.currentTimeMillis()); public static void main(String[] args){ final Phaser phaser = new Phaser(); IntStream.rangeClosed(1,5).map(i -> phaser).forEach(Task::new); phaser.register(); phaser.arriveAndAwaitAdvance(); System.out.println("working finished...") } -------------------------------------------------------- public class Task extends Thread{ private final Phaser phaser; public Task(Phaser phaser){ this.phaser = phaser; this.phaser.register(); start(); } @Override public void run(){ System.out.println(Thread.currentThread().getName() + ":working"); try{ TimeUnit.SECONDS.sleep(random.next(2)); }catch(InterruptedException e){ e.printStackTrace(); } phaser.arriveAndAwaitAdvance(); } }
案例二:所有线程完成一个阶段,等待所有线程完成后再开始下一个阶段
- 其中phaser中计数是递增的,而CountDownLatch是递减的
public static void main(String[] args){ final Phaser phaser = new Phaser(5); for(int i=1;i < 6;i++){ new Athletes(i,phaser).start(); } } ---------------------------------------------------------- public class Athletes extends Thread{ private final Phaser phaser; private final int number; public Athletes(Phaser phaser,int number){ this.phaser = phaser; this.number = number; } @Override public void run(){ try{ System.out.println(number + ": start 1"); TimeUnit.SECONDS.sleep(3); System.out.println(number + ":end 1"); phaser.arriveAndAwaitAdvance(); System.out.println(number + ": start 2"); TimeUnit.SECONDS.sleep(3); System.out.println(number + ":end 2"); phaser.arriveAndAwaitAdvance(); System.out.println(number + ": start 3"); TimeUnit.SECONDS.sleep(3); System.out.println(number + ":end 3"); phaser.arriveAndAwaitAdvance(); }catch(InterruptedException e){ e.printStackTrace(); } } }
案例三:当运动员完成到第二项后,第三项有人受伤不能触发arriveAndAwaitAdvance(),那么所有的线程都会等待这个线程完成,这样的话就会造成阻塞,所以要将这个有问题的线程异常注销掉#arriveAndDeregister()
- 动态增加或减少参与者
public static void main(String[] args){ final Phaser phaser = new Phaser(5); for(int i=1;i < 5;i++){ new Athletes(i,phaser).start(); } new injuredAthletes(6,phaser).start(); } ---------------------------------------------------------- public class injuredAthletes extends Thread{ private final Phaser phaser; injuredAthletes(int number,Phaser phaser){ this.number = number; this.phaser = phaser; } @Override public void run(){ try{ System.out.println(number + ": start 1"); TimeUnit.SECONDS.sleep(3); System.out.println(number + ":end 1"); phaser.arriveAndAwaitAdvance(); System.out.println(number + ": start 2"); TimeUnit.SECONDS.sleep(3); System.out.println(number + ":end 2"); phaser.arriveAndAwaitAdvance(); System.out.println(number + ": Injured 3"); phaser.arriveAndDeregister(); }catch(InterruptedException e){ e.printStackTrace(); } } }
Phaser的API
- 构造方法:Phaser phaser = new Phaser(int n):创建一个注册数为n的Phaser
- register():注册party
- arriveAndAwaitAdvance():arrive并等待其他parties完成
- arriveAndDeregister():arrive并注销当前party
- phase():获取当前Phaser处于的阶段
- getRegisteredParties():获取总的注册数
- getArriveParties():获取已arrive的parties数
- getUnarrivedParties():获取还未arrive的parties数
- bulkRegister(int n):批量注册
onadvice():
- 此方法每个阶段执行完都会调用一次,一般都会进行重写
- 此方法的返回值是true时Phaser会被终止,可以通过设置此值来终止线程
- arrive():此方法只会通知已经到达,但是不会由于其他线程还未到达而被阻塞住
案例四:子线程使用arrive()方法来进行任务,主线程使用arriveAndAwaitOnAdvance来记录所有线程完成第一阶段的时机
public static void main(String[] args){ final Phaser phaser = new Phaser(5); for(int i=0;i < 4;i++){ new ArriveTask(phaser).start(); } phaser.arriveAndAwaitAdvance(); System.out.println("the first phase of all works were finished...") } ------------------------------------------------- public class ArriveTask extends Thread{ private final Phaser phaser; public ArriveTask(Phaser phaser,int number){ super(String.valueOf(number)); this.phaser = phaser; } @Override public void run(){ try{ System.out.println(getName() + "begin..."); TimeUnit.SECONDS.sleep(2); System.out.println(getName() + "running..."); phaser.arrive(); TimeUnit.SECONDS.sleep(3); System.out.println(getName() + "other work"); }catch(InterruptedException e){ e.printStackTrace(); } } }
AwaitAdvance(int phase):类似CountDownLatch,调用AwaitAdvance(int phase)方法的线程同样进入阻塞(其中phase为当前的阶段数),但是不会计入parties,只有当parties达到指定数量时,阻塞才会解除
public static void main(String[] args){ final Phaser phaser = new Phaser(6); IntStream.rangeClosed(0,5).boxed().map(i -> phaser) .forEach(AwaitAdvanceTask::new); phaser.AwaitAdvance(phaser.getPhase()); System.out.println("all work were finished..."); } ----------------------------------------------- public class AwaitAdvanceTask extends Thread{ private final Phaser phaser; public AwaitAdvanceTask(Phaser phaser){ this.phaser = phaser; start(); } @Override public void run(){ try{ TimeUnit.SECONDS.sleep(2); }catch(InterruptedException e){ e.printStackTrace(); } phaser.arriveAndAwaitAdvance(); System.out.println(getName() + "finished work..."); } }
forceTermination():强制退出阻塞
public static void main(String[] args){ final Phaser phaser = new Phaser(3); new Thread(phaser::arriveAndAwaitAdvance).start(); TimeUnit.SECONDS.sleep(3); System.out.println(phaser.isTerminated()); phaser.forceTermination(); System.out.println(phaser.isTerminated()); }
4.3 Executor框架
4.3.1 Executor
-
Executor:顶层接口
- Executor ---> ExecutorService ---> AbstractExecutorService ---> ThreadPoolExecutor/ForkJoinPool
ThreadPoolExecutor构造方法的参数
- corePoolSize:线程池中核心线程数的最大值
- maximunPoolSize:线程池中能拥有的最多线程
keepAliveTime:表示空闲线程的存活时间
- 如果没有空闲的线程执行该任务,且当前运行的线程数小于corePoolSize,则添加新的线程执行该任务
- 如果没有空闲的线程执行该任务,且当前的线程数等于corePoolSize,同时阻塞队列未满,则将任务加入队列,而不添加新的线程
- 如果没有空闲的线程执行该任务,且当前阻塞队列已满,同时线程数小于maximunPoolSize,则创建新的线程执行任务
- 如果没有空闲的线程执行该任务,且当前阻塞队列已满,同时线程池中的线程数等于maximunPoolSize,则根据构造函数中的handler指定的策略来拒绝新的任务
- timeUnit:表示KeepAliveTime的单位
- blockingQueue:用于缓存任务的阻塞队列
- ThreadFactory:指定创建线程的工厂
- handler:表示当workQueue已满,且线程池中的线程数达到maximunPoolSize时,线程池拒绝新任务时采取的策略
public static void main(String[] args){ ThreadPoolExecutor pool = (ThreadPoolExecutor)buildThreadPooExecutor(); int activeCount = -1; int queueSize = -1; while(true){ if(activeCount != pool.getActiveCount() || queueSize != pool.getQueue().size()){ System.out.println(pool.getActiveCount()); System.out.println(pool.getQueue().size()); System.out.println(pool.getCorePoolSize()); System.out.println(pool.getMaximunPoolSize); } } } private static ExecutorService buildThreadPoolExecutor(){ ExecutorService pool = new ThreadPoolExecutor(1,2,30, TimeUnit.SECONDS,new ArrayBlockingQueue<>(1),r -> { Thread t = new Thread(r); return t; },ThreadPoolExecutor.AbortPolicy()); System.out.println("The ThreadPoolExecutor create Done"); pool.execute(() -> sleepSeconds(100)); pool.execute(() -> sleepSeconds(10)); pool.execute(() -> sleepSeconds(10)); return pool; } private static void sleepSeconds(long seconds){ try{ System.out.println(Thread.currentThread().getName() +"working..."); TimeUnit.SECONDS.sleep(seconds); }catch(InterruptedException e){ e.printStackTrace(); } }
ThreadPoolExecutor的关闭
线程池开启后执行完任务是不会自动结束的
shutdown():将线程池状态置为SHUTDOWN状态,但并不会立即停止
- 停止接收外部submit的任务
- 线程池内部正在跑的任务和队列里等待的任务,会继续执行完
- 待第二步完成后,才真正停止
shutdownNow():将线程池状态设置为STOP,企图立即停止,但事实上不一定
- 跟shutdown()方法一样,先停止接收外部提交的任务
- 忽略队列中等待的任务
- 调用interrupt()方法尝试中断正在执行的任务
- 待以上任务全部结束后,才真正停止,并返回执行的任务列表
- 它试图终止线程的方法是通过调用Thread,interrupt()方法来实现的,但是大家都知道,这种方法的作用有限,如果线程中没有sleep,wait,Condition,定时锁等应用,interrupt()方法是无法终止当前的线程的.
awaitTermination(long timeout,TimeUnit unit):当前线程阻塞,直到
- 等待所有已提交的任务(包括正在跑和队列中等待的)执行完
- 或者等待超时时间到
- 或者线程被中断,抛出interruptedException
- 返回ture(shutdown请求后所有任务执行完毕)或false(已超时)
- 其中awaitTermination()方法是阻塞的,但不关闭添加新任务的通道,而shutdown是非阻塞的,但是会关闭添加新任务的通道,所以两者一般一起使用,来阻塞主线程达到所有子线程结束后再执行之后的操作
但是如果线程中没有阻塞,那么interrupt()也不能中断线程的流程,所以这时候可以使用在构造方法的ThreadFactory对象中将线程设置为Daemon线程
- 其中ThreadFactory中的r即为submit()方法中注入的callable接口实现类
public static void main(String[] args){ ExecutorService pool = new ThreadPoolExecutor(10,20,30, TimeUnit.SECONDS,new ArrayBlockingQueue<>(10),r -> { Thread t = new Thread(r); t.setDaemon(true); return t; },ThreadPoolExecutor.AbortPolicy()); IntStream.range(0,10).boxed().forEach(i -> pool.submit(() -> { while(true){ //empty... } })); pool.shutdown(); pool.awaitTermination(5,TimeUnit.SECONDS); System.out.println("finished...") }
4.3.2 Executors
- 线程池的工厂类
- 此类相当于线程池的工厂类,通过此类根据提供不同的构造方法参数来构造的ThreadPoolExecutor不同的实例对象
newCacheThreadPool
官方注释:此线程池对于执行一些生命周期很短的异步任务效率很高,不适合执行长时间任务
public static Executor newCacheThreadPool(){ reuturn new ThreadPoolExecutor(0,Integer.MAX_VALUE, 60L,TimeUnit.SECONDS,new SynchronousQueue<Runnable>()); }
- 此线程中核心线程数为0,最大线程数为整数最大值
- 此线程池中的队列workQueue是同步队列,只能存放一个元素
- 此线程池最大空闲存活时间为60s
- 此线程在任务结束后,超过空闲时间就会回收所有的线程,然后自动关闭线程池
- 所以此线程一般不会触发拒绝策略,但是会大量的创建和销毁线程
public static void main(String[] args){ ExecutorService executorService = Executors.newCacheThreadPool(); System.out.println((ThreadPoolExecutor)executorService.getActiveCount()); IntStream,range(0,100).boxed().forEach(i -> executorService.execute(() -> { try{ TimeUnit.SECODS.sleep(10); }catch(InterruptedException e){ e.printStackTrace(); } })); System.out.println((ThreadPoolExecutor)executorService.getActiveCount()); }
- 此线程池执行100个线程的任务时,若任务时间比较长,那么就会创建因为核心线程数为0,而workQueue只为1,所以会一直创建新的线程来执行任务,直到超过最大空闲时间会将所有超过核心线程数的线程全部回收,而核心线程数为0,所以所有创建的线程都会被回收,并且会自动的关闭线程池----所以会大量的创建和销毁线程
newFixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads){ return new ThreadPoolExecutor(nThreads,nThreads,0L,TimeUnit.MILLISSECONDS, new LinkedBlockingQueue<Runnable>()); }
- 此线程池的核心线程数和最大线程数都为构造方法的入参,所以当workQueue满了后再添加任务,就会直接执行拒绝策略
- 此线程池中的队列workQueue的最大值为最大整数
- 此线程的最大等待时间为0,但由于核心线程数等于最大线程数,所以不会销毁线程,并且也不会创建新的线程
- 所以此线程池一般不会触发拒绝策略,并且不会创建和销毁线程
newSingleThreadExecutor
- 此线程池等价于newFixedThreadExecutor(1),且此线程池是ThreadPoolExecutor的封装代理对象,不能用ThreadPoolExecutor来接收,而仅是ExecutorService的实现
此线程池内部封装使用的是ThreadPoolExecutor,但是其只暴露ExecutorService接口的方法
- 此线程池中核心线程数为1,最大线程数为1
- 此线程池中的队列workQueue存放的线程数量为最大整数
- 此线程池的最大等待时间为0,但不会销毁线程,并且也不会创建新的线程,因为核心线程数和最大线程数相等
- 此线程池一般不会触发拒绝策略,并且不会创建和销毁线程
public static ExecutorService newSingleThreadExecutor(){ return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1,1,OL, new LinkedBlockingQueue<Runnable>())); }
单例线程池和一个线程的区别
- 单个线程会在任务完成时被销毁,而单例线程池会一直存活
- 单个线程不能提交runnable到缓存队列,而单例线程池可以
newWorkStealingPool---1.8以后
此线程池实现了ForkJoinPool,而ForkJoinPool实现了AbstractExecutorService
- 此线程池和newCacheThreadPool一样会自动结束,而newFixedThreadPool和newSingleThreadExecutor不会自动结束
- 此线程池根据电脑的核数来创建并行线程
public static ExecutorService newWorkStealingPool(){ return new ForkJoinPool(Runtime.getRuntime().availableProcessors(), ForkJoinPool.defaultForkJoinWorkerThreadFactory,null,true); }
ForkJoin#execute()/submit()
- execute():异步执行task,无返回值
- submit():异步执行,且带Task有返回值,可以通过task.get实现同步到主线程
- invoke(ForkJoinTask):有join,Task会被同步到主线程,一直阻塞到任务执行完成返回
- 当方法参数为Runnnable接口实现时,会将其封装为RunnableExecuteAction extends ForkJoinTask
public static void main(String[] args){ ExecutorService executorService = Executors.newWorkStralingPool(); List<Callable<String>> callableList = IntStream.range(0,20)boxed().map()i -> (Callable<String> () -> { System.out.println(Thread.currentThread().getName() + "working..."); sleep(100); return "Task-" + i; }).collect(Collectors.toList()); executorService.invokeAll(callableList); }
4.3.3 ExecutorService
- 父接口
使用模板设计模式来获取多线程运行过程中的异常
- 通过捕获异常来获取Runnable中的返回信息,因为runnable中是没返回值的
public static void main(String[] args){ ExecutorService executorService = Executors.newFixedThreadPool(10,new MyThreadFactory()); IntStream.rangeClosed(1,10).forEach(i -> executorService.execute(new MyTask(i){ @Override public void init(){ System.out.println(i + ":do some init..."); } @Override public void done(){ System.out.println(i + ":do some works..."); } @Override public void error(Throwable e){ System.out.println(i + ":error..."); } })); } ------------------------------------------------------------- public class MyThreadFactory implement ThreadFatory{ private final static AtomicInteger i = new AtomicInteger(); @Override public Thread newThread(Runnable r){ Thread t = new Thread(r); t.setName("My-Thread" + i); t.setUncaughtExceptionHandler((t,cause) -> { System.out.println(t.getName() + "execute failed..."); cause.printStackTrace(); }); return t; } } ------------------------------------------------------------- public abstract class MyTask implement Runnable{ private final int i; private MyTask(int i){ this.i = i; } @Override public void run(){ try{ this.init(); this.execute(); }catch(InterruptedException e){ this.error(e); } } protected abstract void init(); protected abstract void execute(); protected abstract void error(Throwable e); }
ExecutorService的API
ThreadPoolExecutor的拒绝策略
- AbortPolicy():对拒绝任务做抛弃处理,并抛出异常
- DiscardPolicy():对拒绝任务做直接抛弃处理,没有异常信息---不建议使用
- CallerRunsPolicy():直接在execute()方法的调用线程中运行被拒绝的任务,如果执行程序已关闭,则会丢弃任务
- DiscardOldestPolicy():当任务被拒绝添加时,会抛弃任务队列中醉旧的任务也就是最先加入到队列中的任务,再把这个新任务添加进去
线程回收:对于newFixedThreadPool和newSingleThreadPool是不会被回收的,因为core线程都大于0,如果要想线程池执行完任务后自动回收,就要设置两个属性
- setKeepAliveTime(10):设置其最大空闲时间不为0
- allowCoreThreadTimeOut(true):设置允许核心线程回收
清除队列中的任务
remove(Runnable r)
public static void main(String[] args){ ThreadPoolExecutor pool = (ThreadPoolExecutor) Executors.newFixedThreadPool(10); pool.setKeepAliveTime(10,TimeUnit.SECONDS); pool.setCoreThreadTimeOut(ture); IntStream.rangeClosed(1,2).boxed().forEach(i -> pool.execute(() -> { try{ TimeUnit.SECONDS.sleep(5); System.out.println("finish"); }catch(InterruptedException e){ e.printStackTrace(); } })); TimeUnit.MILLISECONDS.sleep(20); Runnable r = () -> { System.out.println("new task"); }; pool.execute(r); TimeUnit.MILLISECONDS.sleep(20); pool.remove(r); }
预先创建线程
在没添加任务时,core线程为0,但是没有任务也可以预先创建线程,但是只能创建一个预先线程,只有添加任务才能预先创建下一个
- preStartCoreThread():预先创建一个线程
- getActiveCount():获取正在执行的线程数
- preStartAllCoreThread():预先创建core数的线程数,但是实际上只能是task + 1
invoke方法---阻塞方法
invokeAny(Collection<T> collection,TimeUnit unit):批量执行任务获取其中最快的一个结果,当设置超时时间时,若超时时间到就会停止线程,并抛出异常,返回值为T
- 当其中某一个线程执行完成就会得到返回值,其余的线程将不会继续执行
- invokeAll(Collection<T> collection,TimeUnit unit):批量执行线程任务获取结果集,当设置超时时间时,若超时时间到就会停止线程,并抛出异常,返回值为List<Future<T>>
submit(Runnable/Callable r):参数是Runnable的submit()方法是没有返回值的,只能当做阻塞来使用,返回值future.get()需要在线程任务完成后才能向下执行
public static void main(String[] args){ ExecutorService pool = Executors.newFixedThreadPool(10); Future<?> future = pool.submit(() -> { try{ TimeUnit.SECONDS.sleep(5); }catch(InterruptedException e){ e.printStackTrace(); } }); Object NULL = future.get(); System.out.println(NULL); }
submit()和execute()的区别
- submit()和execute()都属于线程池的方法,execute只能提交Runnable类型的任务,而submit既能提交Runnable类型的任务也能提交Callable类型的任务
- execute()会直接抛出任务执行的异常,而submit()会吃掉异常,可以通过Future的get方法将任务执行时的异常重新抛出
execute()所属顶层接口是Executor,submit所属顶层接口是ExecutorService,实现类ThreadPoolExecutor重写了execute方法,抽象类AbstractExecutorService重写了submit方法
<T> Future<T> submit(Callable<T> task); <T> Future<T> submit(Runnable task,T result); Future<?> submit(Runnable task); void execute(Runnable command);
4.3.4 ScheduledExecutorService
- 定时器线程池
Scheduler Solution:定时任务的方法
- Timer/TimerTask
- Scheduler Executor Service
- crontab
- cron4j
- quartz
Timer
TimeTask就是Runnable接口的一个实现类
- 缺点:若run()方法执行时长为10s,而设定的间隔为1s,那么最后间隔为 10s
public static void main(String[] args){ Timer timer = new Timer(); final TimerTask task = new TimerTask(){ @Override public void run(){ System.out.println(System.currentTimeMillis()); } }; timer.schedule(task,1000,1000); }
crontab linux
- 能保证间隔性
quartz
public static void main(String[] args){ JobDetail job = JobBuilder.newJob(SimpleJob.class) .withIdentity("job1","group1").build(); Trigger trigger = TriggerBuilder.newTrigger() .withIdentity("trigger1","group1").withSchedule("0/5 * * * * ?").build(); Schedule schedule = new StdSchedulerFactory().getScheduler(); scheduler.start(); scheduler.schedule(job,trigger); } -------------------------------------------------------------- public class SimpleJob implement Job{ @Override public void execute(JobExecutionContext context){ System.out.println(System.currentTimeMillis()); } }
ScheduledThreadPoolExecutor
- ExecutorService ---> ScheduledExecutorService/ThreadPoolExecutor ---> ScheduledThreadPoolExecutor
构造方法
public ScheduledThreadPoolExecutor(int corePoolSize){ super(corePoolSize,Integer.MAX_VALUE,0,NAOSECONDS,new DelayedWorkQueue()); }
ScheduledExecutorService接口API
- ScheduledFuture<?> schedule(Runnable var1,long var2,TimeUnit var3):延迟执行一个runnable接口任务,返回值Future可以用来cancel任务
- <V> ScheduledFuture<V> schedule(Runnable var1,long var2,TimeUnit var3):延迟执行一个callable接口任务,返回值Future可以用来获取任务结果
- ScheduledFuture<?> scheduleAtFixedRate(Runnable var1,long initialDelay,long period,TimeUnit var4):延迟initialDelay,按周期period执行任务,如果执行任务的时间time大于period,那么周期则为time
- ScheduledFuture<?> scheduleWithFixedDelay(Runnable var1,long initialDelay,longdelay,TimeUnit var4):延迟initialDelay,如果执行任务的时间time,按周期(period+time)执行任务
public static void main(String[] args){ ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(2); ScheduledFuture<?> future = executor.scheduleAtFixedRate(() -> System.out.println("i am running"),1,2,TimeUnit.SECONDS); }
4.3.5 Future和Callable
-
结果集接口和有返回值的任务接口---Future模式
- Future模式就是有返回值的多线程任务
Future的理念:执行某个工作能快速的返回,能够去执行其他的任务,然后等工作完成后再去拿结果,其中Future是由ThreadPoolExecutor#submit(Callable c)来获取的
- 其中Future#get()方法是一个blocked方法,会一直阻塞到任务结束得到返回值
案例一:开启一个线程打断main线程中的future#get()方法的阻塞,抛出interrupt异常
public static void main(String[] args){ ExecutorService executorService = Executors.newFixedThreadPool(2); Future<Integer> future = executorService.submit(() -> { try{ TimeUnit.SECONDS.sleep(10); } catch(InterruptedException e){ e.printStackTrace(); } return 10; }); Thread mainThread = Thread.currentThread(); newThread(() -> { try{ TimeUnit.SECONDS.sleep(3); }catch(InterruptedException e){ e.printStackTrace(); } mainThread.interrupt(); }).start(); Integer result = future.get(); System.out.println("result:" + result); }
案例二:设置超时的future#get()方法,main线程被打断,但是其他任务线程仍然继续执行
- get()方法打断的是main线程,而执行任务的多线程仍然会继续执行
public static void main(String[] args){ ExecutorService executorService = Executors.newCacheThreadPool(); Future<Integer> future = executorService.submit(() -> { try{ TimeUnit.SECONDS.sleep(10); }catch(InterruptedException e){ e.printStackTrace(); } return 10; }); Integer result = future.get(5,TimeUnit.SECONDS); System.out.println(result); }
isDone():线程任务执行结束的标志:
- 当任务执行完成
- 抛出异常
- 任务被cancel
public static void main(String[] args){ ExecutorService executorService = Executors.newCacheThreadPool(); Future<Integer> future = executorService.submit(() -> { throw new RunTimeExection(); }); try{ integer result = future.get(); System.out.println(result); }catch(Exception e){ System.out.println("is done:" future.isDone()); } }
cancel():cancel不能在任务完成后或已经cancel后成功
- cancel()后任务仍然继续执行
如果有很长的任务需要结束
- 设置interrupted标志,使用cancel来interrupted线程
public static void main(String[] args){ ExecutorService executorService = Executors.newCacheThreadPool(); Future<Integer> future = executorService.submit(() -> { while(!Thread.interrupted()){ //empty... } System.out.println("cancel..."); return 10; }); TimeUnit.MILLISECODNS.sleep(10); System.out.println(future.cancel(true));//true System.out.println(future.isDone());//true System.out.println(future.isCancelled());//true }
若没有interrupted判别式,自定义线程工厂来设置线程为Daemon
public static void main(String[] args){ ExecutorService executorService = Executors.newCacheThreadPool(new ThreadFactory(){ @Override public Thread newThread(Runnable r){ Thread t = new Thread(r); t.setDaemon(true); return t; } }); Future<Integer> future = executorService.submit(() -> { while(true){ //empty... } return 10; }); }
4.3.6 CompletionService
- 解决Future结果集等待的缺陷----ExecutorService的包装类
在Future中获取返回结果,future#get()方法是阻塞的,所以虽然future可以不用管任务线程的执行去执行其他任务,但是如果要去获取任务的返回结果,使用future#get()方法时,那么还是会阻塞调用get()方法的线程
Future的缺点
- get()方法会进行阻塞
- 当执行任务集合时,获取的Future集合是无序的,在获取返回结果时,可能第一个获取的任务结果Future时间是最长的,那么只能等待第一个最长时间任务结束,才能获取接下来其他时间较短的任务结果,而不能做到立即获取集合中最先返回的结果
public static void main(String[] args){ ExecutorService executorService = Executors.newFixedThreadPool(2); final List<Callable<Integer>> tasks = Arrays.asList( () -> { try{ TimeUnit.SECONDS.sleep(10); System.out.println("10 is finished..."); }catch(InterruptedException e){ e.printStackTrace(); } return 10; }, () -> { try{ TimeUnit.SECONDS.sleep(20); System.out.println("20 is finished..."); }catch(InterruptedException e){ e.printStackTrace(); } return 20; } ); List<Future<Integer>> futures = executorService.invokeAll(tasks); Integer v20 = future.get(1).get(); System.out.println(v20);//花费20s Integer v10 = future.get(0).get(); System.out.println(v10);//花费20s }
CompletionService是任务完成了才放入queue中,CompletionService是ExecutorService的包装类
- 当有任务完成时,就获取已完成的任务结果
public static void main(String[] args){ ExecutorService executorService = Executors.newFixedThreadPool(2); List<Callable<Integer>> tasks = Arrays.asList( () -> { try{ TimeUnit.SECONDS.sleep(10); System.out.println("10 is finished..."); }catch(InterruptedException e){ e.printStackTrace(); } return 10 }, () -> { try{ TimeUnit.SECONDS.sleep(20); System.out.println("20 is finished..."); }catch(InterruptedException e){ e.printStackTrace(); } return 20; } ); ExecutorCompletionService<Integer> completionService = new ExecutorCompletionService<>(executorService); tasks.stream().forEach(task -> completionService.submit(task)); Future<Integer> future; while((future = completionService.take()) != null){ System.out.println(future.get()); } }
案例一:对比案例
- 使用ThreadPoolExecutor中invokeAll运行多个任务来获取Future的结果
public static void main(String[] args){ final ExecutorService executorService = Executors.newFixedThreadPool(5); List<Runnable> tasks = IntStream.rangeClosed(1,5).boxed().map(i -> (Runnable)() -> { try{ TimeUnit.SECONDS.sleep(i * 5 + 10); System.out.println(i + ":working..."); }catch(InterruptedException e){ e.pritnStackTrace(); } }).collect(Collectors.toList()); List<Future<?>> futures = new ArrayList<>(); tasks.forEach(r -> futures.add(executorService.submit(r))); futures.get(4).get(); futures.get(3).get(); futures.get(2).get(); futures.get(1).get(); futures.get(0).get(); }
- 使用CompletionService来获取Future中的结果
public static void main(String[] args){ final ExecutorService executorService = Executors.newFixedThreadPool(5); List<Runnable> tasks = IntStream.rangeClosed(1,5).boxed().map(i -> (Runnable)() -> { try{ TimeUnit.SECONDS.sleep(i * 5 + 10); System.out.println(i + ":working..."); }catch(InterruptedException e){ e.printStackTrace(); } }).collect(Collectors.toList()); ExecutorCompletionService completionService = new ExecutorCompletionService(executorService); tasks.forEach(r -> completionService.sumbit(r)); Future<?> future; while((future = completionService.take()) != null){ System.out.println(future.get()); } }
案例二:若在执行任务的过程中,执行到一半想结束,并且想获取到所有未完成的任务
通过shutDownNow()方法来中断线程,并返回队列中所有的任务
- 但是中断时可能有很多任务都在进行中,属于未完成,这些任务也需要返回,所以通过自定义Callable任务.设置一个success字段来记录任务是否完成来筛选未完成的任务
public static void main(String[] args){ final ExecutorService executorService = Executors.newFixedThreadPool(5); List<Callable<Integer>> tasks = IntStream.rangeClosed(1,5) .boxed().map(newTask::new).collect(Collectors.toList()); final ExecutorCompletionService<Integer> completionService = new ExecutorCompletionService<>(executorService); tasks.forEach(c -> completionService.submit(c)); TimeUnit.SECONDS.sleep(10); service.shutDownNow(); tasks.stream().filter(c -> !((MyTask)c).isSuccess()) .forEach(Sysmtem.out::println); } ------------------------------------------------------ public class MyTask implements Callable<Integer>{ private final Integer value; private boolean success = false; MyTask(integer value){ this.value = value; } @Override public Integer call(){ TimeUnit.SECNODS.sleep(value * 5 + 10); System.out.println(value + ":finished..."); success = ture; return value; } public Boolean isSuccess(){ return success; } }
4.3.7 CompletableFuture
-
解决Future结果集等待和get()方法阻塞的两个缺陷,能够注册一个回调,不用主动去获取结果
- Future和ForkJoinPool的结合
CompletableFuture时Future和ExecutorService---ForkJoinPool的结合体,并且会设置为main线程的守护线程
- CompletionService解决了不能根据执行顺序获取返回结果的问题,但是还没有解决get()获取结果阻塞的问题,而CompletableFuture可以解决上述问题,不用主动获取结果,而是注册一个回调,调用结束后主动通知自己,并且不同阶段的任务能够进行级联调用
public static void main(String[] args){ CompletableFuture.runAsync(() -> { try{ TimeUnit.SECONDS.sleep(10); }catch(InterruptedException e){ e.printStackTrace(); } }).whenComplete((v,t) -> System.out.println("Done...")); System.out.println("not blocked..."); Thread.currentThread().join();//CompletableFuture为main线程的守护线程 }
案例一:当有A,B两个任务时
- Future是将所有任务的A任务执行完后,再开始拿着A(get)任务的结果集去执行B(display)任务
public static void main(String[] args){ ExecutorService executorService = Executors.newFixedThreadPool(10); List<Callable<Integer>> tasks = IntStream.rangeClosed(1,10) .boxed().map(i -> (Callable<Integer>)() -> get()).collect(Collectors.toList()); executorService.invokeAll(tasks).stream().map(future -> { try{ return future.get(); }catch(Exception e){ e.printStackTrace(); } }).forEach(i -> display(i)); } public static Integer get(){ int value = ThreadLocalRandom.current().nextInt(10); try{ TimeUnit.SECONDS.sleep(value); }catch(InterrutedException e){ e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "getting..."); return value; } public static void display(Integer value){ int value = ThreadLocalRandom.current().nextInt(10); try{ TimeUnit.SECONDS.sleep(value); }catch(InterruptedException e){ e.printStackTrace(): } System.out.println(data + "displaying..."); }
- 而使用CompletableFuture则是A任务中完成的线程就继续执行B任务,不需要等待所有的A任务全部完成
public static void main(String[] args){ IntStream.range(0,10).boxed() .forEach(i -> CompletableFuture.supplyAsync(() -> get()) .thenAccept(() -> display()) .whenComplete((v,t) -> System.out.println(i + "Done..."))); Thread.currentThread().join(); }
构建方法: 推荐使用静态方法替代new
static CompletableFuture<Void> allOf(CompletableFuture<?>... cfs): Return a new CompletableFuture that is comleted when all of the given CompletableFutures complete; static CompletatbleFuture<Object> anyOf(CompletableFuture<?>... cfs); Return a new CompletableFuture that is completed when any of the given CompletableFutures , with the same result; static <U> CompletableFuture<U> completedFuture(U value); Return a new CompletableFuture that is already completed with the given value; static CompletableFuture<Void> runAsync(Runnable runnable); Return a new CompletableFuture that is asynchronously completed by a task running in the ForkJoinPool.commonPool() after it runs the given action; static CompletableFuture<Void> runAsync(Runnable runnable,Executor executor); Return a new CompletableFuture that is asynchronously completed by a task running in the given executor after it runs the given action; static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier); Return a new CompletableFuture that is asynchronously completed by a task running in the ForkJoinPool.commonPool() with the value obtained by calling the given Supplier; static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier); Return a new CompletableFuture that is asynchronously completed by a task running in the given executor with the value obtained by calling the given Supplier;
completedFuture
public static void main(String[] args){ Future<Void> future = CompletableFuture .completedFuture("Hello").thenAccept(System.out::println); System.out.println(future.isDone()); Thread.currentThread().join(); }
runAsync
public static void main(String[] args){ Future<?> future = CompletableFuture.runAsync(() -> { try{ TimeUnit.SECONDS.sleep(5); System.out.println("runAsync..."); }catch(InterruptedException e){ e.printStackTrace(); } }).whenComplete((v,t) -> System.out.println("=====over======")); System.out.println(future.isDone()); Thread.currentThread().join(); }
supplyAsync
public static void main(String[] args){ Future<Object> future = CompletableFuture.supplyAsync(Object::new) .thenAcceptAsync(obj -> { try{ TimeUnit.SECONDS.sleep(5); System.out.println("supplyAsync...1"); }catch(InterruptedException e){ e.printStackTrace(); } }).runAfterBoth(CompletableFuture.supplyAsync(() -> "Hello") .thenAcceptAsync(s -> { try{ TimeUnit.SECONDS.sleep(5); System.out.printlb("supplyAsync...2"); }catch(InterruptedException e){ e.printStackTrace(); } }),() -> System.out.println("===over===")); System.out.println(future.isDone()); Thread.currentThread().join(); }
- whenCompleted(BiConsumer<? super T, ? super Throwable> action)方法为注册回调方法
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。