当前位置: 首页 > news >正文

网站 公众号 建设方案咸阳网站建设培训学校

网站 公众号 建设方案,咸阳网站建设培训学校,crm平台系统,珠海企业营销型网站建设公司Lock J.U.C最核心组件#xff0c;Lock接口出现之前#xff0c;多线程的并发安全只能由synchronized处理#xff0c;但java5之后#xff0c;Lock的出现可以解决synchronized的短板#xff0c;更加灵活。 Lock本质上是一个接口#xff0c;定义了释放锁#xff08;unlockLock接口出现之前多线程的并发安全只能由synchronized处理但java5之后Lock的出现可以解决synchronized的短板更加灵活。 Lock本质上是一个接口定义了释放锁unlock、获得锁lock的抽象方法。 ReentrantLock重入锁 线程获得锁之后再次获得该锁不需要阻塞而是直接关联一次计数器增加重入次数。它是唯一一个实现了Lock接口的类也是互斥锁。 重入锁可以防止死锁就单个线程而言锁未释放时又要重新获取同一把锁。 reentrantLock.lock() --获得锁 reentrantLock.unlock() --释放锁 public class AtomicDemo { private static int count0; static Lock lock new ReentrantLock(); public static void inc(){ lock.lock(); //获得锁try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } count; //原子递增lock.unlock(); //释放锁} public static void main(String[] args) throws InterruptedException { for(int i0;i1000;i){ new Thread(()-{AtomicDemo.inc();}).start();; } Thread.sleep(3000);System.out.println(result:count); } }ReentrantReadWriteLock重入读写锁 ReentrantLock接口的实现类。这个类维护了两个锁ReadLock和WriteLock这两个锁分别实现了Lock接口。 ReadLock、WriteLock之间的基本原则 读读不互斥、读写互斥、写写互斥。 ReentrantReadWriteLock是一个非排他锁允许多个线程同时访问但在写线程访问时会阻塞其他线程。所以比较适用于读多余写的场景。 public class RWLock {static ReentrantReadWriteLock wrlnew ReentrantReadWriteLock();static Lock read wrl.readLock(); // 读锁static Lock write wrl.writeLock(); // 写锁static MapString,Object cacheMapnew HashMap();public static final Object get(String key){System.out.println(begin read data:key);read.lock(); try {return cacheMap.get(key);}finally {read.unlock();}}public static final Object put(String key,Object val){write.lock();try{return cacheMap.put(key,val);}finally {write.unlock();}} }StampedLock JDK8引入的新的锁机制是读写锁的改进版本。StampedLock是一种乐观的读策略不阻塞写线程解决了读写互斥。 通过偏移量stateOffset比较内存地址以乐观锁的方式保证排他性类似数据库乐观锁。 AQSabstractQueuedSynchronizer 它是一个同步队列也是Lock的核心组件。 AQS功能分两种独占、共享 独占互斥:每次只有一个线程持有锁类似ReentrantLock。 共享允许多个线程同时持有锁类似ReentrantReadWriteLock。 AQS内部实现 AQS内部维护的是一个FIFO双向链表线程争抢锁失败后会封装成Node加入到AQS。当获取锁的线程释放锁之后会从队列中唤醒一个阻塞的Node。 head节点表示获取锁成功的节点prev前置节点next后继节点。 添加新节点 1.新节点的prev指向前置节点并将前置节点的next指向自己。 2.通过CAS将tail指向新节点自己 抢锁时节点变化 1.修改head节点指向下一个获得锁的节点。 2.获得锁的节点prev指针指向null。 公平锁与非公平锁 锁的公平性是相对于获得锁的顺序而言的。 公平锁中如果锁被占用则直接入等待队列并按照队列顺序获得锁。 非公平锁中线程先去检查并设置锁状态会直接与队列中的头节点抢锁抢锁失败才会进入等待队列。 设置公平锁和非公平锁 ReentrantLock lock new ReentrantLock(true); // true -- 公平锁 false -- 非公平锁公平锁 公平锁时获得锁的顺序与线程启动顺序一致。非公平锁则不然。 public class MyFairLock {private static ReentrantLock lock new ReentrantLock(true); //公平锁public static void testFail(){try {lock.lock();System.out.println(Thread.currentThread().getName() 获得了锁);} finally {lock.unlock();}}public static void main(String[] args) throws InterruptedException {Runnable runnable () - {System.out.println(Thread.currentThread().getName()启动);testFail();};Thread[] threadArray new Thread[10];for (int i0; i10; i) {new Thread(runnable).start();}}}如何设计锁 锁的互斥性 没有抢占到锁的线程如何处理 公平和非公平 重入锁 Condition 多线程协调通信的工具类让线程一起等待某个条件condition条件满足时唤醒线程。 调用condition之前需要先获得Lock锁。 condition.await() 使当前线程进入等待队列并释放锁释放锁之后如果节点不在AQS队列则阻塞当前线程如果在则CAS自旋等待尝试获取锁。 condition.signal() 唤醒阻塞线程唤醒等待队列中等待时间最长的节点首节点唤醒之前先将节点移到同步队列。 demo public class ConditionWait implements Runnable{private Lock lock;private Condition condition;public ConditionWait(Lock lock, Condition condition) {this.lock lock;this.condition condition;}Overridepublic void run() {try {lock.lock(); //获得锁try {System.out.println(begin - ConditionWait);condition.await();//阻塞(1.释放锁, 2.阻塞当前线程, FIFO单向、双向)System.out.println(end - ConditionWait);} catch (InterruptedException e) {e.printStackTrace();}}finally {lock.unlock();//释放锁}} }public class ConditionNotify implements Runnable {private Lock lock;private Condition condition;public ConditionNotify(Lock lock, Condition condition) {this.lock lock;this.condition condition;}Overridepublic void run() {try {lock.lock(); //获得了锁System.out.println(begin - conditionNotify);condition.signal(); //唤醒阻塞状态的线程System.out.println(end - conditionNotify);} finally {lock.unlock(); //释放锁}} }public class demo{private Lock lock new ReentrantLock();private Condition condition lock.newCondition();public static void main(String[] args){new Thread(new ConditionWait (lock , condition )).start;new Thread(new ConditionNotify (lock , condition )).start;} }CountDownLatch CountDownLatch countDownLatch new CountDownLatch(3); //入参int类型表示计数器的初始值。 countDownLatch.countDown() // 计数器减一 countDownLatch.await() // 阻塞主流程计数器为0时继续执行。 public static void main(String[] args) throws InterruptedException {CountDownLatch countDownLatch new CountDownLatch(3);for (int i 0; i 2; i) {new Thread(()-{countDownLatch.countDown(); //减1}).start();}countDownLatch.await();//阻塞等到countDownLatch计数为0时继续执行 System.out.println(continue);} }利用CountDownLatch实现高并发场景 public class CountDownLatchDemo extends Thread{static CountDownLatch countDownLatchnew CountDownLatch(1);//模拟一个开关public static void main(String[] args) {for(int i0;i1000;i){ //启动1000个线程new CountDownLatchDemo().start();}countDownLatch.countDown(); //所有阻塞线程同时跑run()模拟并发场景}Overridepublic void run() {try {countDownLatch.await(); //阻塞countDown()之后所有线程同时继续执行} catch (InterruptedException e) {e.printStackTrace();}//TODOSystem.out.println(ThreadName:Thread.currentThread().getName());} } Semaphore 控制访问线程个数常用于限流底层实现基于AQS共享锁。 Semaphore 分公平策略和非公平策略类似公平锁和非公平锁 初始化 Semaphore semaphorenew Semaphore(3); 入参int类型表示同时访问的个数限制只有3个令牌。设置给AQS的state。 semaphore.acquire() 如果没有达到上限则获得一个令牌否则阻塞当前线程直到有令牌释放出来并抢到令牌后继续执行。 state state - 1; semaphore.release(); 释放一个令牌。 state state 1; demo public class SemaphoreDemo {static class Car extends Thread{private int num;private Semaphore semaphore;public Car(int num, Semaphore semaphore) {this.num num;this.semaphore semaphore;}public void run(){try {semaphore.acquire(); //获得一个令牌, 如果拿不到令牌则阻塞System.out.println(第num 抢占一个车位);Thread.sleep(2000);System.out.println(第num 开走喽);semaphore.release(); //释放一个令牌其他线程可以开始抢令牌} catch (InterruptedException e) {e.printStackTrace();}}}public static void main(String[] args) {Semaphore semaphorenew Semaphore(3); //同时访问线程数最多3个for(int i0;i10;i){new Car(i,semaphore).start();}} }LockSupport LockSupport.park() LockSupport.unpark(Thread) // 唤醒某个线程 Waite/notify 无法唤醒某个线程。 Cyclicbarrier 设置一个屏障当所有线程都达到这个屏障时屏障才会开门所有被屏障拦截的线程才会继续工作。 Cyclicbarrier 初始化 CyclicBarrier cyclicBarriernew CyclicBarrier(3); CyclicBarrier cyclicBarriernew CyclicBarrier(3,new CycliBarrierDemo()); 入参int类型表示计数器。 入参Runnable的实现类表示所有线程都到达屏障后主线程唤醒阻塞方法之前先开始跑CycliBarrierDemo的run方法然后所有阻塞的线程继续工作。 cyclicBarrier.await() 设置一个屏障线程在此阻塞 可设置入参超时时间。在限定时间内如果没有足够线程到达也解除阻塞继续工作。 demo public class DataImportThread extends Thread{private CyclicBarrier cyclicBarrier;private String path;public DataImportThread(CyclicBarrier cyclicBarrier, String path) {this.cyclicBarrier cyclicBarrier;this.path path;}Overridepublic void run() {System.out.println(开始导入path 数据);//TODOtry {cyclicBarrier.await(); //设置一个屏障线程在此阻塞} catch (InterruptedException e) {e.printStackTrace();} catch (BrokenBarrierException e) {e.printStackTrace();}System.out.println(path 数据继续开始后续处理);} }public class CycliBarrierDemo extends Thread{Overridepublic void run() { //主线程唤醒阻塞方法之前先跑run()System.out.println(所有文件都已导入解除屏障开始后续处理);}public static void main(String[] args) {CyclicBarrier cyclicBarriernew CyclicBarrier(3,new CycliBarrierDemo());new Thread(new DataImportThread(cyclicBarrier,file1)).start();new Thread(new DataImportThread(cyclicBarrier,file2)).start();new Thread(new DataImportThread(cyclicBarrier,file3)).start();} }ConcurrentHashMap J.U.C里提供的线程安全且高效的hashmap主要为了解决HashMap线程不安全和HashTable效率不高的问题。 HashTable之所以效率不高主要是因为使用了synchronized关键字对put等操作加锁synchronized对整个对象加锁也就是说put等操作修改Hash表时锁住了整个Hash表 HashMap线程不安全 会出现值覆盖的情况 final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {NodeK,V[] tab; NodeK,V p; int n, i;if ((tab table) null || (n tab.length) 0)n (tab resize()).length;if ((p tab[i (n - 1) hash]) null)tab[i] newNode(hash, key, value, null);else …… }NodeK,V newNode(int hash, K key, V value, NodeK,V next) {return new Node(hash, key, value, next); }当多个线程同时进行if ((p tab[i (n - 1) hash]) null)的判断并且都进入的if方法newNode方法当中直接返回了对象进行赋值那么就会出现值覆盖的情况。 ConcurrentHashMap线程安全 final V putVal(K key, V value, boolean onlyIfAbsent) {if (key null || value null) throw new NullPointerException();int hash spread(key.hashCode());int binCount 0;for (NodeK,V[] tab table;;) {NodeK,V f; int n, i, fh;if (tab null || (n tab.length) 0)tab initTable();else if ((f tabAt(tab, i (n - 1) hash)) null) {if (casTabAt(tab, i, null,new NodeK,V(hash, key, value, null)))break; // no lock when adding to empty bin} else {……synchronized (f) {} }static final K,V NodeK,V tabAt(NodeK,V[] tab, int i) {return (NodeK,V)U.getObjectVolatile(tab, ((long)i ASHIFT) ABASE); } static final K,V boolean casTabAt(NodeK,V[] tab, int i,NodeK,V c, NodeK,V v) {return U.compareAndSwapObject(tab, ((long)i ASHIFT) ABASE, c, v); }1.for自旋并且table被volatile修饰保证每次自旋的时候可以拿到最新的table。 2.tabAt(tab, i (n - 1) hash) 表示的意思就是tab[i]但需要注意的是table被volatile修饰但并不能保证table内的各个元素是最新的。所以通过tabAt方法基于native方法直接取内存中第i个元素基于底层的总线锁、缓存锁保证table内元素的可见性。 3.casTabAt取通过cas操作实现赋值保证只有一个线程能修改成功其他的修改失败保证了原子性避免线程安全问题。 4.synchronized给node加锁。 阻塞队列BlockingQueue ArrayBlockingQueue 数组实现的有界阻塞队列按照FIFO原则对元素排序。 LinkedBlockingQueue 链表实现的有界阻塞队列次列队的默认和最大长度为Integer.MAX_VALUE按照FIFO原则对元素排序。 PriorityBlockingQueue 支持优先级排序的无界阻塞队列, 默认情况下元素采取自然顺序升序排列。也可自定义类实现compareTo()方法来指定元素排序规则或者初始化 PriorityBlockingQueue 时指定构造参数 Comparator 来对元素进行排序。 DelayQueue 优先级队列实现的无界阻塞队列有延时功能 SynchronousQueue 没有容量不存储元素的阻塞队列, 每一个 put 操作必须等待一个 take 操作元素被消费了才能再添加。 LinkedTransferQueue 链表实现的无界阻塞队列 LinkedBlockingDeque 链表实现的双向阻塞队列 插入操作 add(e) 添加元素如果队列满了则报错IllegalStateException。 offer(e) 添加元素同时返回一个状态如果成功则返回true。 put(e) 添加元素队列满了之后会阻塞生产者线程直到队列可用。 offer(e, time, unit) 添加元素队列满了之后生产者线程被阻塞指定时间如果超时了则该生产者线程直接退出。 移除操作 Remove() 移除元素移除成功则返回true如果队列为空则返回false。 poll(): 当队列中存在元素则从队列中取出一个元素如果队列为空则直接返回 null take() 基于阻塞的方式获取队列中的元素如果队列为空则 take 方法会一直阻塞直到队列中有新的数据可以消费 poll(time,unit) 带超时机制的获取数据如果队列为空则会等待指定的时间再去获取元素返回 ArrayBlockingQueue ArrayBlockingQueue(int capacity) ArrayBlockingQueue(int capacity, boolean fair) ArrayBlockingQueue(int capacity, boolean fair,Collection? extends E c) capacity --列队长度 fair-- 是否为公平阻塞队列默认情况下是非公平的 public ArrayBlockingQueue(int capacity) { this(capacity, false); //默认非公平锁 }public ArrayBlockingQueue(int capacity, boolean fair) {if (capacity 0)throw new IllegalArgumentException();this.items new Object[capacity];lock new ReentrantLock(fair); //重入锁notEmpty lock.newCondition(); //初始化非空等待队列notFull lock.newCondition(); //初始化非满等待队列 }Atomic J.U.C提供了12个原子操作类 原子更新基本类型 AtomicBoolean、AtomicInteger、AtomicLong 原子更新数组 AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray 原子更新引用 AtomicReference 、AtomicReferenceFieldUpdater、AtomicMarkableReference更新带有标记位的引用类 型 原子更新字段 AtomicIntegerFieldUpdater、AtomicLongFieldUpdater、AtomicStampedReference AtomicInteger 内部实现是调用unsafe类 AtomicInteger atomicIntegernew AtomicInteger(0); //初始化atomicInteger0 atomicInteger.get() //取值 atomicInteger.incrementAndGet(); //递增1 AtomicLong AtomicLong atomicLong new AtomicLong(0L); //初始化atomicLong0L long vaule atomicLong.longValue() //取值调用get() atomicLong .addAndGet(1L) //atomicLong1L 注1.8之后建议使用LongAdder,减少乐观锁的重试次数性能更好。 线程池 线程的创建、销毁所花费的时间和系统资源相当大所以就有了线程池的概念。 任务处理完之后线程不会被销毁而是以挂起的状态返回到线程池等待后续任务的分配。 优点 线程复用避免平凡创建、销毁线程带来的性能开销。 控制资源数量避免出现资源瓶颈。 ThreadpoolExecutor public ThreadPoolExecutor(int corePoolSize, // 核心线程数int maximumPoolSize, // 最大线程数long keepAliveTime, // 核心线程以外的线程最大存活时间TimeUnit unit, // 存活时间单位BlockingQueueRunnable workQueue, //保存执行任务的阻塞队列ThreadFactory threadFactory, // 创建新线程使用的工厂RejectedExecutionHandler handler) // 当任务无法执行时的处理方式corePoolSize 线程数达到corePoolSize后后续到达的任务会被放到阻塞队列中 maximumPoolSize 队列满了则创建新线程处理后续任务直到线程数量达到maximumPoolSize。 keepAliveTime 线程空闲时间达到keepAliveTime则终止该线程直到线程数量等于corePoolSize。如果allowCoreThreadTimeout(boolean)设置为true核心线程也会退出直到线程数量为0。 Unit 时间单位TimeUnit.DAYS、HOURS、MINUTES、SECONDS、MILLISECONDS…… BlockingQueue workQueue 阻塞队列用于存储等待执行的任务。 threadFactory 创建线程池的工厂 RejectedExecutionHandler handler 线程丢弃策略 newFixedThreadPool 适用于 负载比较大且为了资源的合理利用需要限制线程数量的场景 public static ExecutorService newFixedThreadPool(int nThreads) {return new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueueRunnable()); }指定corePoolSize和maximumPoolSize且相同。 阻塞队列用的是LinkedBlockingQueue默认容量是Inter.MAX_VALUE可以一直添加任务自然也就不用创建核心线程以外的线程。 newCachedThreadPool 没有核心线程直接向SynchronousQueue中提交任务添加一个、消费一个交替完成由空闲线程执行任务没有空闲线程则新建一个。 public static ExecutorService newCachedThreadPool() {return new ThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS,new SynchronousQueueRunnable()); }空闲线程的生存时间60S。 newSingleThreadExecutor 只创建一个线程保证任务按照顺序执行。 public static ExecutorService newSingleThreadExecutor() {return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueueRunnable())); }newScheduledThreadPool 延期执行 线程池原理分析 源码 public void execute(Runnable command) {if (command null)throw new NullPointerException();int c ctl.get();if (workerCountOf(c) corePoolSize) { // 池中线程数小于corePooolSize新建if (addWorker(command, true)) // 创建核心线程接收任务return;c ctl.get(); }if (isRunning(c) workQueue.offer(command)) { //核心池满、队列未满添加到队列int recheck ctl.get();if (! isRunning(recheck) remove(command))reject(command);else if (workerCountOf(recheck) 0)addWorker(null, false);}else if (!addWorker(command, false)) // 核心池满、队列满、创建新线程reject(command); // 如果新线程创建失败拒绝任务 }常见问题 使用newfixedThreadPool或者singleThreadPool允许的队列长度为Inter.MAX_VALUE如果使用不当会导致队列堆积了大量请求而导致OOM风险。 使用newCachedThreadPool允许的线程数量为Inter.MAX_VALUE可能导致大量线程创建导致CPU使用过高或者OOM。 如何合理配置线程池大小 CPU密集型 主要执行计算任务响应时间快这种任务CPU利用率很高。那么线程数的配置应该根据CPU的核数来决定。如果CPU4核那么最多同时执行4个线程。否则过多的线程会导致上下文切换返回效率降低。 最大线程数可设置为CPU核数1。 IO密集 主要执行IO操作时间较长CPU利用率不高。这种情况下可以结合线程等待时长来判断等待时间越长线程数可设置的越多。 一般最大线程数设置为CPU核数2倍。 公式 线程池设定最佳线程数目 线程池设定的线程等待时间线程 CPU 时间/ 线程 CPU 时间 * CPU 数目 线程池的初始化 默认情况下创建了线程池后线程池中是没有线程的有任务来才会创建线程去执行任务。 prestartCoreThread() //初始化一个核心线程 prestartAllCoreThreads() //初始化全部的核心线程 线程池的关闭 ThreadPoolExecutor提供了两个方法shutdown() 和 shutdownNow()。 shutdown() 不会立即终止线程池等队列中所有任务执行完毕后才终止并不再接收新的任务。 shutdownNow() 立即终止线程池并尝试打断正在执行的任务并清空队列返回尚未执行的任务。 线程池容量的动态调整 ThreadPoolExecutor提供了两个方法setCorePoolSize() 和 setMaximumPoolSize()。 setCorePoolSize() 设置核心池大小 setMaximumPoolSize() 设置最大线程数 线程池任务缓存队列 – workQueue workQueue的类型为BlockingQueue通常取ArrayBlockingQueue、 LinkedBlockingQueue、SynchronousQueue这三种类型 线程池的监控 ThreadPoolExecutor 项目中大规模的使用线程池那必须有一套监控体系。线程池提供了响应的扩展方法通过重写线程池的beforeExecute、afterExecute、shutdown等方法可以实现对线程的监控。 public class Demo1 extends ThreadPoolExecutor {private ConcurrentHashMapString, Date startTimes; //存储任务开始的时间public Demo1( int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueueRunnable workQueue) {super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);this.startTimes new ConcurrentHashMap();}Overridepublic void shutdown() {System.out.println(已经执行的任务数 this.getCompletedTaskCount() ,当前活动线程数: this.getActiveCount() ,当前排队线程数: this.getQueue().size());super.shutdown();}Overrideprotected void beforeExecute(Thread t, Runnable r) { //任务开始之前执行startTimes.put(String.valueOf(r.hashCode()), new Date()); //记录开始时间super.beforeExecute(t, r);}Overrideprotected void afterExecute(Runnable r, Throwable t) {Date startDate startTimes.remove(String.valueOf(r.hashCode()));Date finishDate new Date();long diff finishDate.getTime() - startDate.getTime(); //任务运行用时System.out.print(任务耗时: diff \n);System.out.print(初始线程数: this.getPoolSize() \n);System.out.print(核心线程数: this.getCorePoolSize() \n);System.out.print(正在执行的任务数量: this.getActiveCount() \n);System.out.print(已经执行的任务数: this.getCompletedTaskCount() \n);System.out.print(任务总数: this.getTaskCount() \n);System.out.print(最大允许的线程数: this.getMaximumPoolSize() \n);System.out.print(线程空闲时间: this.getKeepAliveTime(TimeUnit.MILLISECONDS) \n);super.afterExecute(r, t);}public static ExecutorService newCachedThreadPool() {return new Demo1(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue());} }public class Test implements Runnable{private static ExecutorService es Demo1.newCachedThreadPool();Overridepublic void run() {try {Thread.sleep(1000); } catch (InterruptedException e) {e.printStackTrace();} }public static void main(String[] args) {for (int i 0; i 100; i) { es.execute(new Test());}es.shutdown();} }Callable/Future 线程池执行任务有两种方法execute 和 submit。 execute 和 submit区别 execute 只能接受Runnable的参数没有返回值有异常则抛出。 submit可以接受Runnable和Callable两种类型的参数如果传入Callable类型的参数可以得到一个Future返回值。Submit方法不会抛异常Future.get()才会抛异常。 Submit public class FutureDemo implements CallableString {Overridepublic String call() throws Exception {System.out.println(execute:call);Thread.sleep(5000);return Hello Call;}public static void main(String[] args) throws ExecutionException, InterruptedException {FutureDemo futureDemonew FutureDemo(); // FutureTask futurenew FutureTask(futureDemo); // new Thread(future).start();ExecutorService executorService Executors.newFixedThreadPool(3);Future future executorService.submit(futureDemo);System.out.println(future.get()); //阻塞获取结果} }Submit相对于execute而言多做了一步封装了一个RunnableFuture public T FutureT submit(CallableT task) {if (task null) throw new NullPointerException();RunnableFutureT ftask newTaskFor(task);execute(ftask);return ftask; }FutureTask FutureTack是Runnable和Future的结合FutureTack的run方法计算结果FutureTack的get方法获取结果。 public class FutureDemo implements CallableString {Overridepublic String call() throws Exception {System.out.println(execute:call);Thread.sleep(5000);return Hello Call;}public static void main(String[] args) throws ExecutionException, InterruptedException {FutureDemo futureDemonew FutureDemo();FutureTask futurenew FutureTask(futureDemo);new Thread(future).start(); // ExecutorService executorService Executors.newFixedThreadPool(3); // Future future executorService.submit(futureDemo);System.out.println(future.get()); //阻塞获取结果} }
http://www.hkea.cn/news/14274245/

相关文章:

  • 网站建设 问卷调查怎么设计页面
  • 奉贤集团网站建设天眼在线查企业查询
  • 如东网站制作手机怎么制作公众号
  • 教育网站建设规划书社交网站
  • 小众写作网站天津网站页面设计
  • 广州网站建设优化公司哪家好建筑行业征信查询平台
  • php网站开发api做网站数据分析架构
  • 山西网站制作公司哪家好个人网站要不要备案
  • 科技网站建设分析seo技术教学视频
  • 门户网站制作需要多少钱公司没有销售网站怎么做业务
  • 嘉禾手机网站建设宣传片制作合同模板
  • 中国做木线条的网站网站备案代码
  • 为什么要建设档案网站如何将图片生成链接
  • 苏州网站制作好的公司wordpress关闭多站点
  • 养殖网站源码蔬菜网站建设
  • php网站开发外文安装wordpress空白
  • 中国网站开发用盗版犯法网站外链建设的八大基本准则
  • 查询域名是否做过网站西安做网站建设的公司
  • 网站收录了怎么做排名中国八冶建设集团网站
  • 外贸人常用的网站包头网站建设公司
  • 来广营做网站公司商丘网格通
  • 做网站那个搜索引擎好自己做网站和推广
  • 苏州住房建设建局官方网站青海城乡住房和建设厅网站
  • 陶瓷企业 瓷砖地板公司网站建设淘宝联盟怎么自己做网站
  • 门户网站建设公开情况自查做网站提成
  • 重庆市渝兴建设投资有限公司网站北京王府井图片
  • 怎样做淘宝优惠券网站wordpress趣味插件
  • 网站设计为什么要域名河北石家庄旅游网页设计
  • 自己网站怎么做百度推广设计公司品牌策划
  • 做视频网站是什么职业1688做网站费用