余瑜的博客 余瑜的博客
首页
  • 并发
  • 线程池
  • spring
  • maven
  • 其他
  • redis
  • mysql
  • linux
  • zookeeper
  • docker
  • terminal
  • kong插件开发
  • 资料
  • leetCode-简单
  • blog
  • 其他
关于
GitHub (opens new window)
首页
  • 并发
  • 线程池
  • spring
  • maven
  • 其他
  • redis
  • mysql
  • linux
  • zookeeper
  • docker
  • terminal
  • kong插件开发
  • 资料
  • leetCode-简单
  • blog
  • 其他
关于
GitHub (opens new window)
  • 并发

  • 线程池

    • 浅谈JAVA线程池工作原理
      • 前言
      • 分析
      • 实现一个简单的线程池
      • 调用
      • 警惕
    • 浅谈JAVA线程返回值工作原理
    • FutureTask
    • ThreadPoolExecutor原理
    • 线程池顶级类及其接口
    • ThreadPoolExecutor源码
    • ExecutorCompletionService源码
  • spring

  • maven

  • 其他

  • JAVA
  • 线程池
余瑜
2020-02-26
目录

浅谈JAVA线程池工作原理

# 前言

可以先看看这篇博客: JAVA中的线程和操作系统中的线程的关系 (opens new window),从这里面我们可以直到以下几点

  1. Thread类调用start方法会通过jvm调用系统底层函数创建线程。
  2. 创建完一个新的线程后, 新的线程会回调我们的run方法
  3. 创建线程很麻烦,开销很大

以下代码均为伪代码,目的是提供思路

# 分析

final List list = new ArrayList();
new Thread(new Runnable() {
  @Override
  public void run() {
    fun(list);
  }
}).start();
1
2
3
4
5
6
7

我们的run方法是写在一个实现Runable接口的对象中的, 因为该对象是一个匿名内部类, 所以传入的参数list的引用地址会被赋值到匿名内部类的一个虚拟成员变量中, 我们可以理解如下:

class A implements Runnable{
  List list;
  public void A(List list){
    this.list = list;
  }
  void run(){
    fun(list);
  }
}
1
2
3
4
5
6
7
8
9

新的线程会调用我们实例化出来的类A的run();执行完run()线程关闭;

可以看出来, 废了那么大的劲, 我们的目的就是为了调用run(),而该方法执行时的参数都在我们的匿名内部类A中, 那我们完全可以将A存到一个列队里,我们新建出来线程池后不销毁, 直接从列队中获取到A然后调用A的run()方法就好了

# 实现一个简单的线程池

class ThreadPool{
  // 一个阻塞列队
  ArrayBlockingQueue<Runnable> que ;
  // 创建出来的线程,为了简化我这里只初始化进来一个线程
  Thread thread ;
	// 构造方法
  public ThreadPool() {
    // 初始化列队
    que = new ArrayBlockingQueue<Runnable>(10);
    // 初始化线程
    this.thread = new Thread(new Runnable() {
      @Override
      public void run() {
        // 创建出来的线程不销毁,一致占用着遍历列队, 列队中有值后就获取Runnable,并调用run()
        while (true){
          try {
            Runnable runnable = que.take();
            runnable.run();
          } catch (InterruptedException e) {
            e.printStackTrace();
          }
        }
      }
    }) ;
  }
	
  // 向线程池中加入任务的时候,直接扔到列队里, 让初始化的那个线程去扫描
  void execute(Runnable command){
    try {
      que.put(command);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

# 调用

public static void main(String[] args) {
  ThreadPool pool = new ThreadPool();

  List<String> list = new ArrayList();
  for (String s : list) {
    pool.execute(new Runnable() {
      @Override
      public void run() {
        System.out.println(s);
      }
    });
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13

# 警惕

参数传入匿名内部类后,加入修改了包装对象中的值, 而匿名内部类中保存的是地址值, 所以获取到的也是新的值,而我们无法保证线程池中线程的执行时间, 所以该包装对象千万不要修改!

public static void a(){
  final List<Integer> list = new ArrayList();
  list.add(1);
  new Thread(new Runnable() {
    @Override
    public void run() {
      TimeUnit.SECONDS.sleep(2);
      list.add(3);
      System.out.println("run" + list);
    }
  }).start();
  TimeUnit.SECONDS.sleep(1);
  list.add(2);
  System.out.println("main"+list);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

结果如下

main[1, 2]
run[1, 2, 3]
1
2
上次更新: 2021/02/20, 19:26:07

← jdk1.7-ConcurrentHashMap 浅谈JAVA线程返回值工作原理→

Theme by Vdoing | Copyright © 2018-2022 逆光世间 | 备案号: 京ICP备19016086号
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式