java并发终极总结

版权声明:原创文章 欢迎参考 请勿抄袭 https://blog.csdn.net/aA518189/article/details/83588347

目录

 

Java并发介绍

1.并发编程三要素

2. 线程的五大状态

3.悲观锁与乐观锁

4.线程之间的协作

valitate 关键字

5.1 定义

5.2 原理

5.3 作用

Future

cancel方法

isCancelled

isDone

get()

get(long timeout, TimeUnit unit)

FutureTask

FutureTask与Future使用案例

Executor框架

ExecutorService

Executor 和 ExecutorService 和 Executors的区别

ThreadFactory接口

ThreadLocal

Semaphore

简介:

Interrupt与stop

理解

正确停止线程

线程池

线程池的好处

线程池的功能

常用线程池

ExecutorService介绍

ExecutorService的创建

ExecutorService的使用

ExecutorService的执行

ExecutorService的关闭

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

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

CountDownLatch

CountDownLatch是什么

CountDownLatch如何工作

在实时系统中的使用场景

 CyclicBarrier

CyclicBarrier是什么

CyclicBarrier如何使用

CountDownLatch与CyclicBarrier区别

如何保证线程顺序执行

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

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

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

Lock 与Synchronized的区别

Delay Quene

简介

Delayed

延时队列的实现

1.消息体

2.消费者

3.延时队列

 


Java并发介绍

1.并发编程三要素

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

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

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

2. 线程的五大状态

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

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

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

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

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

3.悲观锁与乐观锁

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

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

4.线程之间的协作

4.1 wait/notify/notifyAll

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

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

valitate 关键字

5.1 定义

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

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

5.2 原理

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

5.3 作用

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

Future

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

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。

FutureTask

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

pu

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();
    Task task = new Task();
    FutureTask<Integer> futureTask = new FutureTask<Integer>(task);
    executor.submit(futureTask);
    executor.shutdown();

    //第二种方式,注意这种方式和第一种方式效果是类似的,只不过一个使用的是ExecutorService,一个使用的是Thread
    /*Task task = new Task();
    FutureTask<Integer> futureTask = new FutureTask<Integer>(task);
    Thread thread = new Thread(futureTask);
    thread.start();*/

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

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

    try {
      System.out.println("task运行结果"+futureTask.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;
  }
}

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

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

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()方法,恢复执行自己的任务。

在实时系统中的使用场景

让我们尝试罗列出在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。

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

 

 

 

猜你喜欢

转载自blog.csdn.net/aA518189/article/details/83588347
今日推荐