首页 文章 精选 留言 我的

精选列表

搜索[并发编程],共10009篇文章
优秀的个人博客,低调大师

并发编程-CompletableFuture解析 | 京东物流技术团队

1、CompletableFuture介绍 CompletableFuture对象是JDK1.8版本新引入的类,这个类实现了两个接口,一个是Future接口,一个是CompletionStage接口。 CompletionStage接口是JDK1.8版本提供的接口,用于异步执行中的阶段处理,CompletionStage定义了一组接口用于在一个阶段执行结束之后,要么继续执行下一个阶段,要么对结果进行转换产生新的结果等,一般来说要执行下一个阶段都需要上一个阶段正常完成,这个类也提供了对异常结果的处理接口 2、CompletableFuture的API 2.1 提交任务 在CompletableFuture中提交任务有以下几种方式: public static CompletableFuture<Void> runAsync(Runnable runnable) public static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor) public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier) public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor) 这四个方法都是用来提交任务的,不同的是supplyAsync提交的任务有返回值,runAsync提交的任务没有返回值。两个接口都有一个重载的方法,第二个入参为指定的线程池,如果不指定,则默认使用ForkJoinPool.commonPool()线程池。在使用的过程中尽量根据不同的业务来指定不同的线程池,方便对不同线程池进行监控,同时避免业务共用线程池相互影响。 2.2 结果转换 2.2.1 thenApply public <U> CompletableFuture<U> thenApply(Function<? super T,? extends U> fn) public <U> CompletableFuture<U> thenApplyAsync(Function<? super T,? extends U> fn) public <U> CompletableFuture<U> thenApplyAsync(Function<? super T,? extends U> fn, Executor executor) thenApply这一组函数入参是Function,意思是将上一个CompletableFuture执行结果作为入参,再次进行转换或者计算,重新返回一个新的值。 2.2.2 handle public <U> CompletableFuture<U> handle(BiFunction<? super T, Throwable, ? extends U> fn) public <U> CompletableFuture<U> handleAsync(BiFunction<? super T, Throwable, ? extends U> fn) public <U> CompletableFuture<U> handleAsync(BiFunction<? super T, Throwable, ? extends U> fn, Executor executor) handle这一组函数入参是BiFunction,该函数式接口有两个入参一个返回值,意思是处理上一个CompletableFuture的处理结果,同时如果有异常,需要手动处理异常。 2.2.3 thenRun public CompletableFuture<Void> thenRun(Runnable action) public CompletableFuture<Void> thenRunAsync(Runnable action) public CompletableFuture<Void> thenRunAsync(Runnable action, Executor executor) thenRun这一组函数入参是Runnable函数式接口,该接口无需入参和出参,这一组函数是在上一个CompletableFuture任务执行完成后,在执行另外一个接口,不需要上一个任务的结果,也不需要返回值,只需要在上一个任务执行完成后执行即可。 2.2.4 thenAccept public CompletableFuture<Void> thenAccept(Consumer<? super T> action) public CompletableFuture<Void> thenAcceptAsync(Consumer<? super T> action) public CompletableFuture<Void> thenAcceptAsync(Consumer<? super T> action, Executor executor) thenAccept这一组函数的入参是Consumer,该函数式接口有一个入参,没有返回值,所以这一组接口的意思是处理上一个CompletableFuture的处理结果,但是不返回结果。 2.2.5 thenAcceptBoth public <U> CompletableFuture<Void> thenAcceptBoth(CompletionStage<? extends U> other, BiConsumer<? super T, ? super U> action) public <U> CompletableFuture<Void> thenAcceptBothAsync(CompletionStage<? extends U> other, BiConsumer<? super T, ? super U> action) public <U> CompletableFuture<Void> thenAcceptBothAsync(CompletionStage<? extends U> other, BiConsumer<? super T, ? super U> action, Executor executor) thenAcceptBoth这一组函数入参包括CompletionStage以及BiConsumer,CompletionStage是JDK1.8新增的接口,在JDK中只有一个实现类:CompletableFuture,所以第一个入参就是CompletableFuture,这一组函数是用来接受两个CompletableFuture的返回值,并将其组合到一起。BiConsumer这个函数式接口有两个入参,并且没有返回值,BiConsumer的第一个入参就是调用方CompletableFuture的执行结果,第二个入参就是thenAcceptBoth接口入参的CompletableFuture的执行结果。所以这一组函数意思是将两个CompletableFuture执行结果合并到一起。 2.2.6 thenCombine public <U,V> CompletableFuture<V> thenCombine(CompletionStage<? extends U> other, BiFunction<? super T,? super U,? extends V> fn) public <U,V> CompletableFuture<V> thenCombineAsync(CompletionStage<? extends U> other, BiFunction<? super T,? super U,? extends V> fn) public <U,V> CompletableFuture<V> thenCombineAsync(CompletionStage<? extends U> other, BiFunction<? super T,? super U,? extends V> fn, Executor executor) thenCombine这一组函数和thenAcceptBoth类似,入参都包含一个CompletionStage,也就是CompletableFuture对象,意思也是组合两个CompletableFuture的执行结果,不同的是thenCombine的第二个入参为BiFunction,该函数式接口有两个入参,同时有一个返回值。所以与thenAcceptBoth不同的是,thenCombine将两个任务结果合并后会返回一个全新的值作为出参。 2.2.7 thenCompose public <U> CompletableFuture<U> thenCompose(Function<? super T, ? extends CompletionStage<U>> fn) public <U> CompletableFuture<U> thenComposeAsync(Function<? super T, ? extends CompletionStage<U>> fn) public <U> CompletableFuture<U> thenComposeAsync(Function<? super T, ? extends CompletionStage<U>> fn, Executor executor) thenCompose这一组函数意思是将调用方的执行结果作为Function函数的入参,同时返回一个新的CompletableFuture对象。 2.3 回调方法 public CompletableFuture<T> whenComplete(BiConsumer<? super T, ? super Throwable> action) public CompletableFuture<T> whenCompleteAsync(BiConsumer<? super T, ? super Throwable> action) public CompletableFuture<T> whenCompleteAsync(BiConsumer<? super T, ? super Throwable> action, Executor executor) whenComplete方法意思是当上一个CompletableFuture对象任务执行完成后执行该方法。BiConsumer函数式接口有两个入参没有返回值,这两个入参第一个是CompletableFuture任务的执行结果,第二个是异常信息。表示处理上一个任务的结果,如果有异常,则需要手动处理异常,与handle方法的区别在于,handle方法的BiFunction是有返回值的,而BiConsumer是没有返回值的。 以上方法都有一个带有Async的方法,带有Async的方法表示是异步执行的,会将该任务放到线程池中执行,同时该方法会有一个重载的方法,最后一个参数为Executor,表示异步执行可以指定线程池执行。为了方便进行控制,最好在使用CompletableFuture时手动指定我们的线程池。 2.4 异常处理 public CompletableFuture<T> exceptionally(Function<Throwable, ? extends T> fn) exceptionally是用来处理异常的,当任务抛出异常后,可以通过exceptionally来进行处理,也可以选择使用handle来进行处理,不过两者有些不同,hand是用来处理上一个任务的结果,如果有异常情况,就处理异常。而exceptionally可以放在CompletableFuture处理的最后,作为兜底逻辑来处理未知异常。 2.5 获取结果 public static CompletableFuture<Void> allOf(CompletableFuture<?>... cfs) public static CompletableFuture<Object> anyOf(CompletableFuture<?>... cfs) allOf是需要入参中所有的CompletableFuture任务执行完成,才会进行下一步; anyOf是入参中任何一个CompletableFuture任务执行完成都可以执行下一步。 public T get() throws InterruptedException, ExecutionException public T get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException public T getNow(T valueIfAbsent) public T join() get方法一个是不带超时时间的,一个是带有超时时间的。 getNow方法则是立即返回结果,如果还没有结果,则返回默认值,也就是该方法的入参。 join方法是不带超时时间的等待任务完成。 3、CompletableFuture原理 join方法同样表示获取结果,但是join与get方法有什么区别呢。 public T join() { Object r; return reportJoin((r = result) == null ? waitingGet(false) : r); } public T get() throws InterruptedException, ExecutionException { Object r; return reportGet((r = result) == null ? waitingGet(true) : r); } public T get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { Object r; long nanos = unit.toNanos(timeout); return reportGet((r = result) == null ? timedGet(nanos) : r); } public T getNow(T valueIfAbsent) { Object r; return ((r = result) == null) ? valueIfAbsent : reportJoin(r); } 以上是CompletableFuture类中两个方法的代码,可以看到两个方法几乎一样。区别在于reportJoin/reportGet,waitingGet方法是一致的,只不过参数不一样,我们在看下reportGet与reportJoin方法。 private static <T> T reportGet(Object r) throws InterruptedException, ExecutionException { if (r == null) // by convention below, null means interrupted throw new InterruptedException(); if (r instanceof AltResult) { Throwable x, cause; if ((x = ((AltResult)r).ex) == null) return null; if (x instanceof CancellationException) throw (CancellationException)x; if ((x instanceof CompletionException) && (cause = x.getCause()) != null) x = cause; throw new ExecutionException(x); } @SuppressWarnings("unchecked") T t = (T) r; return t; } private static <T> T reportJoin(Object r) { if (r instanceof AltResult) { Throwable x; if ((x = ((AltResult)r).ex) == null) return null; if (x instanceof CancellationException) throw (CancellationException)x; if (x instanceof CompletionException) throw (CompletionException)x; throw new CompletionException(x); } @SuppressWarnings("unchecked") T t = (T) r; return t; } 可以看到这两个方法很相近,reportGet方法判断了r对象是否为空,并抛出了中断异常,而reportJoin方法没有判断,同时reportJoin抛出的都是运行时异常,所以join方法也是无需手动捕获异常的。 我们在看下waitingGet方法 private Object waitingGet(boolean interruptible) { Signaller q = null; boolean queued = false; int spins = -1; Object r; while ((r = result) == null) { if (spins < 0) spins = SPINS; else if (spins > 0) { if (ThreadLocalRandom.nextSecondarySeed() >= 0) --spins; } else if (q == null) q = new Signaller(interruptible, 0L, 0L); else if (!queued) queued = tryPushStack(q); else if (interruptible && q.interruptControl < 0) { q.thread = null; cleanStack(); return null; } else if (q.thread != null && result == null) { try { ForkJoinPool.managedBlock(q); } catch (InterruptedException ie) { q.interruptControl = -1; } } } if (q != null) { q.thread = null; if (q.interruptControl < 0) { if (interruptible) r = null; // report interruption else Thread.currentThread().interrupt(); } } postComplete(); return r; } 该waitingGet方法是通过while的方式循环判断是否任务已经完成并产生结果,如果结果为空,则会一直在这里循环,这里需要注意的是在这里初始化了一下spins=-1,当第一次进入while循环的时候,spins是-1,这时会将spins赋值为一个常量,该常量为SPINS。 private static final int SPINS = (Runtime.getRuntime().availableProcessors() > 1 ? 1 << 8 : 0); 这里判断可用CPU数是否大于1,如果大于1,则该常量为 1<< 8,也就是256,否则该常量为0。 第二次进入while循环的时候,spins是256大于0,这里做了减一的操作,下次进入while循环,如果还没有结果,依然是大于0继续做减一的操作,此处用来做短时间的自旋等待结果,只有当spins等于0,后续会进入正常流程判断。 我们在看下timedGet方法的源码 private Object timedGet(long nanos) throws TimeoutException { if (Thread.interrupted()) return null; if (nanos <= 0L) throw new TimeoutException(); long d = System.nanoTime() + nanos; Signaller q = new Signaller(true, nanos, d == 0L ? 1L : d); // avoid 0 boolean queued = false; Object r; // We intentionally don't spin here (as waitingGet does) because // the call to nanoTime() above acts much like a spin. while ((r = result) == null) { if (!queued) queued = tryPushStack(q); else if (q.interruptControl < 0 || q.nanos <= 0L) { q.thread = null; cleanStack(); if (q.interruptControl < 0) return null; throw new TimeoutException(); } else if (q.thread != null && result == null) { try { ForkJoinPool.managedBlock(q); } catch (InterruptedException ie) { q.interruptControl = -1; } } } if (q.interruptControl < 0) r = null; q.thread = null; postComplete(); return r; } timedGet方法依然是通过while循环的方式来判断是否已经完成,timedGet方法入参为一个纳秒值,并通过该值计算出一个deadline截止时间,当while循环还未获取到任务结果且已经达到截止时间,则抛出一个TimeoutException异常。 4、CompletableFuture实现多线程任务 这里我们通过CompletableFuture来实现一个多线程处理异步任务的例子。 这里我们创建10个任务提交到我们指定的线程池中执行,并等待这10个任务全部执行完毕。 每个任务的执行流程为第一次先执行加法,第二次执行乘法,如果发生异常则返回默认值,当10个任务执行完成后依次打印每个任务的结果。 public void demo() throws InterruptedException, ExecutionException, TimeoutException { // 1、自定义线程池 ExecutorService executorService = new ThreadPoolExecutor(5, 10, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(100)); // 2、集合保存future对象 List<CompletableFuture<Integer>> futures = new ArrayList<>(10); for (int i = 0; i < 10; i++) { int finalI = i; CompletableFuture<Integer> future = CompletableFuture // 提交任务到指定线程池 .supplyAsync(() -> this.addValue(finalI), executorService) // 第一个任务执行结果在此处进行处理 .thenApplyAsync(k -> this.plusValue(finalI, k), executorService) // 任务执行异常时处理异常并返回默认值 .exceptionally(e -> this.defaultValue(finalI, e)); // future对象添加到集合中 futures.add(future); } // 3、等待所有任务执行完成,此处最好加超时时间 CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).get(5, TimeUnit.MINUTES); for (CompletableFuture<Integer> future : futures) { Integer num = future.get(); System.out.println("任务执行结果为:" + num); } System.out.println("任务全部执行完成!"); } private Integer addValue(Integer index) { System.out.println("第" + index + "个任务第一次执行"); if (index == 3) { int value = index / 0; } return index + 3; } private Integer plusValue(Integer index, Integer num) { System.out.println("第" + index + "个任务第二次执行,上次执行结果:" + num); return num * 10; } private Integer defaultValue(Integer index, Throwable e) { System.out.println("第" + index + "个任务执行异常!" + e.getMessage()); e.printStackTrace(); return 10; } 作者:京东物流 丁冬 来源:京东云开发者社区 自猿其说Tech

优秀的个人博客,低调大师

Java 并发编程:Callable+Future+FutureTask详解

Runnable其中Runnable应该是我们最熟悉的接口,它只有一个run()函数,用于将耗时操作写在其中,该函数没有返回值。然后使用某个线程去执行该runnable即可实现多线程,Thread类在调用start()函数后就是执行的是Runnable的run()函数。Runnable的声明如下 : publicinterfaceRunnable{/**@seejava.lang.Thread#run()*/publicabstractvoidrun(); } CallableCallable与Runnable的功能大致相似,Callable中有一个call()函数,但是call()函数有返回值,而Runnable的run()函数不能将结果返回给客户程序。Callable的声明如下 : publicinterfaceCallable{/***Computesaresult,orthrowsanexceptionifunabletodoso. * *@returncomputedresult *@throwsExceptionifunabletocomputearesult*/Vcall()throwsException; } FutureExecutor就是Runnable和Callable的调度容器,Future就是对于具体的Runnable或者Callable任务的执行结果进行 取消、查询是否完成、获取结果、设置结果操作。get方法会阻塞,直到任务返回结果(Future简介)。Future声明如下 : publicinterfaceFuture{/***Attemptstocancelexecutionofthistask.Thisattemptwill *failifthetaskhasalreadycompleted,hasalreadybeencancelled, *orcouldnotbecancelledforsomeotherreason.Ifsuccessful, *andthistaskhasnotstartedwhencanceliscalled, *thistaskshouldneverrun.Ifthetaskhasalreadystarted, *thenthemayInterruptIfRunningparameterdetermines *whetherthethreadexecutingthistaskshouldbeinterruptedin *anattempttostopthetask.*/booleancancel(booleanmayInterruptIfRunning);/***Returnstrueifthistaskwascancelledbeforeitcompleted *normally.*/booleanisCancelled();/***Returnstrueifthistaskcompleted. **/booleanisDone();/***Waitsifnecessaryforthecomputationtocomplete,andthen *retrievesitsresult. * *@returnthecomputedresult*/Vget()throwsInterruptedException,ExecutionException;/***Waitsifnecessaryforatmostthegiventimeforthecomputation *tocomplete,andthenretrievesitsresult,ifavailable. * *@paramtimeoutthemaximumtimetowait *@paramunitthetimeunitofthetimeoutargument *@returnthecomputedresult*/Vget(longtimeout,TimeUnitunit)throwsInterruptedException,ExecutionException,TimeoutException; } FutureTask FutureTask是一个RunnableFuture public class FutureTaskRunnableFuture实现了Runnbale又实现了Futrue publicinterfaceRunnableFutureextendsRunnable,Future{/***SetsthisFuturetotheresultofitscomputation *unlessithasbeencancelled.*/voidrun(); } 另外FutureTaslk还可以包装Runnable和Callable,由构造函数注入依赖。publicFutureTask(Callablecallable){if(callable==null)thrownewNullPointerException();this.callable=callable;this.state=NEW;//ensurevisibilityofcallable}publicFutureTask(Runnablerunnable,Vresult){this.callable=Executors.callable(runnable,result);this.state=NEW;//ensurevisibilityofcallable} 上面代码块可以看出:Runnable注入会被Executors.callable()函数转换为Callable类型,即FutureTask最终都是执行Callable类型的任务。该适配函数的实现如下 : publicstaticCallablecallable(Runnabletask,Tresult){if(task==null)thrownewNullPointerException();returnnewRunnableAdapter(task,result); } RunnableAdapter适配器 /** *Acallablethatrunsgiventaskandreturnsgivenresult*/staticfinalclassRunnableAdapterimplementsCallable{finalRunnabletask;finalTresult; RunnableAdapter(Runnabletask,Tresult){this.task=task;this.result=result; }publicTcall(){ task.run();returnresult; } } FutureTask实现Runnable,所以能通过Thread包装执行, FutureTask实现Runnable,所以能通过提交给ExcecuteService来执行 注:ExecuteService:创建线程池实例对象,其中有submit(Runnable)、submit(Callable)方法 还可以直接通过get()函数获取执行结果,该函数会阻塞,直到结果返回。 因此FutureTask是Future也是Runnable,又是包装了的Callable( 如果是Runnable最终也会被转换为Callable )。 Callable和Future接口的区别 1.Callable规定的方法是call(),而Runnable规定的方法是run().2.Callable的任务执行后可返回值,而Runnable的任务是不能返回值的。3.call()方法可抛出异常,而run()方法是不能抛出异常的。4.运行Callable任务可拿到一个Future对象,Future表示异步计算的结果。5.它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。6.通过Future对象可了解任务执行情况,可取消任务的执行,还可获取任务执行的结果。7.Callable是类似于Runnable的接口,实现Callable接口的类和实现Runnable的类都是可被其它线程执行的任务。 示例: packagecom.xzf.callable;importjava.util.concurrent.Callable;importjava.util.concurrent.ExecutorService;importjava.util.concurrent.Executors;importjava.util.concurrent.Future;importjava.util.concurrent.FutureTask;publicclassRunnableFutureTask{staticExecutorServiceexecutorService=Executors.newSingleThreadExecutor();//创建一个单线程执行器publicstaticvoidmain(String[]args){ runnableDemo(); futureDemo(); }/***newThread(Runnablearg0).start();用Thread()方法开启一个新线程 *runnable,无返回值*/staticvoidrunnableDemo(){newThread(newRunnable(){ publicvoidrun(){ System.out.println("runnabledemo:"+fibc(20));//有值} }).start(); }/***Runnable实现的是voidrun()方法,无返回值 *Callable实现的是Vcall()方法,并且可以返回执行结果 *Runnable可以提交给Thread,在包装下直接启动一个线程来执行 *Callable一般都是提交给ExecuteService来执行*/staticvoidfutureDemo(){try{ Futureresult1=executorService.submit(newRunnable(){publicvoidrun(){ fibc(20); } }); System.out.println("futureresultfromrunnable:"+result1.get());//run()无返回值所以为空,result1.get()方法会阻塞Futureresult2=executorService.submit(newCallable(){publicIntegercall()throwsException{returnfibc(20); } }); System.out.println("futureresultfromcallable:"+result2.get());//call()有返回值,result2.get()方法会阻塞FutureTaskresult3=newFutureTask(newCallable(){publicIntegercall()throwsException{returnfibc(20); } }); executorService.submit(result3); System.out.println("futureresultfromFutureTask:"+result3.get());//call()有返回值,result3.get()方法会阻塞/*因为FutureTask实现了Runnable,因此它既可以通过Thread包装来直接执行,也可以提交给ExecuteService来执行*/FutureTaskresult4=newFutureTask(newRunnable(){publicvoidrun(){ fibc(20); } },fibc(20)); executorService.submit(result4); System.out.println("futureresultfromexecuteServiceFutureTask:"+result4.get());//call()有返回值,result3.get()方法会阻塞//这里解释一下什么FutureTask实现了Runnable结果不为null,这就用到FutureTask对Runnable的包装,所以Runnable注入会被Executors.callable()函数转换成Callable类型FutureTaskresult5=newFutureTask(newRunnable(){publicvoidrun(){ fibc(20); } },fibc(20));newThread(result5).start(); System.out.println("futureresultfromThreadFutureTask:"+result5.get());//call()有返回值,result5.get()方法会阻塞}catch(Exceptione){ e.printStackTrace(); }finally{ executorService.shutdown(); } }staticintfibc(intnum){if(num==0){return0; }if(num==1){return1; }returnfibc(num-1)+fibc(num-2); } }

优秀的个人博客,低调大师

Java并发编程之线程安全、线程通信

Java多线程开发中最重要的一点就是线程安全的实现了。所谓Java线程安全,可以简单理解为当多个线程访问同一个共享资源时产生的数据不一致问题。为此,Java提供了一系列方法来解决线程安全问题。 synchronized synchronized用于同步多线程对共享资源的访问,在实现中分为同步代码块和同步方法两种。 同步代码块 1 public class DrawThread extends Thread { 2 3 private Account account; 4 private double drawAmount; 5 public DrawThread(String name, Account account, double drawAmount) { 6 super(name); 7 this.account = account; 8 this.drawAmount = drawAmount; 9 } 10 @Override 11 public void run() { 12 //使用account作为同步代码块的锁对象 13 synchronized(account) { 14 if (account.getBalance() >= drawAmount) { 15 System.out.println(getName() + "取款成功, 取出:" + drawAmount); 16 try { 17 TimeUnit.MILLISECONDS.sleep(1); 18 } catch (InterruptedException e) { 19 e.printStackTrace(); 20 } 21 account.setBalance(account.getBalance() - drawAmount); 22 System.out.println("余额为: " + account.getBalance()); 23 } else { 24 System.out.println(getName() + "取款失败!余额不足!"); 25 } 26 } 27 } 28 } 同步方法 使用同步方法,即使用synchronized关键字修饰类的实例方法或类方法,可以实现线程安全类,即该类在多线程访问中,可以保证可变成员的数据一致性。 同步方法中,隐式的锁对象由锁的是实例方法还是类方法确定,分别为该类对象或类的Class对象。 1 public class SyncAccount { 2 private String accountNo; 3 private double balance; 4 //省略构造器、getter setter方法 5 //在一个简单的账户取款例子中, 通过添加synchronized的draw方法, 把Account类变为一个线程安全类 6 public synchronized void draw(double drawAmount) { 7 if (balance >= drawAmount) { 8 System.out.println(Thread.currentThread().getName() + "取款成功, 取出:" + drawAmount); 9 try { 10 TimeUnit.MILLISECONDS.sleep(1); 11 } catch (InterruptedException e) { 12 e.printStackTrace(); 13 } 14 balance -= drawAmount; 15 System.out.println("余额为: " + balance); 16 } else { 17 System.out.println(Thread.currentThread().getName() + "取款失败!余额不足!"); 18 } 19 } 20 //省略HashCode和equals方法 21 } 同步锁(Lock、ReentrantLock) Java5新增了两个用于线程同步的接口Lock和ReadWriteLock,并且分别提供了两个实现类ReentrantLock(可重入锁)和ReentrantReadWriteLock(可重入读写锁)。 相比较synchronized,ReentrantLock的一些优势功能: 1. 等待可中断:指持有锁的线程长期不释放锁的时候,正在等待的线程可以选择放弃等待。 2. 公平锁:多个线程等待同一个锁时,必须按照申请锁的时间顺序依次获取。synchronized是非公平锁,ReentrantLock可以通过参数设置为公平锁 3. 多条件锁:ReentrantLock可通过Condition类获取多个条件关联 Java 1.6以后,synchronized性能提升较大,因此一般的开发中依然建议使用语法层面上的synchronized加锁。 Java8新增了更为强大的可重入读写锁StampedLock类。 比较常用的是ReentrantLock类,可以显示地加锁、释放锁。下面使用ReentrantLock重构上面的SyncAccount类。 1 public class RLAccount { 2 //定义锁对象 3 private final ReentrantLock lock = new ReentrantLock(); 4 private String accountNo; 5 private double balance; 6 //省略构造方法和getter setter 7 public void draw(double drawAmount) { 8 //加锁 9 lock.lock(); 10 try { 11 if (balance >= drawAmount) { 12 System.out.println(Thread.currentThread().getName() + "取款成功, 取出:" + drawAmount); 13 try { 14 TimeUnit.MILLISECONDS.sleep(1); 15 } catch (InterruptedException e) { 16 e.printStackTrace(); 17 } 18 balance -= drawAmount; 19 System.out.println("余额为: " + balance); 20 } else { 21 System.out.println(Thread.currentThread().getName() + "取款失败!余额不足!"); 22 } 23 } finally { 24 //通过finally块保证释放锁 25 lock.unlock(); 26 } 27 } 28 } 死锁 当两个线程相互等待地方释放锁的时候,就会产生死锁。关于死锁和线程安全的深入分析,将另文介绍。 线程通信方式之wait、notify、notifyAll Object类提供了三个用于线程通信的方法,分别是wait、notify和notifyAll。这三个方法必须由同步锁对象来调用,具体来说: 1. 同步方法:因为同步方法默认使用所在类的实例作为锁,即this,可以在方法中直接调用。 2. 同步代码块:必须由锁来调用。 wait():导致当前线程等待,直到其它线程调用锁的notify方法或notifyAll方法来唤醒该线程。调用wait的线程会释放锁。 notify():唤醒任意一个在等待的线程 notifyAll():唤醒所有在等待的线程 1 /* 2 * 通过一个生产者-消费者队列来说明线程通信的基本使用方法 3 * 注意: 假如这里的判断条件为if语句,唤醒方法为notify, 那么如果分别有多个线程操作入队\出队, 会导致线程不安全. 4 */ 5 public class EventQueue { 6 7 private final int max; 8 9 static class Event{ 10 11 } 12 //定义一个不可改的链表集合, 作为队列载体 13 private final LinkedList<Event> eventQueue = new LinkedList<>(); 14 15 private final static int DEFAULT_MAX_EVENT = 10; 16 17 public EventQueue(int max) { 18 this.max = max; 19 } 20 21 public EventQueue() { 22 this(DEFAULT_MAX_EVENT); 23 } 24 25 private void console(String message) { 26 System.out.printf("%s:%s\n",Thread.currentThread().getName(), message); 27 } 28 //定义入队方法 29 public void offer(Event event) { 30 //使用链表对象作为锁 31 synchronized(eventQueue) { 32 //在循环中判断如果队列已满, 则调用锁的wait方法, 使线程阻塞 33 while(eventQueue.size() >= max) { 34 try { 35 console(" the queue is full"); 36 eventQueue.wait(); 37 } catch (InterruptedException e) { 38 e.printStackTrace(); 39 } 40 } 41 console(" the new event is submitted"); 42 eventQueue.addLast(event); 43 this.eventQueue.notifyAll(); 44 } 45 } 46 //定义出队方法 47 public Event take() { 48 //使用链表对象作为锁 49 synchronized(eventQueue) { 50 //在循环中判断如果队列已空, 则调用锁的wait方法, 使线程阻塞 51 while(eventQueue.isEmpty()) { 52 try { 53 console(" the queue is empty."); 54 eventQueue.wait(); 55 } catch (InterruptedException e) { 56 e.printStackTrace(); 57 } 58 } 59 Event event = eventQueue.removeFirst(); 60 this.eventQueue.notifyAll(); 61 console(" the event " + event + " is handled/taked."); 62 return event; 63 } 64 } 65 } 线程通信方式之Condition 如果使用的是Lock接口实现类来同步线程,就需要使用Condition类的三个方法实现通信,分别是await、signal和signalAll,使用上与Object类的通信方法基本一致。 1 /* 2 * 使用Lock接口和Condition来实现生产者-消费者队列的通信 3 */ 4 public class ConditionEventQueue { 5 //显示定义Lock对象 6 private final Lock lock = new ReentrantLock(); 7 //通过newCondition方法获取指定Lock对象的Condition实例 8 private final Condition cond = lock.newCondition(); 9 private final int max; 10 static class Event{ } 11 //定义一个不可改的链表集合, 作为队列载体 12 private final LinkedList<Event> eventQueue = new LinkedList<>(); 13 private final static int DEFAULT_MAX_EVENT = 10; 14 public ConditionEventQueue(int max) { 15 this.max = max; 16 } 17 18 public ConditionEventQueue() { 19 this(DEFAULT_MAX_EVENT); 20 } 21 22 private void console(String message) { 23 System.out.printf("%s:%s\n",Thread.currentThread().getName(), message); 24 } 25 //定义入队方法 26 public void offer(Event event) { 27 lock.lock(); 28 try { 29 //在循环中判断如果队列已满, 则调用cond的wait方法, 使线程阻塞 30 while (eventQueue.size() >= max) { 31 try { 32 console(" the queue is full"); 33 cond.await(); 34 } catch (InterruptedException e) { 35 e.printStackTrace(); 36 } 37 } 38 console(" the new event is submitted"); 39 eventQueue.addLast(event); 40 cond.signalAll();; 41 } finally { 42 lock.unlock(); 43 } 44 45 } 46 //定义出队方法 47 public Event take() { 48 lock.lock(); 49 try { 50 //在循环中判断如果队列已空, 则调用cond的wait方法, 使线程阻塞 51 while (eventQueue.isEmpty()) { 52 try { 53 console(" the queue is empty."); 54 cond.wait(); 55 } catch (InterruptedException e) { 56 e.printStackTrace(); 57 } 58 } 59 Event event = eventQueue.removeFirst(); 60 cond.signalAll(); 61 console(" the event " + event + " is handled/taked."); 62 return event; 63 } finally { 64 lock.unlock(); 65 } 66 } 67 } Java 1.5开始就提供了BlockingQueue接口,来实现如上所述的生产者-消费者线程同步工具。具体介绍将另文说明。

资源下载

更多资源
腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册