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

目录

1.并发编程三要素

2. 线程的五大状态

3.悲观锁与乐观锁

4.线程之间的协作

5.Callable使用

6.valitate 关键字

 定义

 原理

作用

异步计算

AtomicReference

AtomicReference介绍

AtomicReference使用场景

AtomicReference和volatile的区别

多线程访问成员变量与局部变量

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使用案例-异步的方式发送短信

 Callable接口和Runnable接口的区别

Executor框架

ExecutorService

Executor 和 ExecutorService 和 Executors的区别

ThreadFactory接口

ThreadLocal

ThreadLocal使用场景解析

场景1:

场景2:

典型场景1

使用ThreadLocal

典型场景2

InheritableThreadLocal

ExecutorCompletionService      

Semaphore

简介:

Interrupt与stop

理解

正确停止线程

线程池

线程池的好处

线程池的功能

常用线程池

ExecutorService介绍

ExecutorService的创建

ExecutorService的使用

ExecutorService的执行

ExecutorService的关闭

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

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

线程池任务调度

ThreadPoolExecutor

线程池任务缓冲

二、拒绝策略

三、ThreadFactory自定义线程创建

四、ThreadPoolExecutor扩展

五、线程池线程数量

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 来唤醒​

5.Callable使用

Callable和Runnable使用差不多, 但是Callable有返回值, 可以用Future接收. 看代码:

public static void main(String[] args) {
    Callable callable = new Callable() {
        @Override
        public Object call() throws Exception {
            return "this is Callable's message";
        }
    };
    /**
     * 将callable丢进任务里面
     */
    FutureTask futureTask = new FutureTask(callable);

    /**
     * 启动线程, 执行任务
     */
    new Thread(futureTask).start();
    try {
        Thread.sleep(3000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    try {
        System.out.println("执行任务后的结果: " + futureTask.get());
    } catch (ExecutionException e) {
        e.printStackTrace();
    }  catch (InterruptedException e) {
        e.printStackTrace();
    }
}

6.valitate 关键字

 定义

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

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

 原理

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

作用

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

异步计算

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

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

AtomicReference

AtomicReference介绍


①.AtomicReference和AtomicInteger非常类似,不同之处就在于AtomicInteger是对整数的封装,而AtomicReference则对应普通的对象引用(javabean,java集合等)。也就是它可以保证你在修改对象引用时的线程安全性。

②.AtomicReference是作用是对”对象”进行原子操作。 提供了一种读和写都是原子性的对象引用变量。原子意味着多个线程试图改变同一个AtomicReference(例如比较和交换操作)将不会使得AtomicReference处于不一致的状态。

AtomicReference使用场景

     实现对象引用的原子更新。使用场景:一个线程使用student对象,另一个线程负责定时读表,更新这个对象。那么就可以用AtomicReference这个类。

AtomicReference和volatile的区别

                首先volatile是java中关键字用于修饰变量,AtomicReference是并发包java.util.concurrent.atomic下的类。
首先volatile作用,当一个变量被定义为volatile之后,看做“程度较轻的 synchronized”,具备两个特性:
1.保证此变量对所有线程的可见性(当一条线程修改这个变量值时,新值其他线程立即得知)
2.禁止指令重新排序
注意volatile修饰变量不能保证在并发条件下是线程安全的,因为java里面的运算并非原子操作。
java.util.concurrent.atomic工具包,支持在单个变量上解除锁的线程安全编程。其基本的特性就是在多线程环境下,当有多个线程同时执行这些类的实例包含的方法时,具有排他性,即当某个线程进入方法,执行其中的指令时,不会被其他线程打断,而别的线程就像自旋锁一样,一直等到该方法执行完成,才由JVM从等待队列中选择一个另一个线程进入,这只是一种逻辑上的理解。

volatile是不能保证原子性的, 写了一点junit. 这里使用了包装类Integer, 来验证 对引用操作 的原子性. 可以看到使用了AtomicReference可以保证结果是正确的.

 1     private static volatile Integer num1 = 0;
 2     private static AtomicReference<Integer> ar=new AtomicReference<Integer>(num1);
 3 
 4     @Test
 5     public void dfasd111() throws InterruptedException{
 6         for (int i = 0; i < 1000; i++) {
 7             new Thread(new Runnable(){
 8                 @Override
 9                 public void run() {
10                     for (int i = 0; i < 10000; i++)
11                         while(true){
12                             Integer temp=ar.get();
13                             if(ar.compareAndSet(temp, temp+1))break;
14                         }
15                 }       
16             }).start();
17         }
18         Thread.sleep(10000);
19         System.out.println(ar.get()); //10000000
20     }
21         
22     @Test
23     public void dfasd1112() throws InterruptedException{
24         for (int i = 0; i < 1000; i++) {
25             new Thread(new Runnable(){
26                 @Override
27                 public void run() {
28                     for (int i = 0; i < 10000; i++) {
29                         num1=num1++;
30                     }
31                 }       
32             }).start();
33         }
34         Thread.sleep(10000);
35         System.out.println(num1); //something like 238981
36     }

如果想让运算具有原子性, 请使用:

  1. AtomicInteger
  2. AtomicLong

类似i++这样的"读-改-写"复合操作(在一个操作序列中, 后一个操作依赖前一次操作的结果), 在多线程并发处理的时候会出现问题, 因为可能一个线程修改了变量, 而另一个线程没有察觉到这样变化, 当使用原子变量之后, 则将一系列的复合操作合并为一个原子操作,从而避免这种问题, i++=>i.incrementAndGet()
原子变量只能保证对一个变量的操作是原子的, 如果有多个原子变量之间存在依赖的复合操作, 也不可能是安全的, 另外一种情况是要将更多的复合操作作为一个原子操作, 则需要使用synchronized将要作为原子操作的语句包围起来. 因为涉及到可变的共享变量(类实例成员变量)才会涉及到同步, 否则不必使用synchronized

多线程访问成员变量与局部变量

类变量(类里面static修饰的变量)保存在“方法区”
实例变量(类里面的普通变量)保存在“堆”
局部变量(方法里声明的变量)“虚拟机栈”
“方法区”和“堆”都属于线程共享数据区,“虚拟机栈”属于线程私有数据区。

因此,局部变量是不能多个线程共享的,而类变量和实例变量是可以多个线程共享的。事实上,在java中,多线程间进行通信的唯一途径就是通过类变量和实例变量。也就是说,如果一段多线程程序中如果没有类变量和实例变量,那么这段多线程程序就一定是线程安全的。
 案例

public class HelloThreadTest
{
    public static void main(String[] args)
    {
        HelloThread r = new HelloThread();

        Thread t1 = new Thread(r);
        Thread t2 = new Thread(r);

        t1.start();
        t2.start();

    }

}

class HelloThread implements Runnable
{
    int i;

    @Override
    public void run()
    {

        while (true)
        {
            System.out.println("Hello number: " + i++);

            try
            {
                Thread.sleep((long) Math.random() * 1000);
            }
            catch (InterruptedException e)
            {
                e.printStackTrace();
            }

            if (50 == i)
            {
                break;
            }
        }

    }
}

  该例子中,HelloThread类实现了Runnable接口,其中run()方法的主要工作是输出"Hello number: "字符串加数字i,并且同时递增i,当i到达50时,退出循环。

  main()方法中生成了一个HelloThread类的对象r,并且利用这个一个对象生成了两个线程。

  程序的执行结果是:顺次打印了0到49的数字,共50个数字。

  这是因为,i是成员变量,则HelloThread的对象r只包含这一个i,两个Thread对象因为由r构造,所以共享了同一个i

  当我们改变代码如下时(原先的成员变量i被注释掉,增加了方法中的局部变量i):

public class HelloThreadTest
{
    public static void main(String[] args)
    {
        HelloThread r = new HelloThread();

        Thread t1 = new Thread(r);
        Thread t2 = new Thread(r);

        t1.start();
        t2.start();

    }

}

class HelloThread implements Runnable
{
    // int i;
    // 若i是成员变量,则HelloThread的对象r只包含这一个i,两个Thread对象因为由r构造,所以共享了同一个i
    // 打印结果是0到49的数字
    @Override
    public void run()
    {
        int i = 0;
        // 每一个线程都会拥有自己的一份局部变量的拷贝
        // 线程之间互不影响
        // 所以会打印100个数字,0到49每个数字都是两遍
        while (true)
        {
            System.out.println("Hello number: " + i++);

            try
            {
                Thread.sleep((long) Math.random() * 1000);
            }
            catch (InterruptedException e)
            {
                e.printStackTrace();
            }

            if (50 == i)
            {
                break;
            }
        }

    }
}

  如注释中所述,由于局部变量对于每一个线程来说都有自己的拷贝,所以各个线程之间不再共享同一个变量,输出结果为100个数字,实际上是两组,每组都是0到49的50个数字,并且两组数字之间随意地穿插在一起。 

得到的结论如下:

  如果一个变量是成员变量,那么多个线程对同一个对象的成员变量进行操作时,它们对该成员变量是彼此影响的,也就是说一个线程对成员变量的改变会影响到另一个线程。

  如果一个变量是局部变量,那么每个线程都会有一个该局部变量的拷贝(即便是同一个对象中的方法的局部变量,也会对每一个线程有一个拷贝),一个线程对该局部变量的改变不会影响到其他线程。

Future

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

Future 接口的局限性

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

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

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

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

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


public interface Future<V> {
   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<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)

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

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

示例:对list数据进行分页异步处理

List<List<Long>> allIds = Lists.partition(ids,200);

final List<User> result = Lists.newArrayList();
allIds.stream().forEach((batchIds) -> {
   CompletableFuture.supplyAsync(() -> {
        result.addAll(remoteCallUser(batchIds));
        return Boolean.TRUE;
    }, executor);
})

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

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

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)
public CompletableFuture<T> exceptionally(Function<Throwable,? extends T> fn)

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

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

示例

public static void whenComplete() throws Exception {
    CompletableFuture<Void> 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<Void, Throwable>() {
        @Override
        public void accept(Void t, Throwable action) {
            System.out.println("执行完成!");
        }
        
    });
    future.exceptionally(new Function<Throwable, Void>() {
        @Override
        public Void apply(Throwable t) {
            System.out.println("执行失败!"+t.getMessage());
            return null;
        }
    });
    
    TimeUnit.SECONDS.sleep(2);
}

3、 thenApply 方法

当一个线程依赖另一个线程时,可以使用 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)

Function<? super T,? extends U>
T:上一个任务返回结果的类型
U:当前任务的返回值类型

示例

private static void thenApply() throws Exception {
    CompletableFuture<Long> future = CompletableFuture.supplyAsync(new Supplier<Long>() {
        @Override
        public Long get() {
            long result = new Random().nextInt(100);
            System.out.println("result1="+result);
            return result;
        }
    }).thenApply(new Function<Long, Long>() {
        @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 <U> CompletionStage<U> handle(BiFunction<? super T, Throwable, ? extends U> fn);
public <U> CompletionStage<U> handleAsync(BiFunction<? super T, Throwable, ? extends U> fn);
public <U> CompletionStage<U> handleAsync(BiFunction<? super T, Throwable, ? extends U> fn,Executor executor);

示例

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

        @Override
        public Integer get() {
            int i= 10/0;
            return new Random().nextInt(10);
        }
    }).handle(new BiFunction<Integer, Throwable, Integer>() {
        @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<Void> thenAccept(Consumer<? super T> action);
public CompletionStage<Void> thenAcceptAsync(Consumer<? super T> action);
public CompletionStage<Void> thenAcceptAsync(Consumer<? super T> action,Executor executor);

示例

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

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

6、thenRun 方法

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

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

示例

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

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

FutureTask

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

blic class FutureTask<V> implements RunnableFuture<V> {

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

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

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

public FutureTask(Callable<V> 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<Integer> 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<Integer>{
  @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<String> future = new FutureTask<String>(new Callable<String>() {
            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();
}

 Callable接口和Runnable接口的区别

1.Runnable需要在内部使用try-catch处理异常,callable在方法上抛出异常给上一级调用者处理。
2.Runnable没有返回值,Callable有返回值,泛型指定返回值类型,call方法类型由泛型指定。
3.需要使用线程池才能启动Callable实现类线程

Executor框架

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

ExecutorService

继承自Executor接口

主要方法

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

Executor 和 ExecutorService  Executors的区别

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

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 实现的就是一个线程中的全局变量,与真正的全局变量的区别在于 ThreadLocal 的变量是每个线程中的全局变量,也就是说不同线程访问到的值是不一样的。其填充的变量属于当前线程,该变量对于其他线程是隔离的。

核心点:每个线程拥有自己单独的k-v系统,可以put,和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。

ThreadLocal使用场景解析

场景1:

ThreadLocal 用作保存每个线程独享的对象,为每个线程都创建一个副本,这样每个线程都可以修改自己所拥有的副本, 而不会影响其他线程的副本,确保了线程安全,这些就避免了任务级别变量的创建

场景2:

ThreadLocal 用作每个线程内需要独立保存信息,以便供其他方法更方便地获取该信息的场景。每个线程获取到的信息可能都是不一样的,前面执行的方法保存了信息后,后续方法可以通过ThreadLocal 直接获取到,避免了传参,类似于全局变量的概念。

典型场景1

      这种场景通常用于保存线程不安全的工具类,典型的需要使用的类就是 SimpleDateFormat。在这种情况下,每个Thread内都有自己的实例副本,且该副本只能由当前Thread访问到并使用,相当于每个线程内部的本地变量,这也是ThreadLocal命名的含义。因为每个线程独享副本,而不是公用的,所以不存在多线程间共享的问题。

比如有10个线程都要用到SimpleDateFormat

public class ThreadLocalDemo01 {

    public static void main(String[] args) throws InterruptedException {

        for (int i = 0; i < 10; i++) {
            int finalI = i;
            new Thread(() -> {
                String data = new ThreadLocalDemo01().date(finalI);
                System.out.println(data);
            }).start();
            Thread.sleep(100);
        }

    }

    private String date(int seconds){
        Date date = new Date(1000 * seconds);
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("mm:ss");
        return simpleDateFormat.format(date);
    }
}

我们给每个线程都创建了SimpleDateFormat对象,他们之间互不影响,代码是可以正常执行的。输出结果:

00:00
00:01
00:02
00:03
00:04
00:05
00:06
00:07
00:08
00:09

我们用图来看一下当前的这种状态:

每个任务都会创建一个SimpleDateFormat对象

如果有1000个线程都用到SimpleDateFormat对象呢?

我们一般不会直接去创建这么多线程,而是通过线程池,比如:

public class ThreadLocalDemo011 {
   public static ExecutorService threadPool = Executors.newFixedThreadPool(16);

    public static void main(String[] args) throws InterruptedException {

        for (int i = 0; i < 1000; i++) {
            int finalI = i;
            threadPool.submit(() -> {
                String data = new ThreadLocalDemo011().date(finalI);
                System.out.println(data);
            });
        }
        threadPool.shutdown();
    }

    private String date(int seconds){
        Date date = new Date(1000 * seconds);
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("mm:ss");
        return simpleDateFormat.format(date);
    }

}

可以看出,我们用了一个16线程的线程池,并且给这个线程池提交了1000次任务。每个任务中它做的事情和之前是一样的,还是去执行date方法,并且在这个方法中创建一个

simpleDateFormat 对象。结果:

1

2

3

4

5

6

7

8

9

10

00:00

00:07

00:04

00:02

...

16:29

16:28

16:27

16:26

16:39

我们刚才所做的就是每个任务都创建了一个 simpleDateFormat 对象,也就是说,1000 个任务对应 1000 个 simpleDateFormat 对象,但是如果任务数巨多怎么办?

这么多对象的创建是有开销的,并且在使用完之后的销毁同样是有开销的,同时存在在内存中也是一种内存的浪费。

我们可能会想到,要不所有的线程共用一个 simpleDateFormat 对象?但是simpleDateFormat 又不是线程安全的,我们必须做同步,比如使用synchronized加锁。到这里也许就是我们最终的一个解决方法。但是使用synchronized加锁会陷入一种排队的状态,多个线程不能同时工作,这样一来,整体的效率就被大大降低了。有没有更好的解决方案呢?

使用ThreadLocal

对这种场景,ThreadLocal再合适不过了,ThreadLocal给每个线程维护一个自己的simpleDateFormat对象,这个对象在线程之间是独立的,互相没有关系的。这也就避免了线程安全问题。与此同时,simpleDateFormat对象还不会创造过多,线程池一共只有 16 个线程,所以需要16个对象即可。

public class ThreadLocalDemo04 {

    public static ExecutorService threadPool = Executors.newFixedThreadPool(16);

    public static void main(String[] args) throws InterruptedException {

        for (int i = 0; i < 1000; i++) {
            int finalI = i;
            threadPool.submit(() -> {
                String data = new ThreadLocalDemo04().date(finalI);
                System.out.println(data);
            });
        }
        threadPool.shutdown();
    }

    private String date(int seconds){
        Date date = new Date(1000 * seconds);
        SimpleDateFormat dateFormat = ThreadSafeFormater.dateFormatThreadLocal.get();
        return dateFormat.format(date);
    }
}

class ThreadSafeFormater{
    public static ThreadLocal<SimpleDateFormat> dateFormatThreadLocal = ThreadLocal.withInitial(() -> new SimpleDateFormat("mm:ss"));
}

我们用图来看一下当前的这种状态:

典型场景2

           每个线程内需要保存类似于全局变量的信息(例如在拦截器中获取的用户信息),可以让不同方法直接使用,避免参数传递的麻烦却不想被多线程共享(因为不同线程获取到的用户信息不一样)。

例如,用 ThreadLocal 保存一些业务内容(用户权限信息、从用户系统获取到的用户名、用户ID 等),这些信息在同一个线程内相同,但是不同的线程使用的业务内容是不相同的。

在线程生命周期内,都通过这个静态 ThreadLocal 实例的 get() 方法取得自己 set 过的那个对象,避免了将这个对象(如 user 对象)作为参数传递的麻烦。

比如说我们是一个用户系统,那么当一个请求进来的时候,一个线程会负责执行这个请求,然后这个请求就会依次调用service-1()、service-2()、service-3()、service-4(),这4个方法可能是分布在不同的类中的。

我们用图画的形式举一个实例:

代码:

package com.kong.threadlocal;


public class ThreadLocalDemo05 {
    public static void main(String[] args) {
        User user = new User("jack");
        new Service1().service1(user);
    }

}

class Service1 {
    public void service1(User user){
        //给ThreadLocal赋值,后续的服务直接通过ThreadLocal获取就行了。
        UserContextHolder.holder.set(user);
        new Service2().service2();
    }
}

class Service2 {
    public void service2(){
        User user = UserContextHolder.holder.get();
        System.out.println("service2拿到的用户:"+user.name);
        new Service3().service3();
    }
}

class Service3 {
    public void service3(){
        User user = UserContextHolder.holder.get();
        System.out.println("service3拿到的用户:"+user.name);
        //在整个流程执行完毕后,一定要执行remove
        UserContextHolder.holder.remove();
    }
}

class UserContextHolder {
    //创建ThreadLocal保存User对象
    public static ThreadLocal<User> holder = new ThreadLocal<>();
}

class User {
    String name;
    public User(String name){
        this.name = name;
    }
}

执行的结果:

service2拿到的用户:jack
service3拿到的用户:jack

InheritableThreadLocal

  InheritableThreadLocal用于子线程能够拿到父线程往ThreadLocal里设置的值。使用代码如下:

public class Test {

    public static ThreadLocal<Integer> threadLocal = new InheritableThreadLocal<Integer>();

    public static void main(String args[]) {
        threadLocal.set(new Integer(456));
        Thread thread = new MyThread();
        thread.start();
        System.out.println("main = " + threadLocal.get());
    }

    static class MyThread extends Thread {
        @Override
        public void run() {
            System.out.println("MyThread = " + threadLocal.get());
        }
    }
}

如果把上面的InheritableThreadLocal换成ThreadLocal的话,在子线程里的输出将为是空。从上面的代码里也可以看出InheritableThreadLocal是ThreadLocal的子类。下面是InheritableThreadLocal的源码:

package java.lang;
import java.lang.ref.*;

public class InheritableThreadLocal<T> extends ThreadLocal<T> {

 //这个方法留给子类实现
    protected T childValue(T parentValue) {
        return parentValue;
    }

//重写getMap方法,返回的是Thread的inheritableThreadLocals引用
    ThreadLocalMap getMap(Thread t) {
       return t.inheritableThreadLocals;
    }

//重写createMap方法,构造的ThreadLocalMap会传给Thread的inheritableThreadLocals 变量
    void createMap(Thread t, T firstValue) {
        t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
    }
}

从源码上看,跟ThreadLocal不一样的无非是ThreadLocalMap的引用不一样了,从逻辑上来讲,这并不能做到子线程得到父线程里的值。那秘密在那里呢?通过跟踪Thread的构造方法,你能够发现是在构造Thread对象的时候对父线程的InheritableThreadLocal进行了复制。下面是Thread的部分源码:

public class Thread implements Runnable {
      //默认人构造方法,会调用init方法进行初使化
      public Thread() {
        init(null, null, "Thread-" + nextThreadNum(), 0);
    }

    private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize) {
        init(g, target, name, stackSize, null);
    }

//最终会调用到当前这个方法
    private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc) {
        if (name == null) {
            throw new NullPointerException("name cannot be null");
        }

        this.name = name.toCharArray();
// parent为当前线程,也就是调用了new Thread();方法的线程
        Thread parent = currentThread();
        SecurityManager security = System.getSecurityManager();
        if (g == null) {
            if (security != null) {
                g = security.getThreadGroup();
            }
            if (g == null) {
                g = parent.getThreadGroup();
            }
        }
        g.checkAccess();
        if (security != null) {
            if (isCCLOverridden(getClass())) {
                security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
            }
        }

        g.addUnstarted();

        this.group = g;
//在这里会继承父线程是否为后台线程的属性还有父线程的优先级
        this.daemon = parent.isDaemon();
        this.priority = parent.getPriority();
        if (security == null || isCCLOverridden(parent.getClass()))
            this.contextClassLoader = parent.getContextClassLoader();
        else
            this.contextClassLoader = parent.contextClassLoader;
        this.inheritedAccessControlContext =
                acc != null ? acc : AccessController.getContext();
        this.target = target;
        setPriority(priority);
//这里是重点,当父线程的inheritableThreadLocals 不为空的时候,会调用 ThreadLocal.createInheritedMap方法,传入的是父线程的inheritableThreadLocals。原来复制变量的秘密在这里
        if (parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
        /* Stash the specified stack size in case the VM cares */
        this.stackSize = stackSize;

        /* Set thread ID */
        tid = nextThreadID();
    }

}

通过跟踪Thread的构造方法,我们发现只要父线程在构造子线程(调用new Thread())的时候inheritableThreadLocals变量不为空。新生成的子线程会通过ThreadLocal.createInheritedMap方法将父线程inheritableThreadLocals变量有的对象复制到子线程的inheritableThreadLocals变量上。这样就完成了线程间变量的继承与传递。
ThreadLocal.createInheritedMap方法的源码如下:

public class ThreadLocal<T> {
//根据传入的map,构造一个新的ThreadLocalMap
    static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
        return new ThreadLocalMap(parentMap);
    }

   static class ThreadLocalMap {
//这个private的构造方法就是专门给ThreadLocal使用的
          private ThreadLocalMap(ThreadLocalMap parentMap) {
//ThreadLocalMap还是用Entry数组来存储对象的
            Entry[] parentTable = parentMap.table;
            int len = parentTable.length;
            setThreshold(len);
            table = new Entry[len];
//这里是复制parentMap数据的逻辑
            for (int j = 0; j < len; j++) {
                Entry e = parentTable[j];
                if (e != null) {
                    ThreadLocal key = e.get();
                    if (key != null) {
                        Object value = key.childValue(e.value);
                        Entry c = new Entry(key, value);
                        int h = key.threadLocalHashCode & (len - 1);
                        while (table[h] != null)
                            h = nextIndex(h, len);
                        table[h] = c;
                        size++;
                    }
                }
            }
        }
  }

}

总结:

  • ThreadLocal和InheritableThreadLocal本质上只是为了方便编码给的工具类,具体存数据是ThreadLocalMap 对象。
  • ThreadLocalMap 存的key对象是ThreadLocal,value就是真正需要存的业务对象。
  • Thread里通过两个变量持用ThreadLocalMap 对象,分别为:threadLocals和inheritableThreadLocals。
  • InheritableThreadLocal之所以能够完成线程间变量的传递,是在new Thread()的时候对inheritableThreadLocals对像里的值进行了复制。
  • 子线程通过继承得到的InheritableThreadLocal里的值与父线程里的InheritableThreadLocal的值具有相同的引用,如果父子线程想实现不影响各自的对象,可以重写InheritableThreadLocal的childValue方法。

ExecutorCompletionService      

             假设现在有一大批需要进行计算的任务,为了提高整批任务的执行效率,你可能会使用线程池,向线程池中不断submit异步计算任务,同时你需要保留与每个任务关联的Future,最后遍历这些Future,通过调用Future接口实现类的get方法获取整批计算任务的各个结果。

             虽然使用了线程池提高了整体的执行效率,但遍历这些Future,调用Future接口实现类的get方法是阻塞的,也就是和当前这个Future关联的计算任务真正执行完成的时候,get方法才返回结果,如果当前计算任务没有执行完成,而有其它Future关联的计算任务已经执行完成了,就会白白浪费很多等待的时间,所以最好是遍历的时候谁先执行完成就先获取哪个结果,这样就节省了很多持续等待的时间。而ExecutorCompletionService可以实现这样的效果,它的内部有一个先进先出的阻塞队列,用于保存已经执行完成的Future,通过调用它的take方法或poll方法可以获取到一个已经执行完成的Future,进而通过调用Future接口实现类的get方法获取最终的结果。
案例演示

@Test	
public void test() throws InterruptedException, ExecutionException {	
    Executor executor = Executors.newFixedThreadPool(3);	
    CompletionService<String> service = new ExecutorCompletionService<>(executor);	
    for (int i = 0 ; i < 5 ;i++) {	
        int seqNo = i;	
        service.submit(new Callable<String>() {	
            @Override	
            public String call() throws Exception {	
                return "HelloWorld-" + seqNo + "-" + Thread.currentThread().getName();	
            }	
        });	
    }	
    for (int j = 0 ; j < 5; j++) {	
        System.out.println(service.take().get());	
    }	
}

结果

HelloWorld-2-pool-1-thread-3	
HelloWorld-1-pool-1-thread-2	
HelloWorld-3-pool-1-thread-2	
HelloWorld-4-pool-1-thread-3	
HelloWorld-0-pool-1-thread-1 

Semaphore

简介:

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

作用:

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

方法

 void acquire()

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

 void acquire(int permits)

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

 void acquireUninterruptibly()

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

 void acquireUninterruptibly(int permits)

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

 int availablePermits()

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

 int drainPermits()

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

protected  Collection<Thread> 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()方法。这个动作将跳过所有正在执行的任务和被提交还没有执行的任务。但是它并不对正在执行的任务做任何保证,有可能它们都会停止,也有可能执行完成

注意:ExecutorService执行定时任务时,为了避免异常导致定时任务无法进行,最好在run()中进行try catch


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

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

 异常处理区别       

         在execute的时候,如果你没有实现一个handler,那么他就使用默认的handler来处理异常,你要是实现了一个handler他就会使用的实例化的handler,除此之外,也可以改写ThreadPoolExecutor.afterExecute()中自定义异常,但是对于submit来说,异常是绑定到Future上了,但是调用future.get()的时候,这些异常才会给你抛出来,意味着你自己定义的handler其实是无效

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

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<Runnable>()无解阻塞队列
  • 通俗:创建可容纳固定数量线程的池子,每个线程的存活时间是无限的,当池子满了就不在添加线程了;如果池中的所有线程均在繁忙状态,对于新任务会进入阻塞队列中(无界的阻塞队列)。
  • 适用:执行长期的任务,性能好很多

newSingleThreadExecutor:

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

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也将关闭

 验证上述执行流程

public class ThreadTest {
    
    public static void main(String[] args) {

        List<Integer> list = new ArrayList<>();
        int size = 10;
        for (int i = 0; i < size; i++) {
            list.add(i);
        }


        ThreadFactory threadFactory = new ThreadFactoryBuilder()
                .setNameFormat("TaskRunner-%d")
                .setUncaughtExceptionHandler(
                        (thread, throwable) ->
                             System.out.println("Thread {} has uncaughtException."+thread.getName()+throwable))
                .build();

        ArrayBlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(10);
        ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 15, 3,
                TimeUnit.MINUTES, workQueue, threadFactory);

        List<List<Integer>> allIds = Lists.partition(list,2);
        AtomicInteger      count  = new AtomicInteger();
        allIds.stream().forEach((item) -> {
            CompletableFuture.supplyAsync(() -> {

                count.getAndIncrement();
                System.out.println("count : " + count);

                System.out.println("workQueue  size  is : " + workQueue.size());
                try {
                    //线程一直运行 测试最大并行度
                    Thread.sleep(1000*10000000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                return Boolean.TRUE;
            }, executor);
        });

    }
}

注意: 以下测试 corePoolSize=2   maximumPoolSize=15

1.当测试list的size是10,队列大小是10的时候

count : 1
workQueue  size  is : 3
count : 2
workQueue  size  is : 3

2.当测试list的size是100, 队列大小是10的时候

count : 1
count : 2
workQueue  size  is : 10
workQueue  size  is : 10
count : 3
workQueue  size  is : 10
count : 4
workQueue  size  is : 10
count : 5
workQueue  size  is : 10
count : 6
workQueue  size  is : 10
count : 7
workQueue  size  is : 10
count : 8
workQueue  size  is : 10
count : 9
workQueue  size  is : 10
count : 10
workQueue  size  is : 10
count : 11
workQueue  size  is : 10
count : 12
workQueue  size  is : 10
count : 13
workQueue  size  is : 10
count : 14
workQueue  size  is : 10
count : 15
workQueue  size  is : 10
Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.CompletableFuture$AsyncSupply@43556938 rejected from java.util.concurrent.ThreadPoolExecutor@3d04a311[Running, pool size = 15, active threads = 15, queued tasks = 10, completed tasks = 0]
	at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2063)
	at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:830)
	at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1379)
	at java.util.concurrent.CompletableFuture.asyncSupplyStage(CompletableFuture.java:1604)
	at java.util.concurrent.CompletableFuture.supplyAsync(CompletableFuture.java:1830)
	at com.techwolf.flink.learning.application.stream.ThreadTest.lambda$main$2(ThreadTest.java:44)
	at java.util.Iterator.forEachRemaining(Iterator.java:116)
	at java.util.Spliterators$IteratorSpliterator.forEachRemaining(Spliterators.java:1801)
	at java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:580)
	at com.techwolf.flink.learning.application.stream.ThreadTest.main(ThreadTest.java:43)

3.当测试list的size是100, 队列大小是100的时候

count : 1
workQueue  size  is : 26
count : 2
workQueue  size  is : 48

我们可以看到:

1.当线程数达到corePoolSize ,2时,新提交任务将被放入workQueue中

2.当队列不满,线程最大并行度是corePoolSize

3.当线程数超过队列大小,每超过一个就执行立刻一个,直到最大线程数=maximumPoolSize

4.当队列满了并且超过maximumPoolSize时,任务会被拒绝

注意:在线程池执行任务,务必处理好异常,否则线程将无法执行,出错线程池也不会报任何异常

  ScheduledExecutorService service = Executors.newScheduledThreadPool(1);
        service.scheduleAtFixedRate(() -> {
            Integer i=null;
            try {
                System.out.println(i.toString());
            } catch (Exception e) {
                e.printStackTrace();
            }

        },0,1, TimeUnit.SECONDS);

ThreadPoolExecutor

              在《阿里巴巴java开发手册》中指出了线程资源必须通过线程池提供,不允许在应用中自行显示的创建线程,这样一方面是线程的创建更加规范,可以合理控制开辟线程的数量;另一方面线程的细节管理交给线程池处理,优化了资源的开销。而线程池不允许使用Executors去创建,而要通过ThreadPoolExecutor方式,这一方面是由于jdk中Executor框架虽然提供了如newFixedThreadPool()、newSingleThreadExecutor()、newCachedThreadPool()等创建线程池的方法,但都有其局限性,不够灵活;另外由于前面几种方法内部也是通过ThreadPoolExecutor方式实现,使用ThreadPoolExecutor有助于大家明确线程池的运行规则,创建符合自己的业务场景需要的线程池,避免资源耗尽的风险。下面我们就对ThreadPoolExecutor的使用方法进行一个详细的概述。

ThreadPoolExecutor的构造函数

  public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.acc = System.getSecurityManager() == null ?
                null :
                AccessController.getContext();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

 在ThreadPoolExecutor类中提供了四个构造方法:

public class ThreadPoolExecutor extends AbstractExecutorService {

   .....

    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,

            BlockingQueue<Runnable> workQueue);

    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,

            BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory);

    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,

            BlockingQueue<Runnable> workQueue,RejectedExecutionHandler handler);

    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,

        BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler);

    ...

}

构造函数的参数含义如下:

  1. corePoolSize:指定了线程池中的线程数量,它的数量决定了添加的任务是开辟新的线程去执行,还是放到workQueue任务队列中去;
  2. maximumPoolSize:指定了线程池中的最大线程数量,这个参数会根据你使用的workQueue任务队列的类型,决定线程池会开辟的最大线程数量;
  3. keepAliveTime:  表示线程没有任务执行时最多保持多久时间会终止。默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用,直到线程池中的线程数不大于corePoolSize,即当线程池中的线程数大于corePoolSize时,如果一个线程空闲的时间达到keepAliveTime,则会终止,直到线程池中的线程数不超过corePoolSize。但是如果调用了allowCoreThreadTimeOut(boolean)方法,在线程池中的线程数不大于corePoolSize时,keepAliveTime参数也会起作用,直到线程池中的线程数为0;unit:keepAliveTime的单位
  4. workQueue:任务队列,被添加到线程池中,但尚未被执行的任务;它一般分为直接提交队列、有界任务队列、无界任务队列、优先任务队列几种;
  5. threadFactory:线程工厂,用于创建线程,一般用默认即可;
  6. handler:拒绝策略;当任务太多来不及处理时,如何拒绝任务;

线程池任务缓冲

               任务缓冲模块是线程池能够管理任务的核心部分。线程池的本质是对任务和线程的管理,而做到这一点最关键的思想就是将任务和线程两者解耦,不让两者直接关联,才可以做后续的分配工作。线程池中是以生产者消费者模式,通过一个阻塞队列来实现的。阻塞队列缓存任务,工作线程从阻塞队列中获取任务。

阻塞队列(BlockingQueue)是一个支持两个附加操作的队列。这两个附加的操作是:在队列为空时,获取元素的线程会等待队列变为非空。当队列满时,存储元素的线程会等待队列可用。阻塞队列常用于生产者和消费者的场景,生产者是往队列里添加元素的线程,消费者是从队列里拿元素的线程。阻塞队列就是生产者存放元素的容器,而消费者也只从容器里拿元素。

下图中展示了线程1往阻塞队列中添加元素,而线程2从阻塞队列中移除元素:

图片

图5 阻塞队列

使用不同的队列可以实现不一样的任务存取策略。在这里,我们可以再介绍下阻塞队列的成员:

上面我们已经介绍过了,它一般分为

  1. 直接提交队列(SynchronousQueue)
  2. 有界任务队列(ArrayBlockingQueue)
  3. 无界任务队列(LinkedBlockingQueue)
  4. 优先任务队列(PriorityBlockingQueue)

1、直接提交队列:设置为SynchronousQueue队列,SynchronousQueue是一个特殊的BlockingQueue,它没有容量,每执行一个插入操作就会阻塞,需要再执行一个删除操作才会被唤醒,反之每一个删除操作也都要等待对应的插入操作。

public class ThreadPool {
    private static ExecutorService pool;
    public static void main( String[] args )
    {
        //maximumPoolSize设置为2 ,拒绝策略为AbortPolic策略,直接抛出异常
        pool = new ThreadPoolExecutor(1, 2, 1000, TimeUnit.MILLISECONDS, new SynchronousQueue<Runnable>(),Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
        for(int i=0;i<3;i++) {
            pool.execute(new ThreadTask());
        }   
    }
}

public class ThreadTask implements Runnable{
    
    public ThreadTask() {
        
    }
    
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}
输出结果为pool-1-thread-1
pool-1-thread-2
Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task com.hhxx.test.ThreadTask@55f96302 rejected from java.util.concurrent.ThreadPoolExecutor@3d4eac69[Running, pool size = 2, active threads = 0, queued tasks = 0, completed tasks = 2]
    at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(Unknown Source)
    at java.util.concurrent.ThreadPoolExecutor.reject(Unknown Source)
    at java.util.concurrent.ThreadPoolExecutor.execute(Unknown Source)
    at com.hhxx.test.ThreadPool.main(ThreadPool.java:17)

可以看到,当任务队列为SynchronousQueue,创建的线程数大于maximumPoolSize时,直接执行了拒绝策略抛出异常

使用SynchronousQueue队列,提交的任务不会被保存,总是会马上提交执行。如果用于执行任务的线程数量小于maximumPoolSize,则尝试创建新的进程,如果达到maximumPoolSize设置的最大值,则根据你设置的handler执行拒绝策略。因此这种方式你提交的任务不会被缓存起来,而是会被马上执行,在这种情况下,你需要对你程序的并发量有个准确的评估,才能设置合适的maximumPoolSize数量,否则很容易就会执行拒绝策略;

2、有界的任务队列:有界的任务队列可以使用ArrayBlockingQueue实现,如下所示

pool = new ThreadPoolExecutor(1, 2, 1000, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(10),Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());

使用ArrayBlockingQueue有界任务队列,若有新的任务需要执行时,线程池会创建新的线程,直到创建的线程数量达到corePoolSize时,则会将新的任务加入到等待队列中。若等待队列已满,即超过ArrayBlockingQueue初始化的容量,则继续创建线程,直到线程数量达到maximumPoolSize设置的最大线程数量,若大于maximumPoolSize,则执行拒绝策略。在这种情况下,线程数量的上限与有界任务队列的状态有直接关系,如果有界队列初始容量较大或者没有达到超负荷的状态,线程数将一直维持在corePoolSize以下,反之当任务队列已满时,则会以maximumPoolSize为最大线程数上限。

3、无界的任务队列:有界任务队列可以使用LinkedBlockingQueue实现,如下所示

pool = new ThreadPoolExecutor(1, 2, 1000, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(),Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());

使用无界任务队列,线程池的任务队列可以无限制的添加新的任务,而线程池创建的最大线程数量就是你corePoolSize设置的数量,也就是说在这种情况下maximumPoolSize这个参数是无效的,哪怕你的任务队列中缓存了很多未执行的任务,当线程池的线程数达到corePoolSize后,就不会再增加了;若后续有新的任务加入,则直接进入队列等待,当使用这种任务队列模式时,一定要注意你任务提交与处理之间的协调与控制,不然会出现队列中的任务由于无法及时处理导致一直增长,直到最后资源耗尽的问题。

4、优先任务队列:优先任务队列通过PriorityBlockingQueue实现,下面我们通过一个例子演示下

public class ThreadPool {
    private static ExecutorService pool;
    public static void main( String[] args )
    {
        //优先任务队列
        pool = new ThreadPoolExecutor(1, 2, 1000, TimeUnit.MILLISECONDS, new PriorityBlockingQueue<Runnable>(),Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
          
        for(int i=0;i<20;i++) {
            pool.execute(new ThreadTask(i));
        }    
    }
}
public class ThreadTask implements Runnable,Comparable<ThreadTask>{   
    private int priority;  
    public int getPriority() {
        return priority;
    }
    public void setPriority(int priority) {
        this.priority = priority;
    }
    public ThreadTask() {     
    }  
    public ThreadTask(int priority) {
        this.priority = priority;
    }
    //当前对象和其他对象做比较,当前优先级大就返回-1,优先级小就返回1,值越小优先级越高
    public int compareTo(ThreadTask o) {
         return  this.priority>o.priority?-1:1;
    }
    public void run() {
        try {
            //让线程阻塞,使后续任务进入缓存队列
            Thread.sleep(1000);
            System.out.println("priority:"+this.priority+",ThreadName:"+Thread.currentThread().getName());
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}
我们来看下执行的结果情况
priority:0,ThreadName:pool-1-thread-1
priority:9,ThreadName:pool-1-thread-1
priority:8,ThreadName:pool-1-thread-1
priority:7,ThreadName:pool-1-thread-1
priority:6,ThreadName:pool-1-thread-1
priority:5,ThreadName:pool-1-thread-1
priority:4,ThreadName:pool-1-thread-1
priority:3,ThreadName:pool-1-thread-1
priority:2,ThreadName:pool-1-thread-1
priority:1,ThreadName:pool-1-thread-1

大家可以看到除了第一个任务直接创建线程执行外,其他的任务都被放入了优先任务队列,按优先级进行了重新排列执行,且线程池的线程数一直为corePoolSize,也就是只有一个。

通过运行的代码我们可以看出PriorityBlockingQueue它其实是一个特殊的无界队列,它其中无论添加了多少个任务,线程池创建的线程数也不会超过corePoolSize的数量,只不过其他队列一般是按照先进先出的规则处理任务,而PriorityBlockingQueue队列可以自定义规则根据任务的优先级顺序先后执行。

二、拒绝策略

一般我们创建线程池时,为防止资源被耗尽,任务队列都会选择创建有界任务队列,但种模式下如果出现任务队列已满且线程池创建的线程数达到你设置的最大线程数时,这时就需要你指定ThreadPoolExecutor的RejectedExecutionHandler参数即合理的拒绝策略,来处理线程池"超载"的情况。ThreadPoolExecutor自带的拒绝策略如下:

  1. 1、AbortPolicy策略:该策略会直接抛出异常,阻止系统正常工作;
  2. 2、CallerRunsPolicy策略:如果线程池的线程数量达到上限,该策略会把任务队列中的任务放在调用者线程当中运行;
  3. 3、DiscardOledestPolicy策略:该策略会丢弃任务队列中最老的一个任务,也就是当前任务队列中最先被添加进去的,马上要被执行的那个任务,并尝试再次提交;
  4. 4、DiscardPolicy策略:该策略会默默丢弃无法处理的任务,不予任何处理。当然使用此策略,业务场景中需允许任务的丢失;
  5. 以上内置的策略均实现了RejectedExecutionHandler接口,当然你也可以自己扩展RejectedExecutionHandler接口,定义自己的拒绝策略,我们看下示例代码:
public class ThreadPool {
    private static ExecutorService pool;
    public static void main( String[] args )
    {
        //自定义拒绝策略
        pool = new ThreadPoolExecutor(1, 2, 1000, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(5),
                Executors.defaultThreadFactory(), new RejectedExecutionHandler() {
            public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
                System.out.println(r.toString()+"执行了拒绝策略");               
            }
        });          
        for(int i=0;i<10;i++) {
            pool.execute(new ThreadTask());
        }    
    }
}
public class ThreadTask implements Runnable{    
    public void run() {
        try {
            //让线程阻塞,使后续任务进入缓存队列
            Thread.sleep(1000);
            System.out.println("ThreadName:"+Thread.currentThread().getName());
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }   
    }
}

输出结果:

com.hhxx.test.ThreadTask@33909752执行了拒绝策略
com.hhxx.test.ThreadTask@55f96302执行了拒绝策略
com.hhxx.test.ThreadTask@3d4eac69执行了拒绝策略
ThreadName:pool-1-thread-2
ThreadName:pool-1-thread-1
ThreadName:pool-1-thread-1
ThreadName:pool-1-thread-2
ThreadName:pool-1-thread-1
ThreadName:pool-1-thread-2
ThreadName:pool-1-thread-1

可以看到由于任务加了休眠阻塞,执行需要花费一定时间,导致会有一定的任务被丢弃,从而执行自定义的拒绝策略;

三、ThreadFactory自定义线程创建

 线程池中线程就是通过ThreadPoolExecutor中的ThreadFactory,线程工厂创建的。那么通过自定义ThreadFactory,可以按需要对线程池中创建的线程进行一些特殊的设置,如命名、优先级等,下面代码我们通过ThreadFactory对线程池中创建的线程进行记录与命名

public class ThreadPool {
    private static ExecutorService pool;
    public static void main( String[] args )
    {
        //自定义线程工厂
        pool = new ThreadPoolExecutor(2, 4, 1000, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(5),
                new ThreadFactory() {
            public Thread newThread(Runnable r) {
                System.out.println("线程"+r.hashCode()+"创建");
                //线程命名
                Thread th = new Thread(r,"threadPool"+r.hashCode());
                return th;
            }
        }, new ThreadPoolExecutor.CallerRunsPolicy());
          
        for(int i=0;i<10;i++) {
            pool.execute(new ThreadTask());
        }    
    }
}

public class ThreadTask implements Runnable{    
    public void run() {
        //输出执行线程的名称
        System.out.println("ThreadName:"+Thread.currentThread().getName());
    }
}

我们看下输出结果

线程118352462创建
线程1550089733创建
线程865113938创建
ThreadName:threadPool1550089733
ThreadName:threadPool118352462
线程1442407170创建
ThreadName:threadPool1550089733
ThreadName:threadPool1550089733
ThreadName:threadPool1550089733
ThreadName:threadPool865113938
ThreadName:threadPool865113938
ThreadName:threadPool118352462
ThreadName:threadPool1550089733
ThreadName:threadPool1442407170

可以看到线程池中,每个线程的创建我们都进行了记录输出与命名。

四、ThreadPoolExecutor扩展

ThreadPoolExecutor扩展主要是围绕beforeExecute()、afterExecute()和terminated()三个接口实现的,

1、beforeExecute:线程池中任务运行前执行

2、afterExecute:线程池中任务运行完毕后执行

3、terminated:线程池退出后执行

通过这三个接口我们可以监控每个任务的开始和结束时间,或者其他一些功能。下面我们可以通过代码实现一下

我看下输出结果

线程118352462创建
线程1550089733创建
准备执行:Task0
准备执行:Task1
TaskNameTask0---ThreadName:threadPool118352462
线程865113938创建
执行完毕:Task0
TaskNameTask1---ThreadName:threadPool1550089733
执行完毕:Task1
准备执行:Task3
TaskNameTask3---ThreadName:threadPool1550089733
执行完毕:Task3
准备执行:Task2
准备执行:Task4
TaskNameTask4---ThreadName:threadPool1550089733
执行完毕:Task4
准备执行:Task5
TaskNameTask5---ThreadName:threadPool1550089733
执行完毕:Task5
准备执行:Task6
TaskNameTask6---ThreadName:threadPool1550089733
执行完毕:Task6
准备执行:Task8
TaskNameTask8---ThreadName:threadPool1550089733
执行完毕:Task8
准备执行:Task9
TaskNameTask9---ThreadName:threadPool1550089733
准备执行:Task7
执行完毕:Task9
TaskNameTask2---ThreadName:threadPool118352462
TaskNameTask7---ThreadName:threadPool865113938
执行完毕:Task7
执行完毕:Task2
线程池退出
public class ThreadPool {
    private static ExecutorService pool;
    public static void main( String[] args ) throws InterruptedException
    {
        //实现自定义接口
        pool = new ThreadPoolExecutor(2, 4, 1000, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(5),
                new ThreadFactory() {
            public Thread newThread(Runnable r) {
                System.out.println("线程"+r.hashCode()+"创建");
                //线程命名
                Thread th = new Thread(r,"threadPool"+r.hashCode());
                return th;
            }
        }, new ThreadPoolExecutor.CallerRunsPolicy()) {
    
            protected void beforeExecute(Thread t,Runnable r) {
                System.out.println("准备执行:"+ ((ThreadTask)r).getTaskName());
            }
            
            protected void afterExecute(Runnable r,Throwable t) {
                System.out.println("执行完毕:"+((ThreadTask)r).getTaskName());
            }
            
            protected void terminated() {
                System.out.println("线程池退出");
            }
        };
          
        for(int i=0;i<10;i++) {
            pool.execute(new ThreadTask("Task"+i));
        }    
        pool.shutdown();
    }
}

public class ThreadTask implements Runnable{    
    private String taskName;
    public String getTaskName() {
        return taskName;
    }
    public void setTaskName(String taskName) {
        this.taskName = taskName;
    }
    public ThreadTask(String name) {
        this.setTaskName(name);
    }
    public void run() {
        //输出执行线程的名称
        System.out.println("TaskName"+this.getTaskName()+"---ThreadName:"+Thread.currentThread().getName());
    }
}

可以看到通过对beforeExecute()、afterExecute()和terminated()的实现,我们对线程池中线程的运行状态进行了监控,在其执行前后输出了相关打印信息。另外使用shutdown方法可以比较安全的关闭线程池, 当线程池调用该方法后,线程池中不再接受后续添加的任务。但是,此时线程池不会立刻退出,直到添加到线程池中的任务都已经处理完成,才会退出。

五、线程池线程数量

线程池线程数量的设置没有一个明确的指标,根据实际情况,只要不是设置的偏大和偏小都问题不大,结合下面这个公式即可

            /**
             * Nthreads=CPU数量
             * Ucpu=目标CPU的使用率,0<=Ucpu<=1
             * W/C=任务等待时间与任务计算时间的比率
             */
            Nthreads = Ncpu*Ucpu*(1+W/C)

ForkJoinPool

为什么使用ForkJoinPool

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

优势

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

使用

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

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

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

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

举例

以还行没有返回值的“大任务”(简单低打印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 <br/>
 * Function: 继承RecursiveAction来实现“可分解”的任务。
 */
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<end;i++){
                System.out.println(Thread.currentThread().getName()+"的i值:"+i);
            }
        }else {
            int middle =(start+end)/2;
            PrintTask left = new PrintTask(start, middle);
            PrintTask right = new PrintTask(middle, end);
            //并行执行两个“小任务”
            left.fork();
            right.fork();
        }
    }  
}

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、ConcurrentHashMapBlockingQueue,它们都存在于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()方法,恢复执行自己的任务。

使用场景

            CountDownLatch的一个非常典型的应用场景是:有一个任务想要往下执行,但必须要等到其他的任务执行完毕后才可以继续往下执行。假如我们这个想要继续往下执行的任务调用一个CountDownLatch对象的await()方法,其他的任务执行完自己的任务后调用同一个CountDownLatch对象上的countDown()方法,此时cnt计数器的值就会减1。这个调用await()方法的任务将一直阻塞等待,直到这个CountDownLatch对象的计数值减到0为止。

 设置cnt初始值为3
 CountDownLatch cnt=new CountDownLatch(3);
 TA线程启动
 调用cnt.await()方法
 TA线程等待
 T1线程执行完成
 调用cnt.countDown()方法,此时 cnt=3-1=2
 T2线程执行完成
 调用cnt.countDown()方法,此时 cnt=2-1=1
 T3线程执行完成
 调用cnt.countDown()方法,此时 cnt=1-1=0
 cnt=0 重新唤醒TA主线程继续执行

案例
         举个例子,有三个工人在为老板干活,这个老板有一个习惯,就是当三个工人把一天的活都干完了的时候,他就来检查所有工人所干的活。记住这个条件:三个工人先全部干完活,老板才检查。所以在这里用Java代码设计两个类,Worker代表工人,Boss代表老板,具体的代码实现如下:

Worker

import java.util.Random;  
import java.util.concurrent.CountDownLatch;  
import java.util.concurrent.TimeUnit;  
  
public class Worker implements Runnable{  
      
    private CountDownLatch downLatch;  
    private String name;  
      
    public Worker(CountDownLatch downLatch, String name){  
        this.downLatch = downLatch;  
        this.name = name;  
    }  
      
    public void run() {  
        this.doWork();  
        try{  
            TimeUnit.SECONDS.sleep(new Random().nextInt(10));  
        }catch(InterruptedException ie){  
        }  
        System.out.println(this.name + "活干完了!");  
        this.downLatch.countDown();  
          
    }  
      
    private void doWork(){  
        System.out.println(this.name + "正在干活!");  
    }  
      
}  

Boss

import java.util.concurrent.CountDownLatch;  
  
public class Boss implements Runnable {  
  
    private CountDownLatch downLatch;  
      
    public Boss(CountDownLatch downLatch){  
        this.downLatch = downLatch;  
    }  
      
    public void run() {  
        System.out.println("老板正在等所有的工人干完活......");  
        try {  
            this.downLatch.await();  
        } catch (InterruptedException e) {  
        }  
        System.out.println("工人活都干完了,老板开始检查了!");  
    }  
  
}  

在实时系统中的使用场景

让我们尝试罗列出在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<Runnable>());
    //当拦截线程数达到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<Message> queue;  
  
    public Consumer(DelayQueue<Message> 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<Message> queue = new DelayQueue<Message>();    
            // 添加延时消息,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。

参考文件:java并发之DelayQueue实际运用示例 - 那啥快看 - 博客园

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交换回来的数据是: 零食

  • 5
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

阿华田512

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值