美文网首页
Java 线程池

Java 线程池

作者: 云师兄 | 来源:发表于2018-11-04 19:37 被阅读13次

引子

最近在学习Netty的过程中,发现NioEventLoop中涉及到很多java线程池相关的概念,为了能更好的理解这些内容,为此在回过头来专门去了解线程池的相关内容,在学习过程中,顺便在这篇博客中做下记录。

参考资料

  • 《Java 并发编程:核心方法与框架》(这本书主要讲线程池相关的API)
  • 《Java多线程编程核心技术》(这本书主要讲线程相关的API,这两本书都是一个作者写的,优点在于例子多,讲的浅显易懂,后续将会针对这本书做些阅读后的体会)

线程池

在JDK5中提供了线程池的支持,来支持高并发的访问处理,对线程对象进行复用。在线程池ThreadPool中支持线程对象管理(创建和销毁),使用池时只需要执行具体的任务即可,线程对象的处理都在池中被封装了。

Executor接口

  • 与线程池相关的大部分类都基于此接口实现。它只有一个execute方法。
  • ExecutorService是Executor的子接口,添加了一些其他方法定义,但还是不能实例化,它的唯一实现类为AbstractExecutorService,是个抽象类。
  • AbstractExecutorService抽象类的子类有ThreadPoolExecutor类。
  • 实现类ThreadPoolExecutor可进行实例化进而使用相关功能。

这个接口完整的继承关系如下图所示:


image.png

使用Executors工厂类创建线程池

上面讲到ThreadPoolExecutor作为接口实现类,但使用起来不是很方便,考虑到参数传递,线程并发数等参数设置,官方建议使用Executors工厂类来创建线程池对象。下面就来讨论Executors这个工厂类的一些常用API。

  • newCachedThreadPool() 创建一个无界线程池。无界指的是线程池中的线程数量最大值为
    Integer.MAX_VALUE(很大)。
        ExecutorService executorService = Executors.newCachedThreadPool();
        for
        executorService.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println("run!");
            }
        });

上述示例执行execute方法后从线程池中创建一个新线程。

  • 线程池可以异步创建多个线程,示例如下:
        ExecutorService executorService = Executors.newCachedThreadPool();
        executorService.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println("run A!");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("end A!");
            }
        });
        executorService.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println("run B!");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("end B!");
            }
        });

上述线程A和B几乎是异步同时创建,结果如下:

run A!
run B!
end A!
end B!
  • 线程池对创建的线程对象支持复用。下面是个具体的例子:
public class Server {
    public static void main(String[] args) throws Exception {

        ExecutorService executorService = Executors.newCachedThreadPool();
        for(int i=0;i<5;i++){
            executorService.execute(new MyRunnable(Integer.toString(i)));
        }

        Thread.sleep(1000);
        System.out.println("");

        for(int i=0;i<5;i++){
            executorService.execute(new MyRunnable(Integer.toString(i)));
        }
    }
}
public class MyRunnable implements Runnable {
    private String username;
    public MyRunnable(String username){
        this.username = username;
    }
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + " username="+ username + " begin" + System.currentTimeMillis());
        System.out.println(Thread.currentThread().getName() + " username="+ username + " end" + System.currentTimeMillis());
    }
}

上述示例中使用了分别两次创建了5个线程,如果线程池没有复用线程对象,那么实际打印处理应该有10个不一样的线程,而实际打印结果显示最终很多线程名称是重复的,即先后创建的线程是支持复用的。

pool-1-thread-3 username=2 begin1541329549485
pool-1-thread-3 username=2 end1541329549485
pool-1-thread-2 username=1 begin1541329549485
pool-1-thread-1 username=0 begin1541329549485
pool-1-thread-1 username=0 end1541329549485
pool-1-thread-4 username=3 begin1541329549485
pool-1-thread-4 username=3 end1541329549485
pool-1-thread-2 username=1 end1541329549485
pool-1-thread-5 username=4 begin1541329549485
pool-1-thread-5 username=4 end1541329549485
  • 使用Executors.newCachedThreadPool(ThreadFactory);定制线程工厂,例如:
ExecutorService executorService = Executors.newCachedThreadPool(MythreadFactory);

其中MythreadFactory是ThreadFactory的实现类,实现的newThread()方法中可以对创建的线程进行定制,比如给线程名称加前缀等。

  • 除了使用Executors.newCachedThreadPool();方法创建线程池外,还可以使用Executors.newFixedThreadPool(int);创建有界的指定大小的线程池。
  • Executors.newFixedThreadPool(int,ThreadFactory);创建有界的指定大小的线程池时,也可以对线程池进行定制
  • 另外还可以使用Executors.newSingleThreadExecutor();方法创建单一线程池,即线程池中只有一个线程可以使用。示例如下:
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        for(int i=0;i<3;i++){
            executorService.execute(new MyRunnable(Integer.toString(i)));
        }

这种情况下,三个execute内部执行的任务是由同一个线程完成的,所有任务是以队列的方式执行的。
输出结果如下:

pool-1-thread-1 username=0 begin1541330285203
pool-1-thread-1 username=0 end1541330285203
pool-1-thread-1 username=1 begin1541330285203
pool-1-thread-1 username=1 end1541330285203
pool-1-thread-1 username=2 begin1541330285203
pool-1-thread-1 username=2 end1541330285203
```。
总结:
上面讲到了使用Executors工厂类的newXXXThreadPool()方法来创建线程池,这些方法内部最终都是实例化了ThreadPoolExecutor方法,代码如下:
public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}

相关文章

网友评论

      本文标题:Java 线程池

      本文链接:https://www.haomeiwen.com/subject/nwskxqtx.html