Java常见的线程池

一、线程池的作用

  1. 线程池的作用就是限制系统中执行线程的数量。
  2. 根据系统的环境情况,可以自动或手动设置线程数量,达到运行的最佳效果。
  3. 多了浪费了系统资源,少了造成系统拥挤效率不高。
  4. 用线程池控制线程数量,其它线程排队等候。
  5. 一个任务执行完毕,再从队列中取出最前面的任务开始执行。
  6. 若队列中没有等待进程,线程池的这一资源处于等待。
  7. 当一个新任务需要运行时,如果线程中有等待的工作线程,就可以开始运行了,否则进入等待队列。

二、为什么要用线程池

  1. 减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。
  2. 可以根据系统的承受能力,调整线程池中工作线程的数目,防止因为消耗过多的内存,而吧服务器累趴下(每个内存需要大约1MB内存,线程开的越多,消耗的内存也就越多,最后死机。)
  3. Java里线程池的顶级接口是Executor,但是严格意义上将Executor并不是一个线程池,而只是一个执行线程的工具。真正的线程池接口是ExecutorService。

三、关于线程池比较重要的几个类

1、ExecutorService

真正的线程池接口。

2、ScheduledExecutorService 

和timer类似,解决那些需要任务重复执行的问题。

3、ThreadPoolExecutor 

ExecutorService的默认实现。

4、ScheduledThreadPoolExecutor 

继承ThreadPoolExecutor 的ScheduledExecutorService接口实现,周期性任务调度的类实现。

四、new Thread的弊端

public class Test {
    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("hello world");
            }
        }).start();
    }
}

执行一个异步任务还要new Thread吗?错,new Thread的弊端如下:

  1. 每次new Thread新建对象性能差。
  2. 线程缺少统一管理,可能无限制新建线程,相互之间竞争,可能占用过多的系统资源导致死机。
  3. 缺乏更多功能,如定时执行、定期执行、线程中断。

相比new Thread,Java提供了四种线程池,线程池的好处在于:

  1. 重用存在的线程,减少对象创建、消亡的开销,性能佳。
  2. 可有效控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞。
  3. 提供定时执行、定期执行、单线程、并发数控制等功能。

五、使用线程池的风险

1、死锁

正在执行的线程都在等待等待队列阻塞线程的结果。

2、资源不足

线程池的一个优点在于:相对于其它替代调度机制(有些我们已经讨论过)而言,它们通常执行得很好。但只有恰当地调整了线程池大小时才是这样的。线程消耗包括内存和其它系统资源在内的大量资源。除了 Thread 对象所需的内存之外,每个线程都需要两个可能很大的执行调用堆栈。除此以外,JVM 可能会为每个 Java 线程创建一个本机线程,这些本机线程将消耗额外的系统资源。最后,虽然线程之间切换的调度开销很小,但如果有很多线程,环境切换也可能严重地影响程序的性能。

如果线程池太大,那么被那些线程消耗的资源可能严重地影响系统性能。在线程之间进行切换将会浪费时间,而且使用超出比您实际需要的线程可能会引起资源匮乏问题,因为池线程正在消耗一些资源,而这些资源可能会被其它任务更有效地利用。除了线程自身所使用的资源以外,服务请求时所做的工作可能需要其它资源,例如 JDBC 连接、套接字或文件。这些也都是有限资源,有太多的并发请求也可能引起失效,例如不能分配 JDBC 连接。

3、并发错误

4、线程泄漏

当从池中拿出一个线程执行一项任务,但任务完成后该线程没有返回池时,会发生线程泄漏。这是线程池就少了一个线程,这种情况出现多次后,线程池就空了,系统就会停止,因为没有可用的线程执行任务了。

5、请求过载

六、线程池的大小设置

调整线程池的大小基本上就是避免两类错误:线程太少或线程太多。幸运的是,对于大多数应用程序来说,太多和太少之间的余地相当宽。

请回忆:在应用程序中使用线程有两个主要优点,尽管在等待诸如 I/O 的慢操作,但允许继续进行处理,并且可以利用多处理器。在运行于具有 N 个处理器机器上的计算限制的应用程序中,在线程数目接近 N 时添加额外的线程可能会改善总处理能力,而在线程数目超过 N 时添加额外的线程将不起作用。事实上,太多的线程甚至会降低性能,因为它会导致额外的环境切换开销。

线程池的最佳大小取决于可用处理器的数目以及工作队列中的任务的性质。若在一个具有 N 个处理器的系统上只有一个工作队列,其中全部是计算性质的任务,在线程池具有 N 或 N+1 个线程时一般会获得最大的 CPU 利用率。

对于那些可能需要等待 I/O 完成的任务(例如,从套接字读取 HTTP 请求的任务),需要让池的大小超过可用处理器的数目,因为并不是所有线程都一直在工作。通过使用概要分析,您可以估计某个典型请求的等待时间(WT)与服务时间(ST)之间的比例。如果我们将这一比例称之为 WT/ST,那么对于一个具有 N 个处理器的系统,需要设置大约 N*(1+WT/ST) 个线程来保持处理器得到充分利用。

处理器利用率不是调整线程池大小过程中的唯一考虑事项。随着线程池的增长,您可能会碰到调度程序、可用内存方面的限制,或者其它系统资源方面的限制,例如套接字、打开的文件句柄或数据库连接等的数目。

七、四种常见得 线程池

1、newCachedThreadPool

可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收线程,则新建线程。

这类线程池的特点:

  • 工作线程的创建数量几乎没有限制,这样可灵活的添加线程。
  • 如果长时间没有在线程池中提交任务,即如果工作线程空闲了执行时间(默认为1分钟),则该工作线程将自动终止。终止后,如果你又提交了新的任务,则线程池重新创建一个工作线程。
  • 一定要注意任务的数量,线程过多会造成系统瘫痪。
package test;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolExecutorTest {
    public static void main(String[] args) {
        ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
        for (int i = 0; i < 10; i++) {
            final int index = i;
            try {
                 Thread.sleep(index * 1000);
            } catch (InterruptedException e) {
                 e.printStackTrace();
            }
            cachedThreadPool.execute(new Runnable() {
                public void run() {
                    System.out.println(index);
                }
           });
        }
    }
}

2、newFixedThreadPool

指定工作线程数量的线程池。

newFixedThreadPool是一个典型且优秀的线程池,它具有线程池提高程序效率和节省创建线程成本的优点。但是,在线程空闲时,它不会释放工作线程,还会占用一定的系统资源。

package test;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolExecutorTest {
    public static void main(String[] args) {
        ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
        for (int i = 0; i < 10; i++) {
            final int index = i;
            fixedThreadPool.execute(new Runnable() {
                public void run() {
                    try {
                         System.out.println(index);
                         Thread.sleep(2000);
                    } catch (InterruptedException e) {
                         e.printStackTrace();
                    }
                }
            });
        }
    }
}

3、newSingleThreadExecutor

单线程化的Executor。

即只创建唯一的工作线程来执行任务,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序执行。如果这个线程异常结束,会有另外一个取代它,保证顺序执行。单线程的最大优点是可保证顺序执行各个任务,并且在任意给定的时间不会有其它线程。

package test;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolExecutorTest {
    public static void main(String[] args) {
        ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
        for (int i = 0; i < 10; i++) {
            final int index = i;
            singleThreadExecutor.execute(new Runnable() {
                public void run() {
                    try {
                        System.out.println(index);
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
               }
           });
        }
    }
}

4、newScheduleThreadPool

定长的线程池,支持定时的以及周期性的任务执行。

延迟3秒执行,延迟执行示例代码如下:

package test;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class ThreadPoolExecutorTest {
     public static void main(String[] args) {
        ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
        scheduledThreadPool.schedule(new Runnable() {
            public void run() {
                System.out.println("delay 3 seconds");
            }
        }, 3, TimeUnit.SECONDS);
    }
}

表示延迟1秒后每3秒执行一次,定期执行示例代码如下:

package test;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class ThreadPoolExecutorTest {
    public static void main(String[] args) {
        ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
        scheduledThreadPool.scheduleAtFixedRate(new Runnable() {
            public void run() {
                System.out.println("delay 1 seconds, and excute every 3 seconds");
            }
        }, 1, 3, TimeUnit.SECONDS);
    }
}

上一篇:Java线程池详解

下一篇:深挖ThreadLocal

猜你喜欢

转载自blog.csdn.net/guorui_java/article/details/107632310