이 기사에서는 참고용으로 Java 스레드 풀 사용법을 분석합니다.
1. 새로운 스레드의 단점
비동기 작업을 수행하려면 여전히 사용합니까? 다음과 같은 새로운 스레드?
new Thread(new Runnable() { @Override public void run() { // TODO Auto-generated method stub } } ).start();
새 스레드의 단점은 다음과 같습니다.
a 새 스레드가 좋지 않을 때마다 생성되는 새 객체의 성능.
b. 스레드에는 통합 관리가 부족하고, 새로운 스레드가 무제한으로 존재하며, 서로 경쟁하고, 너무 많은 시스템 리소스를 차지하여 충돌이나 OOM이 발생할 수 있습니다.
c. 예약 실행, 주기적 실행, 스레드 중단 등 추가 기능이 부족합니다.
New Thread와 비교하여 Java에서 제공하는 4가지 스레드 풀의 이점은 다음과 같습니다.
a. 기존 스레드를 재사용하고 객체 생성 및 소멸 비용을 절감하며 성능이 좋습니다.
b. 최대 동시 스레드 수를 효과적으로 제어하고, 시스템 리소스 사용을 개선하며, 과도한 리소스 경쟁과 정체를 방지할 수 있습니다.
c. 예약 실행, 주기적 실행, 단일 스레드, 동시성 제어 등의 기능을 제공합니다.
2. Java 스레드 풀
Java는 Executor를 통해 4개의 스레드 풀을 제공합니다.
newCachedThreadPool은 스레드 풀 길이가 처리 요구 사항을 초과하는 경우 캐시 가능한 스레드 풀을 생성합니다. 유휴 스레드를 유연하게 재활용할 수 있는 방법이 없으면 새 스레드가 생성됩니다.
newFixedThreadPool은 대기열에서 대기하는 최대 동시 스레드 수를 제어할 수 있는 고정 길이 스레드 풀을 생성합니다.
newScheduledThreadPool은 예약되고 주기적인 작업 실행을 지원하는 고정 길이 스레드 풀을 생성합니다.
newSingleThreadExecutor는 고유한 작업자 스레드만 사용하여 작업을 실행하는 단일 스레드 스레드 풀을 생성하여 모든 작업이 지정된 순서(FIFO, LIFO, 우선 순위)로 실행되도록 보장합니다.
(1)newCachedThreadPool:
캐시 가능한 스레드 풀을 생성합니다. 스레드 풀의 길이가 처리 요구 사항을 초과하는 경우 재활용이 없으면 유휴 스레드를 유연하게 재활용할 수 있습니다. . 샘플 코드는 다음과 같습니다.
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() { @Override public void run() { System.out.println(index); } }); }
두 번째 작업이 실행되면 첫 번째 작업이 완료되고 계속됩니다. 매번 새 스레드를 생성하는 대신 첫 번째 작업의 스레드를 재사용합니다.
(2)newFixedThreadPool:
최대 동시 스레드 수를 제어할 수 있는 고정 길이 스레드 풀을 생성하면 초과 스레드는 대기열에서 대기합니다. 샘플 코드는 다음과 같습니다.
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3); for (int i = 0; i < 10; i++) { final int index = i; fixedThreadPool.execute(new Runnable() { @Override public void run() { try { System.out.println(index); Thread.sleep(2000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }); }
스레드 풀 크기가 3이므로 각 작업은 인덱스 출력 후 2초 동안 sleep하므로 3개의 숫자가 출력됩니다. 2초마다.
고정 길이 스레드 풀의 크기는 시스템 리소스에 따라 설정하는 것이 가장 좋습니다. Runtime.getRuntime().availableProcessors()와 같은 것입니다. PreloadDataCache를 참조하세요.
(3)newScheduledThreadPool:
예약 및 주기적인 작업 실행을 지원하기 위해 고정 길이 스레드 풀을 만듭니다. 지연 실행에 대한 샘플 코드는 다음과 같습니다.
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5); scheduledThreadPool.schedule(new Runnable() { @Override public void run() { System.out.println("delay 3 seconds"); } }, 3, TimeUnit.SECONDS);
는 3초 동안 지연 실행을 의미합니다.
정기 실행을 위한 샘플 코드는 다음과 같습니다.
scheduledThreadPool.scheduleAtFixedRate(new Runnable() { @Override public void run() { System.out.println("delay 1 seconds, and excute every 3 seconds"); } }, 1, 3, TimeUnit.SECONDS);
는 1초 지연 후 3초마다 실행한다는 의미입니다.
ScheduledExecutorService는 Timer보다 안전하고 강력합니다
(4) newSingleThreadExecutor:
실행 작업만 사용하는 단일 스레드 스레드 풀을 만듭니다. 모든 작업이 지정된 순서(FIFO, LIFO, 우선순위)로 실행되도록 합니다. 샘플 코드는 다음과 같습니다.
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor(); for (int i = 0; i < 10; i++) { final int index = i; singleThreadExecutor.execute(new Runnable() { @Override public void run() { try { System.out.println(index); Thread.sleep(2000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }); }
결과가 순차적으로 출력됩니다. 이는 각 작업을 순차적으로 실행하는 것과 같습니다.
현재 대부분의 GUI 프로그램은 단일 스레드입니다. Android의 단일 스레드는 데이터베이스 작업, 파일 작업, 애플리케이션 일괄 설치, 애플리케이션 일괄 삭제 및 동시성에 적합하지 않지만 IO 차단을 유발하고 UI 스레드의 응답에 영향을 줄 수 있는 기타 작업에 사용할 수 있습니다.
스레드 풀의 역할:
스레드 풀의 역할은 시스템의 실행 스레드 수를 제한하는 것입니다.
시스템 환경에 따라 스레드 수를 자동 또는 수동으로 설정하여 최상의 작업 효과를 얻을 수 있습니다. 스레드 수를 늘리면 시스템 리소스가 덜 낭비되고 시스템 혼잡과 비효율성이 발생합니다. 스레드 풀을 사용하여 스레드 수를 제어하고 다른 스레드는 줄을 서서 기다립니다. 작업이 실행된 후 대기열의 맨 앞에 있는 작업이 선택되고 실행이 시작됩니다. 대기열에 대기 중인 프로세스가 없으면 스레드 풀의 이 리소스가 대기 중입니다. 새 작업을 실행해야 할 때 스레드 풀에 대기 중인 작업자 스레드가 있으면 실행을 시작할 수 있습니다. 그렇지 않으면 대기 대기열에 들어갑니다.
스레드 풀을 사용하는 이유:
1. 스레드 생성 및 소멸 횟수를 줄이면 각 작업자 스레드를 재사용하고 여러 작업을 수행할 수 있습니다.
2. 과도한 메모리 소모로 인해 서버가 소진되는 것을 방지하기 위해 시스템 용량에 따라 스레드 풀에서 작동하는 스레드 수를 조정할 수 있습니다. (각 스레드에는 약 1MB의 메모리가 필요하며 스레드가 시작됩니다. 더 많은 메모리가 소비될수록 더 많은 메모리가 소비되고 결국 시스템이 충돌하게 됩니다.
Java에서 스레드 풀의 최상위 인터페이스는 Executor이지만 엄밀히 말하면 Executor는 스레드 풀이 아니라 스레드를 실행하기 위한 도구일 뿐입니다. 실제 스레드 풀 인터페이스는 ExecutorService입니다.
더 중요한 클래스:
ExecutorService: 실제 스레드 풀 인터페이스.
ScheduledExecutorService: Timer/TimerTask와 유사하며 반복적인 작업 실행이 필요한 문제를 해결할 수 있습니다.
ThreadPoolExecutor: ExecutorService의 기본 구현입니다.
ScheduledThreadPoolExecutor: 정기적인 작업 예약의 클래스 구현인 ThreadPoolExecutor의 ScheduledExecutorService 인터페이스 구현을 상속합니다.
要配置一个线程池是比较复杂的,尤其是对于线程池的原理不是很清楚的情况下,很有可能配置的线程池不是较优的,因此在Executors类里面提供了一些静态工厂,生成一些常用的线程池。
1.newSingleThreadExecutor
创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。
2.newFixedThreadPool
创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。
3.newCachedThreadPool
创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,
那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。
4.newScheduledThreadPool
创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。
实例代码
一、固定大小的线程池,newFixedThreadPool:
package app.executors; import java.util.concurrent.Executors; import java.util.concurrent.ExecutorService; /** * Java线程:线程池 * * @author xiho */ public class Test { public static void main(String[] args) { // 创建一个可重用固定线程数的线程池 ExecutorService pool = Executors.newFixedThreadPool(2); // 创建线程 Thread t1 = new MyThread(); Thread t2 = new MyThread(); Thread t3 = new MyThread(); Thread t4 = new MyThread(); Thread t5 = new MyThread(); // 将线程放入池中进行执行 pool.execute(t1); pool.execute(t2); pool.execute(t3); pool.execute(t4); pool.execute(t5); // 关闭线程池 pool.shutdown(); } } class MyThread extends Thread { @Override public void run() { System.out.println(Thread.currentThread().getName() + "正在执行。。。"); } }
输出结果:
pool-1-thread-1正在执行。。。 pool-1-thread-3正在执行。。。 pool-1-thread-4正在执行。。。 pool-1-thread-2正在执行。。。 pool-1-thread-5正在执行。。。
改变ExecutorService pool = Executors.newFixedThreadPool(5)中的参数:ExecutorService pool = Executors.newFixedThreadPool(2),输出结果是:
pool-1-thread-1正在执行。。。 pool-1-thread-1正在执行。。。 pool-1-thread-2正在执行。。。 pool-1-thread-1正在执行。。。 pool-1-thread-2正在执行。。。
从以上结果可以看出,newFixedThreadPool的参数指定了可以运行的线程的最大数目,超过这个数目的线程加进去以后,不会运行。其次,加入线程池的线程属于托管状态,线程的运行不受加入顺序的影响。
二、单任务线程池,newSingleThreadExecutor:
仅仅是把上述代码中的ExecutorService pool = Executors.newFixedThreadPool(2)改为ExecutorService pool = Executors.newSingleThreadExecutor();
输出结果:
pool-1-thread-1正在执行。。。 pool-1-thread-1正在执行。。。 pool-1-thread-1正在执行。。。 pool-1-thread-1正在执行。。。 pool-1-thread-1正在执行。。。
可以看出,每次调用execute方法,其实最后都是调用了thread-1的run方法。
三、可变尺寸的线程池,newCachedThreadPool:
与上面的类似,只是改动下pool的创建方式:ExecutorService pool = Executors.newCachedThreadPool();
输出结果:
pool-1-thread-1正在执行。。。 pool-1-thread-2正在执行。。。 pool-1-thread-4正在执行。。。 pool-1-thread-3正在执行。。。 pool-1-thread-5正在执行。。。
这种方式的特点是:可根据需要创建新线程的线程池,但是在以前构造的线程可用时将重用它们。
四、延迟连接池,newScheduledThreadPool:
public class TestScheduledThreadPoolExecutor { public static void main(String[] args) { ScheduledThreadPoolExecutor exec = new ScheduledThreadPoolExecutor(1); exec.scheduleAtFixedRate(new Runnable() {//每隔一段时间就触发异常 @Override publicvoid run() { //throw new RuntimeException(); System.out.println("================"); } }, 1000, 5000, TimeUnit.MILLISECONDS); exec.scheduleAtFixedRate(new Runnable() {//每隔一段时间打印系统时间,证明两者是互不影响的 @Override publicvoid run() { System.out.println(System.nanoTime()); } }, 1000, 2000, TimeUnit.MILLISECONDS); } }
输出结果:
================ 8384644549516 8386643829034 8388643830710 ================ 8390643851383 8392643879319 8400643939383
以上就是本文的全部内容,希望对大家的学习有所帮助。
更多四种Java线程池用法解析相关文章请关注PHP中文网!