java并发与异步操作终极总结

目录

1.并发编程三要素

2. 线程的五大状态

3.悲观锁与乐观锁

4.线程之间的协作

valitate 关键字

 定义

 原理

作用

异步计算

Future

Future 接口的局限性

cancel方法

isCancelled

isDone

get()

get(long timeout, TimeUnit unit)

CompletionStage

CompletableFuture使用详解

1、 runAsync 和 supplyAsync方法

2、计算结果完成时的回调方法

3、 thenApply 方法

4、 handle 方法

5、 thenAccept 消费处理结果

6、thenRun 方法

FutureTask

FutureTask与Future使用案例

FutureTask使用案例-异步的方式发送短信

Executor框架

ExecutorService

Executor 和 ExecutorService 和 Executors的区别

ThreadFactory接口

ThreadLocal

Semaphore

简介:

Interrupt与stop

理解

正确停止线程

线程池

线程池的好处

线程池的功能

常用线程池

ExecutorService介绍

ExecutorService的创建

ExecutorService的使用

ExecutorService的执行

ExecutorService的关闭

Java线程池中submit() 和 execute()方法有什么区别?

线程池的种类,区别和使用场景

ForkJoinPool

为什么使用ForkJoinPool

优势

使用

举例

CountDownLatch

CountDownLatch是什么

CountDownLatch如何工作

在实时系统中的使用场景

 CyclicBarrier

CyclicBarrier是什么

CyclicBarrier如何使用

CountDownLatch与CyclicBarrier区别

如何保证线程顺序执行

方法一:通过共享对象锁加上可见变量来实现。

方法二:通过主线程Join()

方法三:通过线程执行时Join()

Lock 与Synchronized的区别

Delay Quene

简介

Delayed

延时队列的实现

1.消息体

2.消费者

3.延时队列

 


 

1.并发编程三要素

原子性原子,即一个不可再被分割的颗粒。在Java中原子性指的是一个或多个操作要么全部执行成功要么全部执行失败。

有序性程序执行的顺序按照代码的先后顺序执行。(处理器可能会对指令进行重排序)

可见性当多个线程访问同一个变量时,如果其中一个线程对其作了修改,其他线程能立即获取到最新的值。

2. 线程的五大状态

创建状态当用 new 操作符创建一个线程的时候

就绪状态调用 start 方法,处于就绪状态的线程并不一定马上就会执行 run 方法,还需要等待CPU的调度

运行状态CPU 开始调度线程,并开始执行 run 方法

阻塞状态线程的执行过程中由于一些原因进入阻塞状态比如:调用 sleep 方法、尝试去得到一个锁等等​​

死亡状态run 方法执行完 或者 执行过程中遇到了一个异常

3.悲观锁与乐观锁

悲观锁:每次操作都会加锁,会造成线程阻塞。

乐观锁:每次操作不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止,不会造成线程阻塞。​

4.线程之间的协作

4.1 wait/notify/notifyAll

均是Object 类的方法需要注意的是:这三个方法都必须在同步的范围内调用​

wait阻塞当前线程,直到 notify 或者 notifyAll 来唤醒​

valitate 关键字

 定义

java编程语言允许线程访问共享变量,为了确保共享变量能被准确和一致的更新,线程应该确保通过排他锁单独获得这个变量。Java语言提供了volatile,在某些情况下比锁更加方便。如果一个字段被声明成volatile,java线程内存模型确保所有线程看到这个变量的值是一致的。

valitate是轻量级的synchronized,不会引起线程上下文的切换和调度,执行开销更小。

 原理

1. 使用volitate修饰的变量在汇编阶段,会多出一条lock前缀指令2. 它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成3. 它会强制将对缓存的修改操作立即写入主存4. 如果是写操作,它会导致其他CPU里缓存了该内存地址的数据无效

作用

内存可见性多线程操作的时候,一个线程修改了一个变量的值 ,其他线程能立即看到修改后的值防止重排序即程序的执行顺序按照代码的顺序执行(处理器为了提高代码的执行效率可能会对代码进行重排序)

异步计算

  1. 所谓异步调用其实就是实现一个可无需等待被调用函数的返回值而让操作继续运行的方法。在 Java 语言中,简单的讲就是另启一个线程来完成调用中的部分计算,使调用继续运行或返回,而不需要等待计算结果。但调用者仍需要取线程的计算结果。

  2. JDK5新增了Future接口,用于描述一个异步计算的结果。虽然 Future 以及相关使用方法提供了异步执行任务的能力,但是对于结果的获取却是很不方便,只能通过阻塞或者轮询的方式得到任务的结果。阻塞的方式显然和我们的异步编程的初衷相违背,轮询的方式又会耗费无谓的 CPU 资源,而且也不能及时地得到计算结果。

Future

Future就是对于具体的Runnable或者Callable任务的执行结果进行取消、查询是否完成、获取结果。必要时可以通过get方法获取执行结果,该方法会阻塞直到任务返回结

Future 接口的局限性

Future接口可以构建异步应用,但依然有其局限性。它很难直接表述多个Future 结果之间的依赖性。实际开发中,我们经常需要达成以下目的:

  1. 将多个异步计算的结果合并成一个

  2. 等待Future集合中的所有任务都完成

  3. Future完成事件(即,任务完成以后触发执行动作)

Future类位于java.util.concurrent包下,它是一个接口


public interface Future {
   boolean cancel(boolean mayInterruptIfRuning);
    boolean isCancelled();
    boolean isDone();
    V get() throws InterruptedException, ExecutionException;
       V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}

在Future接口中声明了5个方法,下面依次解释每个方法的作用:

cancel方法

用来取消任务,如果取消任务成功则返回true,如果取消任务失败则返回false。参数mayInterruptIfRunning表示是否允许取消正在执行却没有执行完毕的任务,如果设置true,则表示可以取消正在执行过程中的任务。如果任务已经完成,则无论mayInterruptIfRunning为true还是false,此方法肯定返回false,即如果取消已经完成的任务会返回false;如果任务正在执行,若mayInterruptIfRunning设置为true,则返回true,若mayInterruptIfRunning设置为false,则返回false;如果任务还没有执行,则无论mayInterruptIfRunning为true还是false,肯定返回true。

isCancelled

方法表示任务是否被取消成功,如果在任务正常完成前被取消成功,则返回 true。

isDone

方法表示任务是否已经完成,若任务完成,则返回true;

get()

方法用来获取执行结果,这个方法会产生阻塞,会一直等到任务执行完毕才返回;

get(long timeout, TimeUnit unit)

用来获取执行结果,如果在指定时间内,还没获取到结果,就直接返回null。

也就是说Future提供了三种功能:

  1. 判断任务是否完成;
  2. 能够中断任务;
  3. 能够获取任务执行结果。

因为Future只是一个接口,所以是无法直接用来创建对象使用的,因此就有了下面的FutureTask。

CompletionStage

  1. CompletionStage代表异步计算过程中的某一个阶段,一个阶段完成以后可能会触发另外一个阶段

  2. 一个阶段的计算执行可以是一个Function,Consumer或者Runnable。比如:stage.thenApply(x -> square(x)).thenAccept(x -> System.out.print(x)).thenRun(() -> System.out.println())

  3. 一个阶段的执行可能是被单个阶段的完成触发,也可能是由多个阶段一起触发

CompletableFuture使用详解

  1. 在Java8中,CompletableFuture提供了非常强大的Future的扩展功能,可以帮助我们简化异步编程的复杂性,并且提供了函数式编程的能力,可以通过回调的方式处理计算结果,也提供了转换和组合 CompletableFuture 的方法。
  2. 它可能代表一个明确完成的Future,也有可能代表一个完成阶段( CompletionStage ),它支持在计算完成以后触发一些函数或执行某些动作。
  3. 它实现了Future和CompletionStage接口

1、 runAsync 和 supplyAsync方法

CompletableFuture 提供了四个静态方法来创建一个异步操作。

public static CompletableFuture runAsync(Runnable runnable)
public static CompletableFuture runAsync(Runnable runnable, Executor executor)
public static  CompletableFuture supplyAsync(Supplier supplier)
public static  CompletableFuture supplyAsync(Supplier supplier, Executor executor)

没有指定Executor的方法会使用ForkJoinPool.commonPool() 作为它的线程池执行异步代码。如果指定线程池,则使用指定的线程池运行。以下所有的方法都类同。

  • runAsync方法不支持返回值。
  • supplyAsync可以支持返回值。

示例

//无返回值
public static void runAsync() throws Exception {
    CompletableFuture future = CompletableFuture.runAsync(() -> {
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
        }
        System.out.println("run end ...");
    });
    future.get();
}
//有返回值
public static void supplyAsync() throws Exception {         
    CompletableFuture future = CompletableFuture.supplyAsync(() -> {
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
        }
        System.out.println("run end ...");
        return System.currentTimeMillis();
    });
    long time = future.get();
    System.out.println("time = "+time);
}

2、计算结果完成时的回调方法

当CompletableFuture的计算结果完成,或者抛出异常的时候,可以执行特定的Action。主要是下面的方法:

public CompletableFuture whenComplete(BiConsumer action)
public CompletableFuture whenCompleteAsync(BiConsumer action)
public CompletableFuture whenCompleteAsync(BiConsumer action, Executor executor)
public CompletableFuture exceptionally(Function fn)

可以看到Action的类型是BiConsumer它可以处理正常的计算结果,或者异常情况。

whenComplete 和 whenCompleteAsync 的区别:
whenComplete:是执行当前任务的线程执行继续执行 whenComplete 的任务。
whenCompleteAsync:是执行把 whenCompleteAsync 这个任务继续提交给线程池来进行执行。

示例

public static void whenComplete() throws Exception {
    CompletableFuture future = CompletableFuture.runAsync(() -> {
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
        }
        if(new Random().nextInt()%2>=0) {
            int i = 12/0;
        }
        System.out.println("run end ...");
    });
    
    future.whenComplete(new BiConsumer() {
        @Override
        public void accept(Void t, Throwable action) {
            System.out.println("执行完成!");
        }
        
    });
    future.exceptionally(new Function() {
        @Override
        public Void apply(Throwable t) {
            System.out.println("执行失败!"+t.getMessage());
            return null;
        }
    });
    
    TimeUnit.SECONDS.sleep(2);
}

3、 thenApply 方法

当一个线程依赖另一个线程时,可以使用 thenApply 方法来把这两个线程串行化。

public  CompletableFuture thenApply(Function fn)
public  CompletableFuture thenApplyAsync(Function fn)
public  CompletableFuture thenApplyAsync(Function fn, Executor executor)

Function
T:上一个任务返回结果的类型
U:当前任务的返回值类型

示例

private static void thenApply() throws Exception {
    CompletableFuture future = CompletableFuture.supplyAsync(new Supplier() {
        @Override
        public Long get() {
            long result = new Random().nextInt(100);
            System.out.println("result1="+result);
            return result;
        }
    }).thenApply(new Function() {
        @Override
        public Long apply(Long t) {
            long result = t*5;
            System.out.println("result2="+result);
            return result;
        }
    });
    
    long result = future.get();
    System.out.println(result);
}

第二个任务依赖第一个任务的结果。

4、 handle 方法

handle 是执行任务完成时对结果的处理。
handle 方法和 thenApply 方法处理方式基本一样。不同的是 handle 是在任务完成后再执行,还可以处理异常的任务。thenApply 只可以执行正常的任务,任务出现异常则不执行 thenApply 方法。

public  CompletionStage handle(BiFunction fn);
public  CompletionStage handleAsync(BiFunction fn);
public  CompletionStage handleAsync(BiFunction fn,Executor executor);

示例

public static void handle() throws Exception{
    CompletableFuture future = CompletableFuture.supplyAsync(new Supplier() {

        @Override
        public Integer get() {
            int i= 10/0;
            return new Random().nextInt(10);
        }
    }).handle(new BiFunction() {
        @Override
        public Integer apply(Integer param, Throwable throwable) {
            int result = -1;
            if(throwable==null){
                result = param * 2;
            }else{
                System.out.println(throwable.getMessage());
            }
            return result;
        }
     });
    System.out.println(future.get());
}

从示例中可以看出,在 handle 中可以根据任务是否有异常来进行做相应的后续处理操作。而 thenApply 方法,如果上个任务出现错误,则不会执行 thenApply 方法。

5、 thenAccept 消费处理结果

接收任务的处理结果,并消费处理,无返回结果。

public CompletionStage thenAccept(Consumer action);
public CompletionStage thenAcceptAsync(Consumer action);
public CompletionStage thenAcceptAsync(Consumer action,Executor executor);

示例

public static void thenAccept() throws Exception{
    CompletableFuture future = CompletableFuture.supplyAsync(new Supplier() {
        @Override
        public Integer get() {
            return new Random().nextInt(10);
        }
    }).thenAccept(integer -> {
        System.out.println(integer);
    });
    future.get();
}

从示例代码中可以看出,该方法只是消费执行完成的任务,并可以根据上面的任务返回的结果进行处理。并没有后续的输错操作。

6、thenRun 方法

跟 thenAccept 方法不一样的是,不关心任务的处理结果。只要上面的任务执行完成,就开始执行 thenAccept 。

public CompletionStage thenRun(Runnable action);
public CompletionStage thenRunAsync(Runnable action);
public CompletionStage thenRunAsync(Runnable action,Executor executor);

示例

public static void thenRun() throws Exception{
    CompletableFuture future = CompletableFuture.supplyAsync(new Supplier() {
        @Override
        public Integer get() {
            return new Random().nextInt(10);
        }
    }).thenRun(() -> {
        System.out.println("thenRun ...");
    });
    future.get();
}

该方法同 thenAccept 方法类似。不同的是上个任务处理完成后,并不会把计算的结果传给 thenRun 方法。只是处理玩任务后,执行 thenAccept 的后续操作。

FutureTask

我们先来看一下FutureTask的实现:

blic class FutureTask implements RunnableFuture {

FutureTask类实现了RunnableFuture接口,我们看一下RunnableFuture接口的实现:

public interface RunnableFuture extends Runnable, Future {
       void run();
}

可以看出RunnableFuture继承了Runnable接口和Future接口,而FutureTask实现了RunnableFuture接口。所以它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值。
FutureTask提供了2个构造器:

public FutureTask(Callable callable) {
public FutureTask(Runnable runnable, V result) {

事实上,FutureTask是Future接口的一个唯一实现类。

FutureTaskFuture使用案例

public class Test {
  public static void main(String[] args) {
    ExecutorService executor = Executors.newCachedThreadPool();
    Task task = new Task();
    Future result = executor.submit(task);
    executor.shutdown();

    try {
      Thread.sleep(1000);
    } catch (InterruptedException e1) {
      e1.printStackTrace();
    }

    System.out.println("主线程在执行任务");

    try {
      System.out.println("task运行结果"+result.get());
    } catch (InterruptedException e) {
      e.printStackTrace();
    } catch (ExecutionException e) {
      e.printStackTrace();
    }
    System.out.println("所有任务执行完毕");
  }
}
class Task implements Callable{
  @Override
  public Integer call() throws Exception {
    System.out.println("子线程在进行计算");
    Thread.sleep(3000);
    int sum = 0;
    for(int i=0;i<100;i++)
      sum += i;
    return sum;
  }
}

FutureTask使用案例-异步的方式发送短信

public class Test {
  public static void main(String[] args) {
   ExecutorService executor = Executors.newCachedThreadPool();
        FutureTask future = new FutureTask(new Callable() {
            public String call() throws Exception { //建议抛出异常
                try {
                    for (int i = 0; i < split.length; i++) {
                        SendMessages.sendMessage(split[i], appName + text);
                    }
                    Thread.sleep(alterTime);
                    return "send info!";
                } catch (Exception e) {
                    throw new Exception("Callable terminated with Exception!"); // call方法可以抛出异常
                }
            }
        });
        executor.execute(future);
        executor.shutdown();
}

Executor框架

叫执行器,用于管理线程,是线程池的顶级接口

ExecutorService

继承自Executor接口

主要方法

void execute(Runnable runnable)执行一个不需要返回值得线程。
Future submit(Callable callable) 执行有返回值的线程。返回Future对象.
Future submit(Runnable runnable) 执行没有返回值的线程并返回Future对象
Future submit(Runnable runnable,T result)执行没有返回值的线程。如果线程执行成功则返回预设的result.
set> invokeAll(Set set);执行一个集合的有返回值的线程。
Set> invokeAll(Set set,Long time,TimeUnit t);在指定的时间内执行集合的方法,如果指定时间内还没有获取结果,那么终止该线程执行。返回的Future对象可通过isDone()方法和isCancel()来判断是执行成功还是被终止了

Executor 和 ExecutorService  Executors的区别

Executors 类提供工厂方法用来创建不同类型的线程池。比如: newSingleThreadExecutor() 创建一个只有一个线程的线程池,newFixedThreadPool(int numOfThreads)来创建固定线程数的线程池,newCachedThreadPool()可以根据需要创建新的线程,但如果已有线程是空闲的会重用已有线程。

java并发与异步操作终极总结_第1张图片

ThreadFactory接口

ThreadFactory翻译过来是线程工厂,顾名思义,就是用来创建线程的,它用到了工厂模式的思想。它通常和线程池一起使用,主要用来控制创建新线程时的一些行为,比如设置线程的优先级,名字等等。它是一个接口,接口中只有一个方法:

Thread newThread(Runnable r);

一般和ExecutorService一起使用

final ThreadFactory threadFactory = new ThreadFactoryBuilder()
      .setNameFormat("Orders-%d")
      .setDaemon(true)
      .build();
final ExecutorService executorService = Executors.newFixedThreadPool(10, threadFactory);

ThreadLocal

核心点:就是一个变量 通过ThreadLocal.get()去在不同的线程中使用,且互不影响。

在高并发场景,如果只考虑线程安全而不考虑延迟性、数据共享的话,那么使用ThreadLocal会是一个非常不错的选择。当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。

理解:

对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,比如定义一个static变量,同步访问,而ThreadLocal采用了“以空间换时间”的方式。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响。

注意点:

也就是说这个类给线程提供了一个本地变量,这个变量是该线程自己拥有的。在该线程存活和ThreadLocal实例能访问的时候,保存了对这个变量副本的引用.当线程消失的时候,所有的本地实例都会被GC。并且建议我们ThreadLocal最好是 private static 修饰的成员

ThreadLocal和Synchonized区别:

都用于解决多线程并发访问。

Synchronized用于线程间的数据共享(使变量或代码块在某一时该只能被一个线程访问),是一种以延长访问时间来换取线程安全性的策略;

而ThreadLocal则用于线程间的数据隔离(为每一个线程都提供了变量的副本),是一种以空间来换取线程安全性的策略

ThreadLocal是如何做到为每一个线程维护变量的副本的呢?

其实实现的思路很简单:在ThreadLocal类中有一个Map,用于存储每一个线程的变量副本,Map中元素的键为线程对象,而值对应线程的变量副本

当然ThreadLocal并不能替代同步机制,两者面向的问题领域不同。同步机制是为了同步多个线程对相同资源的并发访问,是为了多个线程之间进行通信的有效方式;而ThreadLocal是隔离多个线程的数据共享,从根本上就不在多个线程之间共享资源(变量),这样当然不需要对多个线程进行同步了。所以,如果你需要进行多个线程之间进行通信,则使用同步机制;如果需要隔离多个线程之间的共享冲突,可以使用ThreadLocal,这将极大地简化你的程序,使程序更加易读、简洁。

常用方法:

void set(Object value)

设置当前线程的线程局部变量的值。

public Object get()

该方法返回当前线程所对应的线程局部变量。

public void remove()

将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是JDK 5.0新增的方法。需要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。

protected Object initialValue()

返回该线程局部变量的初始值,该方法是一个protected的方法,显然是为了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第1次调用get()或set(Object)时才执行,并且仅执行1次。ThreadLocal中的缺省实现直接返回一个null。

用法:举例

 public class SequenceNumber {
①通过匿名内部类覆盖ThreadLocal的initialValue()方法,指定初始值
    private static ThreadLocal seqNum = new ThreadLocal(){
      public Integer initialValue(){
        return 0;
      }
    };
②获取下一个序列值
    public int getNextNum(){
      seqNum.set(seqNum.get()+1);
      return seqNum.get();
    }
    public static void main(String[] args)
    {
      SequenceNumber sn = new SequenceNumber();
③ 3个线程共享sn,各自产生序列号
      TestClient t1 = new TestClient(sn)
      TestClient t2 = new TestClient(sn);
      TestClient t3 = new TestClient(sn);
      t1.start();
      t2.start();
      t3.start();
    }
    private static class TestClient extends Thread
    {
      private SequenceNumber sn;
      public TestClient(SequenceNumber sn) {
        this.sn = sn
      }
      public void run()
      {
        for (int i = 0; i < 3; i++) {④每个线程打出3个序列值
          System.out.println("thread["+Thread.currentThread().getName()+
         "] sn["+sn.getNextNum()+"]");

        }
}}
}
  

通常我们通过匿名内部类的方式定义ThreadLocal的子类,提供初始的变量值,如例子中①处所示。TestClient线程产生一组序列号,在③处,我们生成3TestClient,它们共享同一个SequenceNumber实例。运行以上代码,在控制台上输出以下的结果:

thread[Thread-2] sn[1]
thread[Thread-0] sn[1]
thread[Thread-1] sn[1]
thread[Thread-2] sn[2]
thread[Thread-0] sn[2]
thread[Thread-1] sn[2]
thread[Thread-2] sn[3]
thread[Thread-0] sn[3]
thread[Thread-1] sn[3]

考察输出的结果信息,我们发现每个线程所产生的序号虽然都共享同一个SequenceNumber实例,但它们并没有发生相互干扰的情况,而是各自产生独立的序列号,这是因为我们通过ThreadLocal为每一个线程提供了单独的副本。

Semaphore

简介:

Semaphore又称信号量,是操作系统中的一个概念,在Java并发编程中,信号量控制的是线程并发的数量。从概念上讲,信号量维护了一个许可集合,如有必要,在许可可用前会阻塞每一个acquire(),然后再获取该许可,每个release() 添加一个许可,从而可能释放一个正在阻塞的获取者。 在线程池内创建线程并运行时,每个线程必须从信号量获取许可,从而保证可以使用该项。该线程结束后,线程返回到池中并将许可返回到该信号量,从而允许其他线程获取该项。注意,调用acquire() 时无法保持同步锁定,因为这会阻止线程返回到池中。

作用:

主要用于一次可以允许多少个线程并发执行

方法

 void acquire()

          从此信号量获取一个许可,在提供一个许可前一直将线程阻塞,否则线程被中断。

 void acquire(int permits)

          从此信号量获取给定数目的许可,在提供这些许可前一直将线程阻塞,或者线程已被中断。

 void acquireUninterruptibly()

          从此信号量中获取许可,在有可用的许可前将其阻塞。

 void acquireUninterruptibly(int permits)

          从此信号量获取给定数目的许可,在提供这些许可前一直将线程阻塞。

 int availablePermits()

          返回此信号量中当前可用的许可数。

 int drainPermits()

          获取并返回立即可用的所有许可。

protected  Collection getQueuedThreads()

          返回一个 collection,包含可能等待获取的线程。

 int getQueueLength()

          返回正在等待获取的线程的估计数目。

 boolean hasQueuedThreads()

          查询是否有线程正在等待获取。

 boolean isFair()

          如果此信号量的公平设置为 true,则返回 true。

protected  void reducePermits(int reduction)

          根据指定的缩减量减小可用许可的数目。

 void release()

          释放一个许可,将其返回给信号量。

 void release(int permits)

          释放给定数目的许可,将其返回到信号量。

 String toString()

          返回标识此信号量的字符串,以及信号量的状态。

 boolean tryAcquire()

          仅在调用时此信号量存在一个可用许可,才从信号量获取许可。

 boolean tryAcquire(int permits)

          仅在调用时此信号量中有给定数目的许可时,才从此信号量中获取这些许可。

 boolean tryAcquire(int permits, long timeout, TimeUnit unit)

          如果在给定的等待时间内此信号量有可用的所有许可,并且当前线程未被中断,则从此信号量获取给定数目的许可。

 boolean tryAcquire(long timeout, TimeUnit unit)

          如果在给定的等待时间内,此信号量有可用的许可并且当前线程未被中断,则从此信号量获取一个许可。

案例

public class Car extends Thread{
  private Driver driver;

  public Car(Driver driver) {
    super();
    this.driver = driver;
  }

  public void run() {
    driver.driveCar();
  }
}


 public class Run {
    public static void main(String[] args) {
      Driver driver = new Driver();
      for (int i = 0; i < 5; i++) {
        (new Car(driver)).start();
      }
    }
  }


public class Driver {
  // 将信号量设为3
  private Semaphore semaphore = new Semaphore(3);

  public void driveCar() {
    try {
      semaphore.acquire();
      System.out.println(Thread.currentThread().getName() + " start at " + System.currentTimeMillis());
      Thread.sleep(1000);
      System.out.println(Thread.currentThread().getName() + " stop at " + System.currentTimeMillis());
      semaphore.release();
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
  }
}

输出:

Thread-0 start at 1482665412515
Thread-3 start at 1482665412517
Thread-1 start at 1482665412517
Thread-3 stop at 1482665413517
Thread-0 stop at 1482665413517
Thread-4 start at 1482665413517
Thread-2 start at 1482665413517
Thread-1 stop at 1482665413518
Thread-4 stop at 1482665414517
Thread-2 stop at 1482665414517

Interruptstop

中断(Interrupt)一个线程意味着在该线程完成任务之前停止其正在进行的一切,有效地中止其当前的操作,Thread.interrupt()方法不会中断一个正在运行的线程(终止阻塞的线程)。这一方法实际上完成的是,在线程受到阻塞时抛出一个中断信号,这样线程就得以退出阻塞的状态。更确切的说,如果线程被Object.wait, Thread.join和Thread.sleep三种方法之一阻塞,那么,它将接收到一个中断异常(InterruptedException),从而提早地终结被阻塞状态。 

首先,忘掉Thread.stop方法。虽然它确实停止了一个正在运行的线程,然而,这种方法是不安全也是不受提倡的,这意味着,在未来的Java版本中,它将不复存在。 

  • isInterrupted(),用来判断当前线程的中断状态(true or false)。
  • interrupted()是个Thread的static方法,用来恢复中断状态,名字起得额

理解

首先说interrupt, 它没有stop那么的粗暴,因为可以用catch捕捉到InterruptedException这个异常

public class interrupt {
    public static void main(String[] args){
      MyThread mythread =new MyThread();
      mythread.start();
      try{
        Thread.sleep(10000);
      }catch(InterruptedException e){
      }
      mythread.interrupt();
      //mythread.flag=false;
   }
  }
  class MyThread extends Thread{
    public boolean flag =true;
    public void run(){
      while(true){
       System.out.println(new Date());
        try{
          sleep(1000);
        }catch(InterruptedException e){
          System.out.println("Oh,no!!");
          return;
        }
      }
  

输出如下:

Thu Apr 03 20:36:11 CST 2014
Thu Apr 03 20:36:12 CST 2014
Thu Apr 03 20:36:13 CST 2014
Thu Apr 03 20:36:14 CST 2014
Thu Apr 03 20:36:15 CST 2014
Thu Apr 03 20:36:16 CST 2014
Thu Apr 03 20:36:17 CST 2014
Thu Apr 03 20:36:18 CST 2014
Thu Apr 03 20:36:19 CST 2014
Thu Apr 03 20:36:20 CST 2014

Oh,no!!

如果使用stop方法,则更加粗暴一些:

Thu Apr 03 20:49:10 CST 2014
Thu Apr 03 20:49:11 CST 2014
Thu Apr 03 20:49:12 CST 2014
Thu Apr 03 20:49:13 CST 2014
Thu Apr 03 20:49:14 CST 2014
Thu Apr 03 20:49:15 CST 2014

因为此时线程直接终止,没有catch异常的机会, 无法对线程结束这一行为作出任何补救动作。

无论是interrupt还是stop都是不安全的做法,因为如果我们在线程进行时打开了某些资源,那么这样粗暴的结束资源将无法正确关闭

正确停止线程

用一个共享变量标志flag来控制线程的结束


public class interrupt {
  public static void main(String[] args){
    MyThread mythread =new MyThread();
    mythread.start();
    try{
      Thread.sleep(10000);
    }catch(InterruptedException e){
    }
    //mythread.stop();
    mythread.flag=false;
  }
}
class MyThread extends Thread{
  public boolean flag =true;
  public void run(){
    while(flag){
      System.out.println(new Date());
      try{
        sleep(1000);
      }catch(InterruptedException e){
        System.out.println("Oh,no!!");
        return;
      }
    }



线程池

线程池的好处

a. 每次new Thread新建对象性能差。
b. 线程缺乏统一管理,可能无限制新建线程,相互之间竞争,及可能占用过多系统资源导致死机或oom。
c. 缺乏更多功能,如定时执行、定期执行、线程中断。
相比new Thread,Java提供的四种线程池的好处在于:
a. 重用存在的线程,减少对象创建、消亡的开销,性能佳。
b. 可有效控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞。
c. 提供定时执行、定期执行、单线程、并发数控制等功能

线程池的功能

第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。 
第二:提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。 
第三:提高线程的可管理性。 

常用线程池

Java通过Executors提供四种线程池,分别为:
newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。

只能活跃60s
newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

使用方法

ExecutorService    ThreadPool = Executors.newCachedThreadPool(); 

          threadPool.execute(th);

th为自己写好的线程,线程的启动在线程池中执行

ExecutorService介绍

ExecutorService是Java中对线程池定义的一个接口,它java.util.concurrent包中,在这个接口中定义了和后台任务执行相关的方法:

ExecutorService的创建

创建一个什么样的ExecutorService的实例(即线程池)需要g根据具体应用场景而定,不过Java给我们提供了一个Executors工厂类,它可以帮助我们很方便的创建各种类型ExecutorService线程池,Executors一共可以创建下面这四类线程池:

1. newCachedThreadPool 创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。

2. newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。

3. newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。

4. newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所

ExecutorService的使用

ExecutorService executorService = Executors.newFixedThreadPool(10);

executorService.execute(new Runnable() {

public void run() {

    System.out.println("Asynchronous task");

});

executorService.shutdown();//关闭ExecutorService

ExecutorService的执行

ExecutorService有如下几个执行方法:

- execute(Runnable)

- submit(Runnable)

- submit(Callable)

- invokeAny(...)

- invokeAll(...)

ExecutorService的关闭

当我们使用完成ExecutorService之后应该关闭它,否则它里面的线程会一直处于运行状态。如果要关闭ExecutorService中执行的线程,我们可以调用ExecutorService.shutdown()方法。在调用shutdown()方法之后,ExecutorService不会立即关闭,但是它不再接收新的任务,直到当前所有线程执行完成才会关闭,所有在shutdown()执行之前提交的任务都会被执行。

如果我们想立即关闭ExecutorService,我们可以调用ExecutorService.shutdownNow()方法。这个动作将跳过所有正在执行的任务和被提交还没有执行的任务。但是它并不对正在执行的任务做任何保证,有可能它们都会停止,也有可能执行完成

Java线程池中submit() 和 execute()方法有什么区别?

两个方法都可以向线程池提交任务,execute()方法的返回类型是void,它定义在Executor接口中, 而submit()方法可以返回持有计算结果的Future对象,它定义在ExecutorService接口中,它扩展了Executor接口,其它线程池类像ThreadPoolExecutor和ScheduledThreadPoolExecutor都有这些方法

线程池的种类,区别和使用场景

newCachedThreadPool

  • 底层:返回ThreadPoolExecutor实例,corePoolSize为0;maximumPoolSize为Integer.MAX_VALUE;keepAliveTime为60L;unit为TimeUnit.SECONDS;workQueue为SynchronousQueue(同步队列)
  • 通俗创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。线程池为无限大,当执行第二个任务时第一个任务已经完成,会复用执行第一个任务的线程,而不用每次新建线程。
  • 适用:执行很多短期异步的小程序或者负载较轻的服务器

newFixedThreadPool

  • 底层:返回ThreadPoolExecutor实例,接收参数为所设定线程数量nThread,corePoolSize为nThread,maximumPoolSize为nThread;keepAliveTime为0L(不限时);unit为:TimeUnit.MILLISECONDS;WorkQueue为:new LinkedBlockingQueue()无解阻塞队列
  • 通俗:创建可容纳固定数量线程的池子,每个线程的存活时间是无限的,当池子满了就不在添加线程了;如果池中的所有线程均在繁忙状态,对于新任务会进入阻塞队列中(无界的阻塞队列)。
  • 适用:执行长期的任务,性能好很多

newSingleThreadExecutor:

  • 底层:FinalizableDelegatedExecutorService包装的ThreadPoolExecutor实例,corePoolSize为1;maximumPoolSize为1;keepAliveTime为0L;unit为:TimeUnit.MILLISECONDS;workQueue为:new LinkedBlockingQueue() 无解阻塞队列
  • 通俗:创建只有一个线程的线程池,且线程的存活时间是无限的;当该线程正繁忙时,对于新任务会进入阻塞队列中(无界的阻塞队列)
  • 适用:一个任务一个任务执行的场景

NewScheduledThreadPool:

  • 底层:创建ScheduledThreadPoolExecutor实例,corePoolSize为传递来的参数,maximumPoolSize为Integer.MAX_VALUE;keepAliveTime为0;unit为:TimeUnit.NANOSECONDS;workQueue为:new DelayedWorkQueue() 一个按超时时间升序排序的队列
  • 通俗:创建一个固定大小的线程池,线程池内线程存活时间无限制,线程池可以支持定时及周期性任务执行(比如每个线程每隔多少秒执行一次),如果所有线程均处于繁忙状态,对于新任务会进入DelayedWorkQueue队列中,这是一种按照超时时间排序的队列结构
  • 适用:周期性执行任务的场景

线程池任务执行流程:

  1. 当线程池小于corePoolSize时,新提交任务将创建一个新线程执行任务,即使此时线程池中存在空闲线程。
  2. 当线程池达到corePoolSize时,新提交任务将被放入workQueue中,等待线程池中任务调度执行
  3. 当workQueue已满,且maximumPoolSize>corePoolSize时,新提交任务会创建新线程执行任务
  4. 当提交任务数超过maximumPoolSize时,新提交任务由RejectedExecutionHandler处理
  5. 当线程池中超过corePoolSize线程,空闲时间达到keepAliveTime时,关闭空闲线程
  6. 当设置allowCoreThreadTimeOut(true)时,线程池中corePoolSize线程空闲时间达到keepAliveTime也将关闭

ForkJoinPool

为什么使用ForkJoinPool

               ThreadPoolExecutor中每个任务都是由单个线程独立处理的,如果出现一个非常耗时的大任务(比如大数组排序),就可能出现线程池中只有一个线程在处理这个大任务,而其他线程却空闲着,这会导致CPU负载不均衡:空闲的处理器无法帮助工作繁忙的处理器。ForkJoinPool就是用来解决这种问题的:将一个大任务拆分成多个小任务后,使用fork可以将小任务分发给其他线程同时处理,使用join可以将多个线程处理的结果进行汇总;这实际上就是分治思想的并行版本

优势

            ForkJoinPool的优势在于,可以充分利用多cpu,多核cpu的优势,把一个任务拆分成多个“小任务”,把多个“小任务”放到多个处理器核心上并行执行;当多个“小任务”执行完成之后,再将这些执行结果合并起来即可。这种思想值得学习。

使用

Java7 提供了ForkJoinPool来支持将一个任务拆分成多个“小任务”并行计算,再把多个“小任务”的结果合并成总的计算结果。

ForkJoinPool是ExecutorService的实现类,因此是一种特殊的线程池。

使用方法:创建了ForkJoinPool实例之后,就可以调用ForkJoinPool的submit(ForkJoinTask task) 或invoke(ForkJoinTask task)方法来执行指定任务了。其中ForkJoinTask代表一个可以并行、合并的任务。ForkJoinTask是一个抽象类,它还有两个抽象子类:RecusiveAction和RecusiveTask。其中RecusiveTask代表有返回值的任务,而RecusiveAction代表没有返回值的任务。

下面的UML类图显示了ForkJoinPool、ForkJoinTask之间的关系:

java并发与异步操作终极总结_第2张图片

举例

以还行没有返回值的“大任务”(简单低打印1~300的数值)为例,程序将一个“大任务”拆分成多个“小任务”,并将任务交给ForkJoinPool来执行

public class ForkJoinPoolAction {
    public static void main(String[] args) throws Exception{
        PrintTask task = new PrintTask(0, 300);
        //创建实例,并执行分割任务
        ForkJoinPool pool = new ForkJoinPool();
        pool.submit(task);
         //线程阻塞,等待所有任务完成
        pool.awaitTermination(2, TimeUnit.SECONDS);
        pool.shutdown();
    }
}

/**
 * ClassName: PrintTask 
* Function: 继承RecursiveAction来实现“可分解”的任务。 * date: 2017年12月4日 下午5:17:41
* * @author prd-lxw * @version 1.0 * @since JDK 1.7 */ class PrintTask extends RecursiveAction{ private static final int THRESHOLD = 50; //最多只能打印50个数 private int start; private int end; public PrintTask(int start, int end) { super(); this.start = start; this.end = end; } @Override protected void compute() { if(end - start < THRESHOLD){ for(int i=start;i

 

yryr

ForkJoinPool-1-worker-1的i值:262
ForkJoinPool-1-worker-7的i值:75
ForkJoinPool-1-worker-7的i值:76
ForkJoinPool-1-worker-5的i值:225
ForkJoinPool-1-worker-3的i值:187
ForkJoinPool-1-worker-6的i值:150
ForkJoinPool-1-worker-6的i值:151
ForkJoinPool-1-worker-6的i值:152
ForkJoinPool-1-worker-6的i值:153
ForkJoinPool-1-worker-6的i值:154
......

CountDownLatch

CountDownLatch是什么

CountDownLatch是在java1.5被引入的,跟它一起被引入的并发工具类还有CyclicBarrier、Semaphore、ConcurrentHashMap和BlockingQueue,它们都存在于java.util.concurrent包下。CountDownLatch这个类能够使一个线程等待其他线程完成各自的工作后再执行。例如,应用程序的主线程希望在负责启动框架服务的线程已经启动所有的框架服务之后再执行。

CountDownLatch是通过一个计数器来实现的,计数器的初始值为线程的数量。每当一个线程完成了自己的任务后,计数器的值就会减1。当计数器值到达0时,它表示所有的线程已经完成了任务,然后在闭锁上等待的线程就可以恢复执行任务。

CountDownLatch如何工作

CountDownLatch.java类中定义的构造函数:

count.public void CountDownLatch(int count) {...}

与CountDownLatch的第一次交互是主线程等待其他线程。主线程必须在启动其他线程后立即调用CountDownLatch.await()方法。这样主线程的操作就会在这个方法上阻塞,直到其他线程完成各自的任务。构造器中的计数值(count)实际上就是闭锁需要等待的线程数量。这个值只能被设置一次,而且CountDownLatch没有提供任何机制去重新设置这个计数值

其他N 个线程必须引用闭锁对象,因为他们需要通知CountDownLatch对象,他们已经完成了各自的任务。这种通知机制是通过 CountDownLatch.countDown()方法来完成的;每调用一次这个方法,在构造函数中初始化的count值就减1。所以当N个线程都调 用了这个方法,count的值等于0,然后主线程就能通过await()方法,恢复执行自己的任务。

在实时系统中的使用场景

让我们尝试罗列出在java实时系统中CountDownLatch都有哪些使用场景。我所罗列的都是我所能想到的。如果你有别的可能的使用方法,请在留言里列出来,这样会帮助到大家。

实现最大的并行性:有时我们想同时启动多个线程,实现最大程度的并行性。例如,我们想测试一个单例类。如果我们创建一个初始计数为1的CountDownLatch,并让所有线程都在这个锁上等待,那么我们可以很轻松地完成测试。我们只需调用 一次countDown()方法就可以让所有的等待线程同时恢复执行。

开始执行前等待n个线程完成各自任务:例如应用程序启动类要确保在处理用户请求前,所有N个外部系统已经启动和运行了。

3死锁检测:一个非常方便的使用场景是,你可以使用n个线程访问共享资源,在每次测试阶段的线程数目是不同的,并尝试产生死锁。

使用步骤

第一创建 CountDownLatch对象,并设置等待的线程个数

 CountDownLatch  sCountDownLatch = new CountDownLatch(THREAD_NUMBER); 

在每个要执行的线程的run方法中,添加

latch.countDown();

第三步: 在等待线程的前面添加

 sCountDownLatch.await(); 

案例

public static void main(String[] args) {
CountDownLatch latch = new CountDownLatch(3);
   new Thread(new Runnable() {
@Override
public void run() {
 System.out.println("我是线程1");
 latch.countDown();

}
}) {

   }.start();

   new Thread(new Runnable() {

   @Override
   public void run() {
   System.out.println("我是线程2");
   latch.countDown();//不可少

   }
   }) {

   }.start();

   new Thread(new Runnable() {
     @Override
   public void run() {
   System.out.println("我是线程3");
   latch.countDown();

   }
   }) {
   }.start();
   try {

latch.await();
System.out.println("我是主线程");
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();

}

 CyclicBarrier

CyclicBarrier是什么

CyclicBarrier是一个同步的辅助类,可循环利用的屏障允许一组线程相互之间等待,达到一个共同点,再继续执行。可看成是个障碍,所有的线程必须到齐后才能一起通过这个障碍

CyclicBarrier如何使用

介绍CyclicBarrier的两个构造函数:CyclicBarrier(int parties)和CyclicBarrier(int parties, Runnable barrierAction) :前者只需要声明需要拦截的线程数即可,而后者还需要定义一个等待所有线程到达屏障优先执行的Runnable对象。

public class CyclicBarrierDemo {
    private static final ThreadPoolExecutor threadPool=new 
   ThreadPoolExecutor(4,10,60,TimeUnit.SECONDS,new LinkedBlockingQueue());
    //当拦截线程数达到4时,便优先执行barrierAction,然后再执行被拦截的线程。
    private static final CyclicBarrier cb=new CyclicBarrier(4,new Runnable() {
      public void run()
      {
        System.out.println("寝室四兄弟一起出发去球场");
      }
    });
    private static class GoThread extends Thread{
      private final String name;
      public GoThread(String name)
      {
        this.name=name;
      }
      public void run()
      {
        System.out.println(name+"开始从宿舍出发");
        try {
          Thread.sleep(1000);
          cb.await();//拦截线程
          System.out.println(name+"从楼底下出发");
          Thread.sleep(1000);
          System.out.println(name+"到达操场");

        }
        catch(InterruptedException e)
        {
          e.printStackTrace();
        }
        catch(BrokenBarrierException e)
        {
          e.printStackTrace();
        }
      }
    }
    public static void main(String[] args) {
      // TODO Auto-generated method stub
      String[] str= {"李明","王强","刘凯","赵杰"};
      for(int i=0;i<4;i++)
      {
        threadPool.execute(new GoThread(str[i]));
      }
      try
      {
        Thread.sleep(4000);
        System.out.println("四个人一起到达球场,现在开始打球");
      }
      catch(InterruptedException e)
      {
        e.printStackTrace();
      }
    }

结果

李明开始从宿舍出发
赵杰开始从宿舍出发
王强开始从宿舍出发
刘凯开始从宿舍出发
寝室四兄弟一起出发去球场
赵杰从楼底下出发
李明从楼底下出发
刘凯从楼底下出发
王强从楼底下出发
赵杰到达操场
王强到达操场
李明到达操场
刘凯到达操场
四个人一起到达球场,现在开始打球

CountDownLatchCyclicBarrier区别

CyclicBarrier可以将一组线程停留在某一个状态下,而CountDownLatch则不可以。而且CyclicBarrier可以循环使用,CountDownLatch不行。

如何保证线程顺序执行

方法一:通过共享对象锁加上可见变量来实现。

类似下面这种写法

private volatile int orderNum = 1;      
public synchronized void methodA() {  
try {  
while (orderNum != 1) {  
wait();  
}  
for (int i = 0; i < 2; i++) {  
System.out.println("AAAAA");  
}  
orderNum = 2;  
notifyAll();  
} catch (InterruptedException e) {  
e.printStackTrace();  
}  
}  
 public synchronized void methodB() {  
        try {  
            while (orderNum != 2) {  
                wait();  
            }  
            for (int i = 0; i < 2; i++) {  
                System.out.println("BBBBB");  
            }  
            orderNum = 3;  
            notifyAll();  
        } catch (InterruptedException e) {  
            e.printStackTrace();  
        }  
    }  
  
    public synchronized void methodC() {  
        try {  
            while (orderNum != 3) {  
                wait();  
            }  
            for (int i = 0; i < 2; i++) {  
                System.out.println("CCCCC");  
            }  
            orderNum = 1;  
            notifyAll();  
        } catch (InterruptedException e) {  
            e.printStackTrace();  
        }  
    }  

方法二:通过主线程Join()

public class Test2 {  
public static void main(String[] args) throws InterruptedException {  
T11 t1 = new T11();  
T22 t2 = new T22();  
T33 t3 = new T33();  
t1.start();  
t1.join();  
t2.start();  
t2.join();  
t3.start();  
}  
}  

方法三:通过线程执行时Join()

Thread t2=new Thread(new Runnable() {
			public void run() {
				try {
					t1.join();
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				System.out.println("我是线程二");
			}

Lock 与Synchronized的区别

主要目的是和synchronized一样, 两者都是为了解决同步问题,处理资源争端而产生的技术。功能类似但有一些区别。

区别如下:

1、synchronized内置关键字,在JVM层面实现,发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生。Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁

2、Lock具有高级特性:时间锁等候、可中断锁等候、无块结构锁、多个条件变量或者锁投票,可以知道有没有成功获取锁

3、当竞争资源非常激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized

4·lock更灵活,可以自由定义多把锁的枷锁解锁顺序(synchronized要按照先加的后解顺序)

提供多种加锁方案,lock 阻塞式, trylock 无阻塞式, lockInterruptily 可打断式, 还有trylock的带超时时间版本。

本质上和监视器锁(即synchronized是一样的)

结论:建议用 synchronized 开发,直到确实证明 synchronized 不合适,而不要仅仅是假设如果使用 ReentrantLock “性能会更好”。

Delay Quene

简介

是一个无界的BlockingQueue,用于放置实现了Delayed接口的对象,其中的对象只能在其到期时才能从队列中取走。这种队列是有序的,即队头对象的延迟到期时间最长。注意:不能将null元素放置到这种队列中。

Delayed

一种混合风格的接口,用来标记那些应该在给定延迟时间之后执行的对象。此接口的实现必须定义一个 compareTo 方法,该方法提供与此接口的 getDelay 方法一致的排序

延时队列的实现

简单的延时队列要有三部分:第一实现了Delayed接口的消息体第二消费消息的消费者第三存放消息的延时队列,那下面就来看看延时队列demo。

消息体定义 实现Delayed接口就是实现两个方法即compareTo 和 getDelay最重要的就是getDelay方法,这个方法用来判断是否到期

1.消息体

public class Message implements Delayed {  
    private int id;  
    private String body; // 消息内容  
    private long excuteTime;// 延迟时长,这个是必须的属性因为要按照这个判断延时时长。  
  
    public int getId() {  
        return id;  
    }  
  
    public String getBody() {  
        return body;  
    }  
  
    public long getExcuteTime() {  
        return excuteTime;  
    }  
  
    public Message(int id, String body, long delayTime) {  
        this.id = id;  
        this.body = body;  
        this.excuteTime = TimeUnit.NANOSECONDS.convert(delayTime, TimeUnit.MILLISECONDS) + System.nanoTime();  
    }  
  
    // 自定义实现比较方法返回 1 0 -1三个参数  
    @Override  
    public int compareTo(Delayed delayed) {  
        Message msg = (Message) delayed;  
        return Integer.valueOf(this.id) > Integer.valueOf(msg.id) ? 1  
                : (Integer.valueOf(this.id) < Integer.valueOf(msg.id) ? -1 : 0);  
    }  
  
    // 延迟任务是否到时就是按照这个方法判断如果返回的是负数则说明到期否则还没到期  
    @Override  
    public long getDelay(TimeUnit unit) {  
        return unit.convert(this.excuteTime - System.nanoTime(), TimeUnit.NANOSECONDS);  
    }  
}  

2.消费者

public class Consumer implements Runnable {  
    // 延时队列 ,消费者从其中获取消息进行消费  
    private DelayQueue queue;  
  
    public Consumer(DelayQueue queue) {  
        this.queue = queue;  
    }  
    @Override  
    public void run() {  
        while (true) {  
            try {  
                Message take = queue.take();  
                System.out.println("消费消息id:" + take.getId() + " 消息体:" + take.getBody());  
            } catch (InterruptedException e) {  
                e.printStackTrace();  
            }  
        }  
    }  
}  

3.延时队列

public class DelayQueueTest {  
     public static void main(String[] args) {    
            // 创建延时队列    
            DelayQueue queue = new DelayQueue();    
            // 添加延时消息,m1 延时3s    
            Message m1 = new Message(1, "world", 3000);    
            // 添加延时消息,m2 延时10s    
            Message m2 = new Message(2, "hello", 10000);    
            //将延时消息放到延时队列中  
            queue.offer(m2);    
            queue.offer(m1);    
            // 启动消费线程 消费添加到延时队列中的消息,前提是任务到了延期时间   
            ExecutorService exec = Executors.newFixedThreadPool(1);  
            exec.execute(new Consumer(queue));  
            exec.shutdown();  
        }    
}  

将消息体放入延迟队列中,在启动消费者线程去消费延迟队列中的消息,如果延迟队列中的消息到了延迟时间则可以从中取出消息否则无法取出消息也就无法消费,这就是延迟队列demo。

参考文件:https://www.cnblogs.com/shamo89/p/7055039.html

Exchanger

 1、用于实现两个对象之间的数据交换,每个对象在完成一定的事务后想与对方交换数据,第一个先拿出数据的对象将一直等待第二个对象拿着数据

          到来时,彼此才能交换数据。

  2、方法:exchange(V x)

          等待另一个线程到达此交换点(除非当前线程被中断),然后将给定的对象传送给该线程,并接收该线程的对象。

  3、应用:使用 Exchanger 在线程间交换缓冲区

案例


public class ExchangeTest {
	public static void main(String[] args) {
		ExecutorService service =Executors.newCachedThreadPool();
		final Exchanger exchanger = new Exchanger();
		service.execute(new Runnable() {
			@Override
			public void run() {
				try{
					String data1 = "零食";
					System.out.println("线程"+Thread.currentThread().getName()+
							"正在把数据 "+data1+" 换出去");
					Thread.sleep((long)Math.random()*10000);
					String data2 = (String)exchanger.exchange(data1);
					System.out.println("线程 "+Thread.currentThread().getName()+
							"换回的数据为 "+data2);
				}catch(Exception e){
					e.printStackTrace();
				}
				
			}
		});
		
		service.execute(new Runnable() {
			
			@Override
			public void run() {
				try{
					String data1 = "钱";
					System.out.println("线程"+Thread.currentThread().getName()+
							"正在把数据 "+data1+" 交换出去");
					Thread.sleep((long)(Math.random()*10000));
					String data2 =(String)exchanger.exchange(data1);
					System.out.println("线程 "+Thread.currentThread().getName()+
							"交换回来的数据是: "+data2);
				}catch(Exception e){
					e.printStackTrace();
				}
				
				
			}
		});
	}
}

输出结果:

线程pool-1-thread-1正在把数据 零食 换出去
线程pool-1-thread-2正在把数据 钱 交换出去
线程 pool-1-thread-1换回的数据为 钱
线程 pool-1-thread-2交换回来的数据是: 零食

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

你可能感兴趣的:(java知识库)