JUC并发编程之:简单概述(五)

JUC并发编程之:简单概述(五)

##不可变类
##享元模式
##并发工具
  >线程池
  >JUC工具包
  >disruptor
  >guava

一、不可变类

1.1、日期转换的问题

·下面的代码在运行时,由于SimpleDateFormat不是线程安全的,很大机率出现:
java.lang.NumberFormatException或者出现不正确的日期解析结果
@Slf4j
public class SimpleDateFormatTest {
    public static void main(String[] args) {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        for (int i = 0; i <10 ; i++) {
            new Thread(()->{
                try {
                    log.debug("parse : {}",sdf.parse("2021-03-09"));
                } catch (ParseException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}
##结果:
java.lang.NumberFormatException: multiple points
java.lang.NumberFormatException: For input string:""
17:06:50.246 [Thread-9] DEBUG x- parse : Tue Mar 09 00:00:00 CST 2021

##我们可以加锁解决,但【对性能有影响】
 new Thread(()->{
     synchronized(sdf){
         try {
             log.debug("parse : {}",sdf.parse("2021-03-09"));
         } catch (ParseException e) {
             e.printStackTrace();
         }
     }
 }).start();

1.2、不可变类的使用

@Slf4j
public class DateTimeFormatTest {
    public static void main(String[] args) {
        DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd");
        for (int i = 0; i <10 ; i++) {
            new Thread(()->{
                TemporalAccessor accessor = dtf.parse("2021-03-09");
                log.debug("accessor : {}",accessor);
            }).start();
        }
    }
}

1.3、不可变类的设计

##除了上述的DateTimeFormatter是不可变类,String类也是不可变的,以String为例,
说明一下不可变设计的要素:
public final class String
  implements java.io.Serializable, Comparable<String>, CharSequence 
{
    private final char value[];//保存字符串

    private int hash;//缓存hash码

	// ...
}
##一、final的使用
发现该类、类中所有属性都是final的
·属性用final修饰保证了该属性是只读的,不能修改的
·类用final修饰保证了该类中的方法不能被覆盖,防止子类无意间破坏不可变性

##二、保护性拷贝
public String(char value[]) {
	this.value = Arrays.copyOf(value, value.length);
}

通过创建副本对象来避免共享的手段称之为:保护性拷贝

二、享元模式

2.1、享元模式

·Flyweight pattern,当需要重用数量有限的同一类对象时

##体现:
在JDK中Boolean Byte Short Integer Long Character等包装类提供了valueOf方法,
例如Long的valueOf会缓存-128~127之间的Long对象,在这个范围之间会重用对象,大于这个范围才会信件Long对象

public static Long valueOf(long l){
   final int offset = 128;
   if(l >= -128 && l <= 127 ){
     return LongCache.cache[(int)l+offset];                      
   }
   return new Long(l);
}
                          
private static class LongCache {
   private LongCache(){}

   static final Long cache[] = new Long[-(-128) + 127 + 1];

   static {
      for(int i = 0; i < cache.length; i++)
          cache[i] = new Long(i - 128);
      }
}

Byte Short Long缓存的范围都是-128~127
Character缓存范围是0~127
Integer的默认范围是-128~127,最小值不可变,但最大值可以通过调整虚拟机参数来改变
Boolean缓存了TRUE和FALSE

2.2、享元模式-自定义连接池

一个应用,如果每次都重新创建和关闭数据库连接,性能会收到极大影响。这时预先创建好一批连接,
放入连接池。一次请求到达后,从连接池获取连接,使用完毕后在还回连接池,这样既节约了连接的
创建和关闭时间,也实现了连接的重用,不至于让庞大的连接数压垮数据库。
@Slf4j
public class MyConnectPool {

    public static void main(String[] args) {
        ConnectionPool pool = new ConnectionPool(2);
        for (int i = 0; i <5 ; i++) {
            new Thread(()->{
                //获取连接
                Connection conn = pool.gain();
                //使用
                try {
                    Thread.sleep(new Random().nextInt(1000));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //释放连接
                pool.free(conn);
            },"t"+i).start();
        }
    }
}

//连接池
@Slf4j
class ConnectionPool{

    //线程池大小
    private final int poolSize;
    //连接对象数组
    private Connection[] connections;
    //每个连接对象的使用状况--多个线程取修改数组,
    //普通数组线程不安全,因此使用原子数组
    //0 连接空闲 1 连接正在使用
    private AtomicIntegerArray array;

    //初始化线程池大小和连接
    public ConnectionPool(int poolSize) {
        this.poolSize = poolSize;
        this.connections = new Connection[poolSize];
        this.array = new AtomicIntegerArray(new int[poolSize]);
        for (int i = 0; i <poolSize ; i++) {
            connections[i] = new MockConnection();
        }
    }

    //获取空闲连接
    public Connection gain(){
        while (true){
            //获取到空闲连接
            for (int i = 0; i <poolSize ; i++) {
                if(array.get(i)==0){
                    //更改连接状态为1
                    if (array.compareAndSet(i,0,1)) {
                        log.debug("获取连接:{}",i);
                        return connections[i];
                    }
                }
            }
            //没有获取到空闲连接
            synchronized (this){
                try {
                    log.debug("获取连接等待");
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    //归还连接
    public void free(Connection conn){
        for (int i = 0; i <poolSize ; i++) {
            if(connections[i]==conn){
                //更改连接状态为0
                array.set(i,0);
                synchronized (this){
                    log.debug("归还连接:{}",i);
                    this.notifyAll();
                }
                break;
            }
        }
    }
}

//自定义连接
class MockConnection implements Connection{
  //内容省略...   
}
##结果:
11:08:59.956 [t2] XXX - 获取连接等待
11:08:59.956 [t0] XXX - 获取连接:0
11:08:59.956 [t1] XXX - 获取连接:1
11:08:59.973 [t4] XXX - 获取连接等待
11:08:59.973 [t3] XXX - 获取连接等待
11:09:00.334 [t1] XXX - 归还连接:1
11:09:00.334 [t3] XXX - 获取连接等待
11:09:00.334 [t4] XXX - 获取连接:1
11:09:00.334 [t2] XXX - 获取连接等待
11:09:00.836 [t0] XXX - 归还连接:0
11:09:00.836 [t2] XXX - 获取连接:0
11:09:00.836 [t3] XXX - 获取连接等待
11:09:00.951 [t4] XXX - 归还连接:1
11:09:00.951 [t3] XXX - 获取连接:1
11:09:01.401 [t3] XXX - 归还连接:1
11:09:01.732 [t2] XXX - 归还连接:0

##不足:
1> 连接的动态增长与收缩(连接数的动态修改)
2> 连接保活(可用性检测)
3> 等待超时处理
4> 分布式hash

三、线程池

3.1、自定义线程池

3.1.1、阻塞队列

//阻塞队列
@Slf4j
class BlockingQueue<T>{

    //任务队列
    private Deque<T> queue = new ArrayDeque<>();
    //锁
    private ReentrantLock lock = new ReentrantLock();
    //生产者条件变量
    private Condition fullWaitSet = lock.newCondition();
    //消费者条件变量
    private Condition emptyWaitSet = lock.newCondition();
    //队列容量
    private int capacity;

    public BlockingQueue(int capacity) {
        this.capacity = capacity;
    }

    //阻塞获取--带超时的
    public T poll(long timeout, TimeUnit unit){
        lock.lock();
        try {
            long nanos = unit.toNanos(timeout);
            while(queue.isEmpty()){
                try {
                    if(nanos<=0){
                        return null;
                    }
                    nanos = emptyWaitSet.awaitNanos(nanos);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            T t = queue.removeFirst();
            fullWaitSet.signal();
            return t;
        }finally {
            lock.unlock();
        }
    }


    //阻塞获取
    public T take(){
        lock.lock();
        try {
            while(queue.isEmpty()){
                try {
                    emptyWaitSet.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            T t = queue.removeFirst();
            fullWaitSet.signal();
            return t;
        }finally {
            lock.unlock();
        }
    }

    //阻塞添加
    public void put(T t){
        lock.lock();
        try {
            while(queue.size()==capacity){
                try {
                    log.debug("等待...新增任务到阻塞队列,task:{}",t);
                    fullWaitSet.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            log.debug("新增任务到阻塞队列,task:{}",t);
            queue.addLast(t);
            emptyWaitSet.signal();
        }finally {
            lock.unlock();
        }
    }

    //获取容量大小
    public int size(){
        lock.lock();
        try {
            return queue.size();
        }finally {
            lock.unlock();
        }
    }

}

3.1.2、线程池

//线程池
@Slf4j
class ThreadPool{
    //任务队列
    private BlockingQueue<Runnable> taskQueue;
    //线程集合
    private HashSet<Worker> workers = new HashSet<>();
    //核心线程数
    private int coreSize;
    //任务超时时间
    private long timeout;
    //超时时间单位
    private TimeUnit timeUnit;

    public ThreadPool(int coreSize, long timeout, TimeUnit timeUnit, int queueCapacity) {
        this.coreSize = coreSize;
        this.timeout = timeout;
        this.timeUnit = timeUnit;
        taskQueue = new BlockingQueue<>(queueCapacity);
    }

    //执行任务
    public void execute(Runnable task){
        //当任务数没有超过coreSize时,直接执行
        //当任务数超过coreSize时,存储到任务队列taskQueue
        synchronized (workers){
            if(workers.size()<coreSize){
                Worker woker = new Worker(task);
                log.debug("新增任务,task:{}, worker:{}",task,woker);
                workers.add(woker);
                woker.start();
            }else{
                taskQueue.put(task);
            }
        }
    }

    class Worker extends Thread{
        private Runnable task;

        public Worker(Runnable task) {
            this.task = task;
        }

        @Override
        public void run() {
            //当任务不为空时,执行任务
            //当任务为空时,从阻塞队列拿出任务执行
//            while (task!=null || (task = taskQueue.take())!=null){
            while (task!=null || (task = taskQueue.poll(timeout,timeUnit))!=null){
                try{
                    log.debug("执行任务,task:{}",task);
                    task.run();
                }catch (Exception e){
                    e.printStackTrace();
                }finally {
                    task = null;
                }
            }
            synchronized (this){
                log.debug("移除任务,task:{}",this);
                workers.remove(this);
            }
        }
    }
}

3.1.3、测试

@Slf4j
public class MyThreadPool {
    public static void main(String[] args) {
        ThreadPool pool = new ThreadPool(2,1000,TimeUnit.MILLISECONDS,3);
        for (int i=0;i<7;i++){
            int j = i;
            pool.execute(()->{
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                log.debug("任务内容:{}",j);
            });
        }
    }
}
##测试结果:
[main] DEBUG XXX - 新增任务,task:yyy184f6be2, worker:Thread[Thread-0,5,main]
[main] DEBUG XXX - 新增任务,task:yyy2d8f65a4, worker:Thread[Thread-1,5,main]
[main] DEBUG BQ - 新增任务到阻塞队列,task:yyy646d64ab
[main] DEBUG BQ - 新增任务到阻塞队列,task:yyy59e5ddf
[main] DEBUG BQ - 新增任务到阻塞队列,task:yyy536aaa8d
[main] DEBUG BQ - 等待...新增任务到阻塞队列,task:yyye320068
[Thread-0] DEBUG XXX - 执行任务,task:yyy184f6be2
[Thread-1] DEBUG XXX - 执行任务,task:yyy2d8f65a4
[Thread-0] DEBUG MTP - 任务内容:0
[Thread-0] DEBUG XXX - 执行任务,task:yyy646d64ab
[main] DEBUG BQ - 新增任务到阻塞队列,task:yyye320068
[main] DEBUG BQ - 等待...新增任务到阻塞队列,task:yyy76f2b07d
[Thread-1] DEBUG MTP - 任务内容:1
[Thread-1] DEBUG XXX - 执行任务,task:yyy59e5ddf
[main] DEBUG BQ - 新增任务到阻塞队列,task:yyy76f2b07d
[Thread-0] DEBUG MTP - 任务内容:2
[Thread-0] DEBUG XXX - 执行任务,task:yyy536aaa8d
[Thread-1] DEBUG MTP - 任务内容:3
[Thread-1] DEBUG XXX - 执行任务,task:yyye320068
[Thread-0] DEBUG MTP - 任务内容:4
[Thread-0] DEBUG XXX - 执行任务,task:yyy76f2b07d
[Thread-1] DEBUG MTP - 任务内容:5
[Thread-1] DEBUG XXX - 移除任务,task:Thread[Thread-1,5,main]
[Thread-0] DEBUG MTP - 任务内容:6
[Thread-0] DEBUG XXX - 移除任务,task:Thread[Thread-0,5,main]

3.1.4、优化:新增带超时的阻塞队列添加

##阻塞队列BlockingQueue新增 方法:带超时时间的阻塞队列添加
//带超时时间的阻塞添加
public boolean add(T t,long timeout,TimeUnit unit){
    lock.lock();
    try {
        long nanos = unit.toNanos(timeout);
        while(queue.size()==capacity){
            try {
                if(nanos<0){
                    return false;
                }
                log.debug("等待...新增任务到阻塞队列,task:{}",t);
                nanos = fullWaitSet.awaitNanos(nanos);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        log.debug("新增任务到阻塞队列,task:{}",t);
        queue.addLast(t);
        emptyWaitSet.signal();
        return true;
    }finally {
        lock.unlock();
    }
}

3.1.5、优化:拒绝策略

##如果阻塞队列满了怎么办?
·1)死等:上面的put()方法
·2)带超时等待:上面的add()方法
·3)让调用者放弃任务执行
·4)让调用者直接抛出异常
·5)让调用者自己执行任务

##修改方法,将执行任务的接口放开,让调用者自己选择处理方式

拒绝策略接口:

//拒绝策略
@FunctionalInterface
interface rejectPolicy<T>{
    void reject(BlockingQueue<T> queue,T task);
}

线程池新增方法:

//线程池
@Slf4j
class ThreadPool{
    
    //决绝策略
    private RejectPolicy<Runnable> rejectPolicy;
    
    public ThreadPool(int coreSize, long timeout, TimeUnit timeUnit, int queueCapacity,RejectPolicy rejectPolicy) {
        this.coreSize = coreSize;
        this.timeout = timeout;
        this.timeUnit = timeUnit;
        taskQueue = new BlockingQueue<>(queueCapacity);
        this.rejectPolicy = rejectPolicy;
    }
    
    //执行任务
    public void execute(Runnable task){
        //当任务数没有超过coreSize时,直接执行
        //当任务数超过coreSize时,存储到任务队列taskQueue
        synchronized (workers){
            if(workers.size()<coreSize){
                Worker woker = new Worker(task);
                log.debug("新增任务,task:{}, worker:{}",task,woker);
                workers.add(woker);
                woker.start();
            }else{
                //死等
                //taskQueue.put(task);
                //决绝策略
                taskQueue.tryPut(rejectPolicy,task);
            }
        }
    }
}

阻塞队列新增方法:

//阻塞队列
@Slf4j
class BlockingQueue<T>{
    
    //阻塞添加--拒绝策略
    public void tryPut(RejectPolicy<T> rejectPolicy, T task) {
        lock.lock();
        try {
            //队列已满
            if(queue.size()==capacity){
                rejectPolicy.reject(this,task);
            }else{
                //队列未满直接新增
                queue.addLast(task);
            }
        }finally {
            lock.unlock();
        }
    }
}

测试:

@Slf4j
public class MyThreadPool {
    public static void main(String[] args) {
        //ThreadPool pool = new ThreadPool(2,1000,TimeUnit.MILLISECONDS,3);
        ThreadPool pool = new ThreadPool(2,1000,TimeUnit.MILLISECONDS,3,(queue,task)->{
            //1)死等
            //queue.put(task);
            //2)带超时的等待
            //queue.add(task,1000,TimeUnit.MILLISECONDS);
            //3)让调用者放弃任务执行
            //log.debug("自动放弃任务,task{}",task);
            //4)让调用者直接抛出异常
            //throw new RuntimeException("直接抛出异常,task:"+task);
            //5)让调用者自己执行任务
            task.run();
        });
        for (int i=0;i<7;i++){
            int j = i;
            pool.execute(()->{
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                log.debug("任务内容:{}",j);
            });
        }
    }
}

3.2、ThreadPoolExecutor

3.2.1、线程池状态

·ThreadPoolExecutor使用int的高三位表示线程池状态,低29位表示线程数量

·线程状态和线程数量存储在一个原子变量ctl中,目的是将线程池状态与线程个数合二为一,
这样就可以用一次CAS原子操作进行赋值
状态名 高3位 接受新任务 处理阻塞队列任务 说明
RUNNING 111 Y Y
SHUTDOWN 000 N Y 不会接受新任务,但会处理阻塞队列剩余任务
STOP 001 N N 会中断正在执行的任务,并抛弃阻塞队列任务
TIDYING (整理) 010 - - 任务执行完毕,活动线程为0,即将进入终结
TERMINATED 011 - - 终结状态

3.2.2、构造方法

1>ThreadPoolExecutor自带:
public ThreadPoolExecutor(
     int corePoolSize,
     int maximumPoolSize,
     long keepAliveTime,
     TimeUnit unit,
     BlockingQueue<Runnable> workQueue,
     ThreadFactory threadFactory,
     RejectedExecutionHandler handler) {
    
}
名称 作用
corePoolSize 核心线程数目(最多保留的线程数)
maximumPoolSize 最大线程数(核心线程 + 救急线程)
keepAliveTime 生存时间(针对急救线程)
unit 时间单位(针对急救线程)
workQueue 阻塞队列
threadFactory 线程工厂(可以在线程创建时起个好名)
handler 拒绝策略

##工作方式:

·线程池中刚开始没有线程,当一个任务提交给线程池后,线程池会创建一个新线程(核心线程)
来执行任务

·当线程数达到corePoolSize并没由线程空闲时,再加入新任务,新加的任务会被加入
workQueue阻塞队列排队,等待空闲的线程(空闲的核心线程)

·如果队列选择了有界队列,那么任务超过了队列大小时,会创建
maximumPoolSize-corePoolSize数目的线程来救急(救急线程)

·如果线程到达maxumumPoolSize仍然有新任务这是会执行拒绝策略。拒绝策略JDK提供了4种
实现,其他框架也提供了实现:
    >AbortPolicy让调用者抛出RejectExecutionException异常,这是默认策略
    >CallerRunsPolicy让调用者执行任务
    >DiscardPolicy放弃本次任务
    >DiscardOldestPolicy放弃队列中最早的任务,本任务取而代之

·当高峰过去后,超过corePoolSize的救急线程如果一段时间没有任务做,需要结束以节省资源,
这个时间有keepAliveTime和unit来控制

·核心线程执行完任务不会主动的结束自己,仍在运行

2>Executors工具类:
根据上述构造方法,JDK Executors类中提供了众多工厂方法来创建各种用途的线程池:

newFixedThreadPool :

#①、 newFixedThreadPool(int nThreads)
 特点:核心线程数 == 最大线程数,没有救急线程被创建,也无需超时
      阻塞队列是无边界的,可以放任意数量的任务
    
 【适用于任务量已知,相对耗时的任务】

newCachedThreadPool :

#②、newCachedThreadPool()
  特点:核心线程数是0,最大线程数是Integer.MAX_VALUE,救急线程的空闲生存时间是60s,
       意味着:
          全都是救急线程(空闲60s后可以回收)
          救急线程可以无限创建
       队列采用了SynchronousQueue实现特点是,它没有容量,
       它在没有线程来取时任务是放不进去的(现拉现吃)
 【使用任务数比较密集,但每个任务执行时间较短】

newSingleThreadExecutor :

#③、newSingleThreadExecutor()
  特点:只有1个核心线程,没有救急线程,也无需超时
      阻塞队列是无边界的,可以放任意数量的任务
 【希望多个任务排队执行】
  与自己只创建一个线程执行任务的区别:
  自己创建一个单线程串行执行任务,如果任务执行失败或者抛出异常而终止那么没有任何补救措施
而线程池还会充县创建一个线程保证池的正常工作,后续任务仍会照常执行

3.2.3、线程池提交任务

//执行任务---无返回结果
void execute(Runnable command);
    
//提交任务task,返回值Future获得任务执行结果
<T> Future<T> submit(callable<T> task);

//提交tasks中所有任务
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks);

//提交tasks只能够所有任务,带超时时间
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
                      long timeout, TimeUnit unit);

//提交tasks只能够所有任务,哪个任务先成功执行完毕,返回此任务执行结果,其他任务取消
<T> T invokeAny(Collection<? extends Callable<T>> tasks);

//提交tasks只能够所有任务,哪个任务先成功执行完毕,返回此任务执行结果,其他任务取消
//带超时时间
<T> T invokeAny(Collection<? extends Callable<T>> tasks,
                      long timeout, TimeUnit unit)

测试execute:

private static void execute(){
    ExecutorService executorService = Executors.newFixedThreadPool(2);
    executorService.execute(()->{
        log.debug("execute ...");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    });
}
##结果
14:11:24.523 [pool-1-thread-1] DEBUG  - execute ...

测试submit

private static void submit()  {
    ExecutorService executorService = Executors.newFixedThreadPool(2);
    Future<String> future = executorService.submit(()->{
        log.debug("submit...");
        Thread.sleep(1000);
        return "ok";
    });
    log.debug("future:{}",future.get());
}
##结果
14:12:49.785 [pool-1-thread-1] DEBUG - submit...
14:12:50.787 [main] DEBUG - future:ok

测试invokeAll

private static void invokeAll() throws InterruptedException {
    ExecutorService executorService = Executors.newFixedThreadPool(2);
    List<Future<Object>> futureList = executorService.invokeAll(Arrays.asList(
        () -> {
            log.debug("begin 1");
            Thread.sleep(1000);
            return "ok 1";
        },
        () -> {
            log.debug("begin 2");
            Thread.sleep(3000);
            return "ok 2";
        },
        () -> {
            log.debug("begin 3");
            Thread.sleep(500);
            return "ok 3";
        }
    ));
    futureList.forEach(t->{
        try {
            log.debug("result : {}",t.get());
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
    });
}
##结果
14:29:57.558 [pool-1-thread-2] DEBUG xxx - begin 2
14:29:57.558 [pool-1-thread-1] DEBUG xxx - begin 1
14:29:58.561 [pool-1-thread-1] DEBUG xxx - begin 3
14:30:00.561 [main] DEBUG xxx - result : ok 1
14:30:00.562 [main] DEBUG xxx - result : ok 2
14:30:00.562 [main] DEBUG xxx - result : ok 3

invokeAny测试

private static void invokeAny(){
    ExecutorService executorService = Executors.newFixedThreadPool(2);
    String res = executorService.invokeAny(Arrays.asList(
        () -> {
            log.debug("begin 1");
            Thread.sleep(1000);
            return "ok 1";
        },
        () -> {
            log.debug("begin 2");
            Thread.sleep(3000);
            return "ok 2";
        },
        () -> {
            log.debug("begin 3");
            Thread.sleep(500);
            return "ok 3";
        }
    ));

    log.debug("result : {}",res);
}
##结果
14:36:50.198 [pool-1-thread-1] DEBUG xxx - begin 1
14:36:50.198 [pool-1-thread-2] DEBUG xxx - begin 2
14:36:51.200 [pool-1-thread-1] DEBUG xxx - begin 3
14:36:51.200 [main] DEBUG xxx - result : ok 1

3.2.4、关闭线程池

/**
* 线程池状态变为SHUTDOWN
*  - 不会接收新任务
*  - 但已提交任务会执行完
*  - 此方法不会阻塞调用线程的执行
**/
void shutdown();

/**
* 线程池状态变为STOP
*  - 不会接收新任务
*  - 会将队列中的任务返回
*  - 并用interrupt的方式中断正在执行的任务
**/
List<Runnable> shutdownNow();

//其他方法

//只要不是RUNNING状态,此方法就会返回TRUE
boolean isShutdown();

//线程池状态是否是TERMINATED
boolean isTerminated();

//调用shutdown后,由于调用线程并不会等待所有任务结束,
//因此如果它想在线程池TERMINATED后做些事情,可以利用此方法等待
boolean awaitTermination(long timeout,TimeUnit unit);

3.3、设计模式:工作线程

##定义:

·让有限的工作线程来轮流异步处理无限多的任务,也可以将其归类为分工模式。
它的典型实现就是线程池,也体现了经典设计模式中的享元模式

·例如,海底捞服务员(线程),轮流处理每位客人的点餐(任务),如果为每位客人都配一名专属
服务员,成本就太高了。

·【不同任务类型应该使用不同的线程池】,这样能够避免饥饿,并能提升效率。

·例如,如果参观的工人纪要招呼客人(task A),又要到后出做饭(task B)显然效率不咋地,
分成服务员(线程池A)与厨师(线程池B)更为合理。

##饥饿:

·固定大小线程池会有饥饿现象(线程数量不足导致的)

>两个工人都是同一个线程池中的两个线程
>他们要做的事情是:为客人点餐和到后厨做菜,这是两个阶段的工作
  >>客人点餐:必须先点完餐,等菜做好,上菜,在此期间处理点餐的工人必须等待
  >>后厨做菜:做就是了

·比如工人A处理了点餐任务,接下来他要等着工人B把菜做好,然后上菜
·但现在同时来了两个客人,这个时候工人A和工人B都去处理点餐了,这时没人做饭,死锁

问题:

@Slf4j
public class ThreadPoolHungry {

    public static void main(String[] args) {
        //两个服务员--又点餐又做菜
        ExecutorService executorService = Executors.newFixedThreadPool(2);

        //服务员1--服务客人1
        executorService.execute(()->{
            log.debug("1号客人点餐:宫保鸡丁");
            Future<String> future = executorService.submit(() -> {
                log.debug("为1号客人做菜");
                return "宫保鸡丁";
            });
            try {
                log.debug("为1号客人上菜:{}",future.get());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        });


        //服务员2--服务客人2
        executorService.execute(()->{
            log.debug("2号客人点餐:糖醋里脊");
            Future<String> future = executorService.submit(() -> {
                log.debug("为2号客人做菜");
                return "糖醋里脊";
            });
            try {
                log.debug("为2号客人上菜:{}",future.get());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        });
    }
}
##结果:
16:03:12.593 [pool-1-thread-1] DEBUG xxx - 1号客人点餐:宫保鸡丁
16:03:12.593 [pool-1-thread-2] DEBUG xxx - 2号客人点餐:糖醋里脊

解决:

@Slf4j
public class ThreadPoolHungrySolve {

    public static void main(String[] args) {
        //两个服务员--又点餐又做菜
        ExecutorService waiterPool = Executors.newFixedThreadPool(1);
        ExecutorService chefPool = Executors.newFixedThreadPool(1);

        //服务员1--服务客人1
        waiterPool.execute(()->{
            log.debug("1号客人点餐:宫保鸡丁");
            Future<String> future = chefPool.submit(() -> {
                log.debug("为1号客人做菜");
                return "宫保鸡丁";
            });
            try {
                log.debug("为1号客人上菜:{}",future.get());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        });


        //服务员2--服务客人2
        waiterPool.execute(()->{
            log.debug("2号客人点餐:糖醋里脊");
            Future<String> future = chefPool.submit(() -> {
                log.debug("为2号客人做菜");
                return "糖醋里脊";
            });
            try {
                log.debug("为2号客人上菜:{}",future.get());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        });
    }
}
##结果:
16:08:27.927 [pool-1-thread-1] DEBUG xxx - 1号客人点餐:宫保鸡丁
16:08:27.930 [pool-2-thread-1] DEBUG xxx - 为1号客人做菜
16:08:27.930 [pool-1-thread-1] DEBUG xxx - 为1号客人上菜:宫保鸡丁
16:08:27.931 [pool-1-thread-1] DEBUG xxx - 2号客人点餐:糖醋里脊
16:08:27.932 [pool-2-thread-1] DEBUG xxx - 为2号客人做菜
16:08:27.932 [pool-1-thread-1] DEBUG xxx - 为2号客人上菜:糖醋里脊

3.4、线程池创建多少线程合适

·创建线程过少导致程序不能充分地利用资源,容易导致饥饿
·创建线程过多导致更多的线程上下文切换(时间片),占用更多内存

##CPU密集型运算
通常采用【CPU核数+1】能够实现最优的CPU利用率
+1是保证当线程由于也确实故障或其他原因导致暂停时,额外的这个线程就能顶上去,
保证CPU时钟周期不被浪费

##IO密集型运算
CPU不总是处于繁忙状态,例如,当你执行业务计算时,这时候会使用CPU资源,当执行IO操作时,远程RPC调用时,包括进行数据库操作时,这时CPU就闲下来了,可以利用多线程提高它的利用率

经验公式如下:
【线程数 = CPU核数 * 期望CPU利用率 * 总时间(CPU利用时间+等待时间)/ CPU计算时间】

3.5、任务调度线程池

3.5.1、Timer的缺点

在【任务调度线程池】功能加入之前,可以使用java.util.Timer来实现定时功能,Timer的
有点在于简单易用,但由于所有任务都是由同一个线程来调度,因此所有任务都是串行执行的,
同一时间只能有一个任务在执行,前一个任务的延迟或异常都将会影响到之后的任务。
@Slf4j
public class TimerScheduleTask {
    public static void main(String[] args) {
        Timer timer = new Timer();

        TimerTask task1 = new TimerTask() {
            @Override
            public void run() {
                log.debug("任务1");
                int i = 1/0;
            }
        };

        TimerTask task2 = new TimerTask() {
            @Override
            public void run() {
                log.debug("任务2");
            }
        };

        timer.schedule(task1,1000);
        timer.schedule(task2,1000);
    }
}
##结果:
16:35:18.227 [Timer-0] DEBUG - 任务1
Exception in thread "Timer-0" java.lang.ArithmeticException: / by zero
	at com.lee.juc3.TimerScheduleTask$1.run(TimerScheduleTask.java:17)
	at java.util.TimerThread.mainLoop(Timer.java:555)
	at java.util.TimerThread.run(Timer.java:505)

##任务1的异常,影响了任务2的执行

3.5.2、ScheduledThreadPoolExecutor

//常用方法Excutors
      
//任务调度
schedule(任务,初始延时,时间单位);

//以固定的速率执行任务,每隔x个时间间隔1执行一次
//如果任务执行需要3s,时间间隔1是1s,那么两个任务之间的间隔是3s
scheduleAtFixedRate(任务,初始延时,时间间隔1,时间单位);

//以固定的速率执行任务,每隔x个时间间隔2执行一次
//如果任务执行需要3s,时间间隔2是1s,那么两个任务之间的间隔是4s
scheduleWithFixedRate(任务,初始延时,时间间隔2,时间单位);
@Slf4j
public class ScheduledThreadPool {
    public static void main(String[] args) {
        ScheduledExecutorService pool = Executors.newScheduledThreadPool(1);

        pool.schedule(()->{
            log.debug("任务1");
            int i=1/0;
        },1, TimeUnit.SECONDS);

        pool.schedule(()->{
            log.debug("任务2");
        },1, TimeUnit.SECONDS);
       
    }
}
##结果:
16:41:26.084 [pool-1-thread-1] DEBUG  - 任务1
16:41:26.087 [pool-1-thread-1] DEBUG  - 任务2

##任务1的异常没有影响任务2的执行,但任务1的异常没有打印

正确处理线程池异常:

//自己处理try-catch
pool.schedule(()->{
    log.debug("任务1");
    try{
      int i=1/0;
    }catch(Exception e){
      log.error("error:{}",e);   
    }
},1, TimeUnit.SECONDS);

//使用Future
Future<Boolean> f = pool.submit(()->{
    log.debug("任务1");
    try{
      int i=1/0;
    }catch(Exception e){
      log.error("error:{}",e);   
    }
    return true;
},1, TimeUnit.SECONDS);

//future会自己抛出异常
log.debug("result:{}",f.get());

3.5.3、定时任务应用

##每周四18:00:00定时执行任务
/**
 * 定时任务:每周四 18:00:00执行任务
 */
@Slf4j
public class ScheduleTask {

    public static void main(String[] args) {
        //获取当前时间
        LocalDateTime nowTime = LocalDateTime.now();
        //获取周四时间
        LocalDateTime thursdayTime = nowTime.withHour(18).withMinute(0).withSecond(0).withNano(0).with(DayOfWeek.THURSDAY);
        //如果当前时间大于周四获取下周四
        if(nowTime.compareTo(thursdayTime)>0){
            thursdayTime = thursdayTime.plusWeeks(1);//增加一个星期
        }
        //获取时间差转化成毫秒
        long initialDelay = Duration.between(nowTime, thursdayTime).toMillis();
        //每隔1星期执行一次
        long period = 1000*60*60*24*7;
        
        //定时任务
        ScheduledExecutorService pool = Executors.newScheduledThreadPool(1);
        pool.scheduleWithFixedDelay(() -> {
            try {
                log.debug("定时任务");
            } catch (Exception e) {
                log.error("error:{}",e);
            }
        },initialDelay,period, TimeUnit.MILLISECONDS);
    }
}

3.6、Fork-Join

##一、概念:

Fork-join是JDK7加入的新的线程池实现,它体现的是一种分治思想,适用于能够进行任务拆分的
CPU密集型运算

所谓的任务拆分,是将一个大任务拆分为算法上相同的小任务,直至不能拆分可以直接求解。跟递归
相关的一些计算,如归并排序、斐波那契数列都可以用分治思想进行求解

Fork-join在分治的基础上加入了多线程,可以把每个人物的分解和合并交给不同的线程来完成。

Fork-join默认会创建于CPU核心数大小相同的线程池

##二、使用:

提交给Fork-join的任务必须要继承RecursiveTask(有返回值)或RecursiveAction(无返回
值)两个类 
@Slf4j
public class ForkJoinTest {
    public static void main(String[] args) {
        ForkJoinPool pool = new ForkJoinPool();
        Integer res = pool.invoke(new AddTask(5));
        log.debug("res:{}",res);

        //拆分
        //AddTask(5)=5+AddTask(4)
        //AddTask(4)=4+AddTask(3)
        //...
        //AddTask(2)=2+AddTask(1)
        //AddTask(1)=1
    }
}

//计算1+2+3~+N
@Slf4j
class AddTask extends RecursiveTask<Integer>{

    private Integer n;

    public AddTask(Integer n) {
        this.n = n;
    }

    @Override
    protected Integer compute() {
        if(n==1){
            return 1;
        }

        AddTask addTask = new AddTask(n - 1);
        addTask.fork();//将addTask任务交给新的线程

        Integer res = n+addTask.join();//获取任务结果
        return res;
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章