多线程与JUC

多线程与JUC

线程的创建

  1. 创建线程使用thread(不推荐)
@Slf4j(topic = "c.t")
public class Demo2 {

    public static void main(String[] args) {
        Thread thread = new Thread() {
            @Override
            public void run() {
                log.info("子线程");
            }
        };
        thread.start();
        log.info("主线程");
    }
}
  1. 创建线程使用runnable(推荐)
@Slf4j(topic = "c.t3")
public class Demo3 implements Runnable {

    @Override
    public void run() {
        log.info("子线程");
    }

    public static void main(String[] args) {
        Demo3 demo3 = new Demo3();
        Thread thread = new Thread(demo3);
        thread.start();
        log.info("主线程");
    }
}

java8之后支持函数式编程

可实现多线程为lambda表达式

@Slf4j(topic = "c.t1")
public class Demo1 {
 public static void main(String[] args) {
     new Thread(() -> {log.info("子线程");}).start();
     log.info("主线程");
 }
}
  1. 创建线程FutureTask
@Slf4j(topic = "c.t4")
public class Demo4 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        FutureTask<Integer> task = new FutureTask<>(new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                log.info("子线程");
                Thread.sleep(1000);
                return 100;
            }
        });
        Thread thread = new Thread(task);
        thread.start();
        Integer integer = task.get();//阻塞
        log.info("主线程获取{}", integer);
    }
}

⚠️ Futuretask间接实现了Runnable方法与Future接口支持子线程数据的返回

线程中的方法

synchronize

死锁相互等待 循环饮用

定位死锁 jps jstack

JUC中主要的工具类

CountDownLatch

public class Demo1 {
    /**
     * 需求 开器多个线程时需要主线程等待线程全部结束后再继续执行
     * 1. join 简单实现是线程顺序执行
     * 2. @CountDownLatch 实现
     * <p>
     * 代码实现 保安最后关门离开
     */
    public static void main(String[] args) throws InterruptedException {
        int p = 10;
        CountDownLatch countDownLatch = new CountDownLatch(p);
        for (int i = 1; i <= p; i++) {
            new Thread(() -> {
                String name = Thread.currentThread().getName();
                log.info(name + "已经离开商场");
                countDownLatch.countDown();
            }, i + "线程").start();
        }
        countDownLatch.await();
        log.info("保安离开 并且关闭商场的门");
    }
}

CyclicBarrier

public class Demo2 {
    /**
     * 需求 每运行N次线程程序之后 进行主线程的执行
     * <p>
     * P人开会
     */
    public static void main(String[] args) throws BrokenBarrierException, InterruptedException {
        int p = 10;
        CyclicBarrier cyclicBarrier = new CyclicBarrier(p, () -> log.info("人已经到齐 开始会议"));

        for (int i = 1; i <= p; i++) {
            final int temp = i;
            new Thread(() -> {
                String name = Thread.currentThread().getName();
                log.info("{}已经到位,{}号人来了", name, temp);
                try {
                    int await = cyclicBarrier.await();
                } catch (InterruptedException | BrokenBarrierException e) {
                    e.printStackTrace();
                }

            }, i + "线程").start();
        }
    }
}

Semaphore

public class Demo3 {
    /**
     * java 信号量 著名哲学家问题 停车场问题
     */
    public static void main(String[] args) {
        int p = 10;
        Semaphore semaphore = new Semaphore(2);
        for (int i = 1; i <= p; i++) {
            new Thread(() -> {
                try {
                    semaphore.acquire();
                    String name = Thread.currentThread().getName();
                    log.info("{}号🚗进去停车场", name);
                    Thread.sleep(6000);
                    log.info("{}号🚗🚗🚗🚗🚗🚗🚗🚗🚗🚗🚗出停车场", name);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    semaphore.release();
                }
            }, String.valueOf(i)).start();
        }
        semaphore.release();
    }
}

线程池创建

Executors

Executors: 对 ThreadPoolExecutorScheduledThreadPoolExecutor 封装的工具类,方便创建线程池。但是不推荐使用。应该使用ThreadPoolExecutor

线程池提交任务的种类

往线程池中提交任务,主要有两种方法,execute()submit()

  1. execute 提交的线程无返回结果
  2. submit 提交的线程对象有返回结果 可以通过Future接收返回 通过get方法获取 但是get方法是阻塞的方法 get(long timeout, TimeUnit unit)的重载对象获取,时间超过会抛出TimeoutException

关闭线程池

在线程池使用完成之后,我们需要对线程池中的资源进行释放操作,这就涉及到关闭功能。我们可以调用线程池对象的shutdown()shutdownNow()方法来关闭线程池。

这两个方法都是关闭操作,又有什么不同呢?

  1. shutdown()会将线程池状态置为SHUTDOWN,不再接受新的任务,同时会等待线程池中已有的任务执行完成再结束。
  2. shutdownNow()会将线程池状态置为SHUTDOWN,对所有线程执行interrupt()操作,清空队列,并将队列中的任务返回回来。

另外,关闭线程池涉及到两个返回boolean的方法,isShutdown()isTerminated,分别表示是否关闭和是否终止。

线程池监控

如果系统中大量用到了线程池,那么我们有必要对线程池进行监控。利用监控,我们能在问题出现前提前感知到,也可以根据监控信息来定位可能出现的问题。

首先,ThreadPoolExecutor自带了一些方法。

  1. long getTaskCount(),获取已经执行或正在执行的任务数
  2. long getCompletedTaskCount(),获取已经执行的任务数
  3. int getLargestPoolSize(),获取线程池曾经创建过的最大线程数,根据这个参数,我们可以知道线程池是否满过
  4. int getPoolSize(),获取线程池线程数
  5. int getActiveCount(),获取活跃线程数(正在执行任务的线程数)

其次,ThreadPoolExecutor留给我们自行处理的方法有3个,它在ThreadPoolExecutor中为空实现(也就是什么都不做)。

  1. protected void beforeExecute(Thread t, Runnable r) // 任务执行前被调用
  2. protected void afterExecute(Runnable r, Throwable t) // 任务执行后被调用
  3. protected void terminated() // 线程池结束后被调用

四种基本线程池

  1. newFixedThreadPool

    public class Demo1 {
        public static void main(String[] args) {
            //创建固定大小的线程池
            Executor executor = Executors.newFixedThreadPool(5);
            for (int i = 0; i < 10; i++) {
                executor.execute(() -> {
                    log.info("线程运行{}", Thread.currentThread().getName());
                    try {
                        Thread.sleep(4000L);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                });
            }
        }
    }
    
    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }
    

    ⚠️创建了线程固定的线程池 不推荐使用 线程浪费与线程OOM同时存在 存在风险

  2. newSingleThreadExecutor

    public class Demo2 {
        public static void main(String[] args) {
            //创建单线程池
            Executor executor = Executors.newSingleThreadExecutor();
            for (int i = 0; i < 11; i++) {
                executor.execute(() -> {
                    log.info("线程运行{}", Thread.currentThread().getName());
                    try {
                        Thread.sleep(4000L);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                });
            }
        }
    }
    
    
    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));  //最大长度为int类型最大值
    }
    

    ⚠️创建单线称池 累计大量任务造成堆溢出

  3. newCachedThreadPool

    public class Demo3 {
        public static void main(String[] args) {
            //创建缓存线程池
            Executor executor = Executors.newCachedThreadPool();
            for (int i = 0; i < 1111; i++) {
                executor.execute(() -> {
                    log.info(Thread.currentThread().getName());
                });
            }
        }
    }
    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }
    

    创建一个有60s缓存的线程池。该线程可以根据需要智能的创建新的线程,或者重用空闲但未过缓存期的线程。
    如果线程池中有超过缓存期的线程(60s不执行任务),该闲置的线程将会被终止并从线程池中移除。

    不过,该线程池的最大线程数量是 Integer.MAX_VALUE,极端情况下,可能会推积大量请求,从而导致OOM。

  4. newScheduledThreadPool

      public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
            return new ScheduledThreadPoolExecutor(corePoolSize);
        }
    

    ⚠️创建一个支持定时及周期性的任务执行的线程池,多数情况下可用来替代Timer类。

    不过,该线程池的最大线程数量是 Integer.MAX_VALUE,极端情况下,可能会推积大量请求,从而导致OOM。

ThreadPoolExecutor解读

  1. 构造方法

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler)
    
  2. 字段含义

    1. corePoolSize 线程基础数量
    2. maxmumPoolSize 线程最大数量
    3. keepAliveTime 存活时间
    4. unit 存活时间单位
    5. workqueue 存储任务的队列
    6. threadFactory 线程工厂
    7. handler 拒绝策略
  3. 字段解读

    1. 字面意思

    2. 字面意思

      1. 任务的性质:CPU密集型、IO密集型和混杂型
      2. 任务的优先级:高中低
      3. 任务执行的时间:长中短
      4. 任务的依赖性:是否依赖数据库或者其他系统资源

      不同的性质的任务,我们采取的配置将有所不同。在《Java并发编程实践》中有相应的计算公式。

      通常来说,如果任务属于CPU密集型,那么我们可以将线程池数量设置成CPU的个数,以减少线程切换带来的开销。如果任务属于IO密集型,我们可以将线程池数量设置得更多一些,比如CPU个数*2。

      PS:我们可以通过Runtime.getRuntime().availableProcessors()来获取CPU的个数。

    3. 字面意思

    4. 字面意思

    5. 等待队列

      等待队列是BlockingQueue类型的,理论上只要是它的子类,我们都可以用来作为等待队列。

      1. ArrayBlockingQueue,队列是有界的,基于数组实现的阻塞队列
      2. LinkedBlockingQueue,队列可以有界,也可以无界。基于链表实现的阻塞队列
      3. SynchronousQueue,不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作将一直处于阻塞状态。该队列也是Executors.newCachedThreadPool()的默认队列
      4. PriorityBlockingQueue,带优先级的无界阻塞队列
    6. 线程工厂

      public interface ThreadFactory {
      
          /**
           * Constructs a new {@code Thread}.  Implementations may also initialize
           * priority, name, daemon status, {@code ThreadGroup}, etc.
           *
           * @param r a runnable to be executed by new thread instance
           * @return constructed thread, or {@code null} if the request to
           *         create a thread is rejected
           */
          Thread newThread(Runnable r);
      }
      

      Executors的实现使用了默认的线程工厂-DefaultThreadFactory。它的实现主要用于创建一个线程,线程的名字为pool-{poolNum}-thread-{threadNum}

      static class DefaultThreadFactory implements ThreadFactory {
          private static final AtomicInteger poolNumber = new AtomicInteger(1);
          private final ThreadGroup group;
          private final AtomicInteger threadNumber = new AtomicInteger(1);
          private final String namePrefix;
      
          DefaultThreadFactory() {
              SecurityManager s = System.getSecurityManager();
              group = (s != null) ? s.getThreadGroup() :
                                    Thread.currentThread().getThreadGroup();
              namePrefix = "pool-" +
                            poolNumber.getAndIncrement() +
                           "-thread-";
          }
      
          public Thread newThread(Runnable r) {
              Thread t = new Thread(group, r,
                                    namePrefix + threadNumber.getAndIncrement(),
                                    0);
              if (t.isDaemon())
                  t.setDaemon(false);
              if (t.getPriority() != Thread.NORM_PRIORITY)
                  t.setPriority(Thread.NORM_PRIORITY);
              return t;
          }
      }
      

      很多时候,我们需要自定义线程名字。我们只需要自己实现ThreadFactory,用于创建特定场景的线程即可。

    7. 拒绝策略

      1. CallerRunsPolicy // 在调用者线程执行
      2. AbortPolicy // 直接抛出RejectedExecutionException异常
      3. DiscardPolicy // 任务直接丢弃,不做任何处理
      4. DiscardOldestPolicy // 丢弃队列里最旧的那个任务,再尝试执行当前任务

      这四种策略各有优劣,比较常用的是DiscardPolicy,但是这种策略有一个弊端就是任务执行的轨迹不会被记录下来。所以,我们往往需要实现自定义的拒绝策略, 通过实现RejectedExecutionHandler接口的方式。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值