多线程笔记(四)

多线程笔记(四)

多线程笔记(四)

1. Atomic框架包

Atomic包里放着所以保证线程安全的原子类

大致分为7类

  1. 基本数据类型的原子操作类
  2. 引用类型的原子操作类
  3. 数组类型的原子操作类
  4. 修改属性字段的原子操作类
  5. 带版本的引用类型的原子操作类
  6. 增强的基本数据类型的原子操作类
  7. 增强操作的公共辅助类

2. CountDownLatch

CountDownLatch是一个辅助同步器类,是同步器框架里常用的一个类,主要作用是用于计数用的,类似于倒计时。

同步器框架:用来辅助实现同步功能的一些类。例如:CountDownLatch,CyclicBarrier,Semaphore,Exchanger

CountDownLatch的计数器减为0的时候是不能再次使用的,必须重新再new一个CountDownLatch。

CountDownLatch基于AQS实现,内部使用共享锁,因为要允许多个线程同时运行。

代码示例:

public class CountDownLatchTest {
    //多线程,统计给定的内容里面,字母a出现的次数,不区分大小写
    public static void main(String[] args) {
        String[] contents = {
                "sadasgadnqdhqamdjawqeqa",
                "jodasjoqwehmkldasmdqaiwpmclz"
        };
        int[] results = new int[contents.length];
        CountDownLatch cdLatch = new CountDownLatch(contents.length);

        //启动统计的计算线程
        for(int i = 0; i < contents.length; i++){
            new CountDownWorker(contents,i , results, cdLatch).start();
        }
        //等待所有的统计的计算线程运行结束过后,才计算最后的统计结果
        try {
            cdLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //计算最后的统计结果
        new CountDownWorkerSum(results).start();
    }
}
class CountDownWorkerSum extends Thread{
    private int[] results = null;
    public CountDownWorkerSum(int[] results){
        this.results = results;
    }
    public void run(){
        int sum = 0;
        for(int i = 0; i < results.length; i++){
            sum += results[i];
        }
        System.out.println("a总共出现的次数为 " + sum);
    }
}

class CountDownWorker extends Thread{
    //需要统计的内容
    private String[] contents = null;
    //本线程需要统计的索引
    private int index = 0;
    //用引用传递的方式,返回统计的结构数据
    private int[] results;
    private CountDownLatch cdLatch;
    public CountDownWorker(String[] contents, int index, int[] results, CountDownLatch cdLatch){
        this.contents = contents;
        this.index = index;
        this.results = results;
        this.cdLatch = cdLatch;
    }
    public void run(){
        System.out.println("Thread=" + Thread.currentThread().getName());
        String str = contents[index];
        str = str.trim();

        int count = 0;
        for(int i = 1; i <= str.length(); i++){
            if("a".equalsIgnoreCase(str.substring(i - 1, i))){
                count++;
            }
        }
        System.out.println("第"+(index+1) +"行,共有字母a " + count + " 个");
        results[index] = count;
        //计数
        cdLatch.countDown();
    }
}

3. CyclicBarrier

CyclicBarrier,循环栅栏,也可称为循环屏障,是一个辅助同步器类,功能和CountDownLatch类似。

使用Condition条件队列,在没有满足要求之前让线程进行等待。

代码示例:

public class CyclicBarrierTest {
    //多线程,统计给定的内容里面,字母a出现的次数,不区分大小写
    public static void main(String[] args) {
        String[] contents = {
                "sadasgadnqdhqamdjawqeqa",
                "jodasjoqwehmkldasmdqaiwpmclz"
        };
        int[] results = new int[contents.length];
        //运行完的线程达到了指定数量之后,自动执行CyclicBarrierSum(results)
        CyclicBarrier cb = new CyclicBarrier(contents.length, new CyclicBarrierSum(results) );

        //启动统计的计算线程
        for(int i = 0; i < contents.length; i++){
            new CyclicBarrierWorker(contents,i , results, cb).start();
        }
    }
}

class CyclicBarrierSum extends Thread{
    private int[] results = null;
    public CyclicBarrierSum(int[] results){
        this.results = results;
    }
    public void run(){
        int sum = 0;
        for(int i = 0; i < results.length; i++){
            sum += results[i];
        }
        System.out.println("a总共出现的次数为 " + sum);
    }
}

class CyclicBarrierWorker extends Thread{
    //需要统计的内容
    private String[] contents = null;
    //本线程需要统计的索引
    private int index = 0;
    //用引用传递的方式,返回统计的结构数据
    private int[] results;
    private CyclicBarrier cb;
    public CyclicBarrierWorker(String[] contents, int index, int[] results, CyclicBarrier cb){
        this.contents = contents;
        this.index = index;
        this.results = results;
        this.cb = cb;
    }
    public void run(){
        System.out.println("Thread=" + Thread.currentThread().getName());
        String str = contents[index];
        str = str.trim();

        int count = 0;
        for(int i = 1; i <= str.length(); i++){
            if("a".equalsIgnoreCase(str.substring(i - 1, i))){
                count++;
            }
        }
        System.out.println("第"+(index+1) +"行,共有字母a " + count + " 个");
        results[index] = count;

        //到达栅栏,等待
        try {
            cb.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (BrokenBarrierException e) {
            e.printStackTrace();
        }
    }
}

4. Semaphore

Semaphore的意思就是信号量,也是一个辅助同步器类

Semaphore支持公平锁与非公平锁

Semaphore维护一定数量的“许可证”。当有线程想要访问共享资源的时候,首先就要去获得许可,如果许可证不够了,线程就一直等待,直到获得许可证。

线程使用完共享资源过后,应该归还“许可证”,以供其他的线程来获得许可证。当线程使用完共享资源过后,应该归还许可证,以供其他的线程来获得许可证。

代码示例:

public class SemaphoreTest {
    //模拟前面走了一个人,后一个人才能进入地铁站
    public static void main(String[] args) {
        //假设地铁站容量为3,但是要来10倍容量的人
        int maxNum = 3;
        Semaphore sp = new Semaphore(maxNum);

        for(int i = 0; i < maxNum * 10; i++){
            new WeAreProgrammer(sp).start();
        }
    }

}

class WeAreProgrammer extends Thread{
    private Semaphore sp = null;
    public WeAreProgrammer(Semaphore sp){
        this.sp = sp;
    }
    public void run(){
        System.out.println("到达地铁口,请求获取进入地铁站的许可,线程= "+ Thread.currentThread().getName());
        //请求得到许可
        try {
            sp.acquire();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("获取到许可,终于可以进入到地铁站了,开始等地铁,线程= "+ Thread.currentThread().getName());
        try {
            Thread.sleep(2000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("上车了,真开心,释放许可,线程= "+ Thread.currentThread().getName());
        //释放许可
        sp.release();
    }
}

5. Exchanger

Exchanger是交换器的意思,也是一个辅助同步器类。

Exchanger可用于两个线程间交换数据(单槽位交换数据)。也支持多个线程间交换数据(多槽位交换数据),但是多少线程交换数据无法保证把某一线程的数据与另一指定线程交换。

代码示例

public class ExchangerTest {
    //实现两个线程之间交换数据
    public static void main(String[] args) {
        Exchanger<String> exchanger = new Exchanger<>();
        new ExchangerA(exchanger).start();
        new ExchangerB(exchanger).start();
    }
}

class ExchangerA extends Thread{
    //假定交换字符串类型的数据,所有泛型为String
    private Exchanger<String> exchanger = null;
    public ExchangerA(Exchanger<String> exchanger){
        this.exchanger = exchanger;
    }
    public void run(){
        System.out.println("ExchangerA在做之间的业务,产生了数据,线程= "+ Thread.currentThread().getName());
        try {
            Thread.sleep(2000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("ExchangerA等待与ExchangerB交换数据");
        //交换数据
        try {
            String exchangeMsg = exchanger.exchange("《这是ExchangerA的信息》");
            System.out.println("获得ExchangerB交换过来的数据=" + exchangeMsg);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("ExchangerA接着执行自己的业务");
    }
}

class ExchangerB extends Thread{
    //假定交换字符串类型的数据,所有泛型为String
    private Exchanger<String> exchanger = null;
    public ExchangerB(Exchanger<String> exchanger){
        this.exchanger = exchanger;
    }
    public void run(){
        System.out.println("ExchangerB在做之间的业务,产生了数据,线程= "+ Thread.currentThread().getName());
        try {
            Thread.sleep(2000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("ExchangerB等待与ExchangerB交换数据");
        //交换数据
        try {
            String exchangeMsg = exchanger.exchange("《这是ExchangerB的信息》");
            System.out.println("获得ExchangerA交换过来的数据=" + exchangeMsg);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("ExchangerB接着执行自己的业务");
    }
}

6. Executors框架

Executors框架是用来对多个线程,按照一定的策略,进行调度,执行和控制的框架。主要用于对线程池的管理

Executor接口

该接口内只有一个方法void execute(Runnable command);

用来在任务创建和任务执行之间进行解耦,用来提交可执行任务

ExecutorService接口

Executor的增强接口,继承了Executor接口,在Executor的基础上,增加了几类实用的方法。提供对线程池声明周期的管理,增加了对异步任务,批处理任务的支持。

  • 关闭执行器:

    • void shutdown();:在调用该方法的时候,已经提交给执行器的任务会继续执行,不会接受新任务的提交
    • List<Runnable> shutdownNow();:不会接受新任务的提交,也会尝试中断现在正在执行的任务(需要任务支持响应中断)
  • 监视执行器的状态:

    • boolean isShutdown();:如果执行器关闭了,返回true
    • boolean isTerminated();:如果执行器终止(执行器关闭,并且所有的任务都已经完成)了,返回true
  • 提供了对异步任务的支撑:

    • <T> Future<T> submit(Callable<T> task);:提交有返回值的任务
    • <T> Future<T> submit(Runnable task, T result);:提交任务,result指向返回值,可在外部通过get获得
    • Future<?> submit(Runnable task);:提交没有返回值的任务
  • 提供了对批处理任务的支持:

    • <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks):执行任务集合,返回Future集合
    • <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit):执行任务集合,返回Future集合,加入了超时限制
    • <T> T invokeAny(Collection<? extends Callable<T>> tasks):随机执行任务集合中的任务,有一个任务执行完毕直接返回,同时终止其他没有执行完毕的任务。
    • <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit):随机执行任务集合中的任务,有一个任务执行完毕直接返回,同时终止其他没有执行完毕的任务,加入了超时限制。

ScheduledExecutorService接口

继承了ExecutorService接口,用来定时执行或者周期性执行任务。

  • ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit);:提交一个任务,延时一段时间执行
  • <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit);:提交一个任务,延时一段时间执行
  • ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit);:提交一个任务,时间等于或超过time首次执行任务,之后每隔period*unit重复执行任务。
  • ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit);:创建并执行一个在给定初始延迟后首次启用的定期操作,随后,在每一次执行终止和下一次执行开始之间都存在给定的延迟。

Executors

Executors用于辅助创建Executor接口的实现类的实例

Executors是一个简单工厂,大体包括

  • 创建固定线程数的线程池

    • ExecutorService newFixedThreadPool(int nThreads) {
          return new ThreadPoolExecutor(nThreads, nThreads,
                                        0L, TimeUnit.MILLISECONDS,
                                        new LinkedBlockingQueue<Runnable>());
      }
      
  • 创建可缓存的线程池

    • ExecutorService newCachedThreadPool() {
          return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                        60L, TimeUnit.SECONDS,
                                        new SynchronousQueue<Runnable>());
      }
      
  • 创建可延时,可周期执行的线程池

    • ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
          return new ScheduledThreadPoolExecutor(corePoolSize);
      }
      
  • 创建单个线程数的线程池

    • ExecutorService newSingleThreadExecutor() {
          return new FinalizableDelegatedExecutorService
              (new ThreadPoolExecutor(1, 1,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>()));
      }
      
  • 创建Fork/Join框架

    • ExecutorService newWorkStealingPool() {
          return new ForkJoinPool
              (Runtime.getRuntime().availableProcessors(),
               ForkJoinPool.defaultForkJoinWorkerThreadFactory,
               null, true);
      }
      

ThreadPoolExecutor

ThreadPoolExecutor继承AbstractExecutorService类,而AbstractExecutorService又实现了ExecutorService接口

为什么引入线程池:

  • 减少因为频繁创建和销毁检查所带来的开销
  • 提高程序的响应速度
  • 自动管理线程,调度任务的执行,使得调用者只用关注任务的创建

构造方法参数解释

线程池的构造方法

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          RejectedExecutionHandler handler) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
         Executors.defaultThreadFactory(), handler);
}

参数

corePoolSize:核心线程池数量

maximumPoolSize:线程池最大线程数量(核心线程池数量+非核心线程池数量)

keepAliveTime:非核心线程存活时间

unit:存活时间的单位

workQueue:任务队列,当线程池的线程满了之后,任务会进入任务队列等待提交

handler:拒绝策略

拒绝策略

有两种情况会执行拒绝策略:

  • 核心线程池满了,任务队列也满了,同时非核心线程池也满了,这个时候再提交新的任务就会拒绝。
  • 提交任务的时候,线程池关闭了。

一共有四种拒绝策略:

  • AbortPolicy:抛出一个异常
  • DiscardPolicy:什么都不做,等任务自己被回收
  • DiscardOldestPolicy:丢弃队列中最先入队的一个任务,并执行当前任务
  • CallerRunsPolicy:用调用者的线程来执行任务

线程池状态

用AtomicInteger类型的变量ctl来表示线程池的状态,高3位保存线程池的状态,低29位保存线程的数量

一共五种状态:

  • RUNNING(-1):接受新任务,而且会处理已经进入阻塞队列的任务

  • SHUTDOWN(0):不接受新任务,但会处理已经进入阻塞队列的任务

  • STOP(1):不再接受新的任务,而且不再处理已经进入阻塞队列的任务,同时会中断正在运行的任务

  • TIDYING(2):所有任务都已经终止,工作线程数量为0时,准备调用terminated方法

  • TERMINATED(3):terminated方法已经执行完成

线程池状态变化示意图:

7. Future接口

Future模式是多线程中一种常见的设计模式,用来实现异步执行任务。调用方拿到的是凭证,拿到凭证之后就可以去处理其他的任务,在需要使用结果的时候再使用凭证还获取调用结果。Future接口的具体实现类是FutureTask

FutureTask

状态:

  • NEW(0):表示任务的初始化状态
  • COMPLETING(1):表示任务已经执行完成,属于中间状态
  • NORMAL(2):表示任务正常完成,属于最终状态
  • EXCEPTIONAL(3):表示任务异常完成,属于最终状态
  • CANCELLED(4):表示任务还没有开始执行就被取消了(非中断方式),属于最终状态
  • INTERRUPTING(5):表示任务还没有开始执行就被取消了(中断方式),属于中间状态
  • INTERRUPTED(6):表示任务还没有开始执行就被取消了(中断方式),已经被中断,属于最终状态

状态变化示意图

原文地址:https://www.cnblogs.com/xuzhuo123/archive/2022/05/16/16277040.html