1. 단순하고 조악한 스레드
가장 원시적인 방법으로 작업을 병렬 또는 비동기적으로 실행하려는 경우 아래와 같이 스레드를 직접 시작하는 방법을 사용합니다.
new Thread(new Runnable() { @Override public void run() { // TODO Auto-generated method stub 这里放你要执行的方法 } }).start();
그러나 위와 같이 매번 new Thread를 사용하거나 다음과 같은 유사한 방법을 사용하는 데는 단점이 많습니다.
new Thread가 좋지 않을 때마다 새로운 객체를 생성하는 성능이 좋지 않습니다. ;
스레드에는 통합 관리가 부족합니다. 스레드는 무제한으로 새 스레드를 생성하고 서로 경쟁할 수 있으며 시스템 리소스를 너무 많이 차지하여 충돌이나 OOM(메모리 부족)을 일으킬 수도 있습니다.
더 많은 스레드가 부족합니다. 예약 실행, 주기적 실행, 스레드 중단 등의 기능
2. 스레드 풀
이러한 문제를 해결하기 위해 Jdk1.5 이후에 java.util.concurrent 패키지가 추가되었습니다. 이 패키지에서는 주로 Java에서 스레드 및 스레드 풀의 사용을 소개합니다. . 개발 중 스레드 문제를 처리하는 데 많은 도움을 제공합니다.
1. 기능
시스템 환경에 따라 스레드 수를 자동 또는 수동으로 설정하여 최상의 작업 효과를 얻을 수 있으며 시스템 리소스가 낭비되지 않고 시스템 혼잡이 발생합니다. 그리고 비효율성이 높습니다. 스레드 풀을 사용하여 스레드 수를 제어하고 다른 스레드는 대기열에 대기합니다. 작업이 실행된 후 대기열의 맨 앞에 있는 작업이 선택되고 실행이 시작됩니다. 대기열에 대기 중인 프로세스가 없으면 스레드 풀의 이 리소스가 대기 중입니다. 새 작업을 실행해야 할 때 스레드 풀에 대기 중인 작업자 스레드가 있으면 실행을 시작할 수 있습니다. 그렇지 않으면 대기 대기열에 들어갑니다.
2. 스레드 풀을 사용하는 이유는 무엇입니까?
객체 생성 및 소멸 비용을 줄이고 성능을 높이기 위해 기존 스레드를 재사용합니다.
최대 동시 스레드 수를 효과적으로 제어하고, 시스템 리소스 사용을 개선하며, 과도한 리소스 경쟁과 혼잡을 피할 수 있습니다.
예약 실행, 주기적 실행, 단일 스레드, 동시성 제어 등의 기능을 제공합니다.
과다한 메모리 소모로 인해 서버가 소진되는 것을 방지하기 위해 시스템 용량에 따라 스레드 풀에서 작동하는 스레드 수를 조정할 수 있습니다. (각 스레드에는 약 1MB의 메모리가 필요하며 스레드가 오래 열릴수록 , 더 많은 메모리를 소비할수록 더 많은 메모리가 소비되고 결국 시스템이 충돌하게 됩니다.)
3. 메인 클래스
Java에서 스레드 풀의 최상위 인터페이스는 Executor이지만 엄밀히 말하면 Executor는 스레드 풀이 아니라 스레드를 실행하기 위한 도구일 뿐입니다. 실제 스레드 풀 인터페이스는 ExecutorService입니다.
스레드 풀을 구성하는 것은 상대적으로 복잡하며, 특히 스레드 풀의 원리가 명확하지 않은 경우에는 더욱 그렇습니다. 최적이 아니므로 일반적으로 사용되는 스레드 풀을 생성하기 위해 Executors 클래스에 일부 정적 팩토리가 제공됩니다.
1) newSingleThreadExecutor
단일 스레드 스레드 풀을 생성합니다. 이 스레드 풀에는 단 하나의 스레드만 작동하며 이는 모든 작업을 순차적으로 실행하는 단일 스레드와 동일합니다. 유일한 스레드가 비정상적으로 종료되면 새 스레드가 이를 대체합니다. 이 스레드 풀은 모든 작업이 제출된 순서대로 실행되도록 보장합니다.
2) newFixedThreadPool
고정 크기 스레드 풀을 생성합니다. 스레드는 스레드 풀의 최대 크기에 도달할 때까지 작업이 제출될 때마다 생성됩니다. 스레드 풀의 크기는 최대값에 도달하면 변경되지 않습니다. 실행 예외로 인해 스레드가 종료되면 스레드 풀은 새 스레드로 채워집니다.
3) newCachedThreadPool
캐시 가능한 스레드 풀을 생성합니다. 스레드 풀의 크기가 작업을 처리하는 데 필요한 스레드를 초과하는 경우
일부 유휴 스레드(60초 동안 작업을 실행하지 않음)가 재활용됩니다. 작업 수가 증가하면 이 스레드 풀이 재활용될 수 있습니다. 작업을 처리하기 위해 지능적으로 새 스레드를 추가했습니다. 이 스레드 풀은 스레드 풀의 크기를 제한하지 않습니다. 스레드 풀의 크기는 운영 체제(또는 JVM)가 생성할 수 있는 최대 스레드 크기에 전적으로 의존합니다.
4) newScheduledThreadPool
무제한 크기의 스레드 풀을 생성합니다. 이 스레드 풀은 작업의 타이밍과 주기적인 실행을 지원합니다.
3. 예시
1) newCachedThreadPool
스레드 풀의 길이가 처리 요구량을 초과하는 경우 재활용이 없으면 유휴 스레드를 유연하게 재활용할 수 있습니다. , 새 스레드를 만듭니다. 샘플 코드는 다음과 같습니다.
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
최대 동시 스레드 수를 제어할 수 있는 고정 길이 스레드 풀을 만듭니다. 초과하는 스레드는 대기열에서 대기합니다. 샘플 코드는 다음과 같습니다.
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이므로 각 작업은 인덱스 출력 후 2초 동안 대기하므로 2초마다 3개의 숫자가 인쇄됩니다.
고정 길이 스레드 풀의 크기는 시스템 리소스에 따라 가장 잘 설정됩니다. Runtime.getRuntime().availableProcessors()
3)newScheduledThreadPool
创建一个定长线程池,支持定时及周期性任务执行。延迟执行示例代码如下:
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); } }
表示延迟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); } }
表示延迟1秒后每3秒执行一次。
4)newSingleThreadExecutor
创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。示例代码如下:
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(); } } }); } } }
结果依次输出,相当于顺序执行各个任务。
注意:以上的execute()方法可以替换为submit()方法,执行的结果是一样的。
四、submit()和execute()的区别
JDK5往后,任务分两类:一类是实现了Runnable接口的类,一类是实现了Callable接口的类。两者都可以被ExecutorService执行,它们的区别是:
execute(Runnable x) 没有返回值。可以执行任务,但无法判断任务是否成功完成。——实现Runnable接口
submit(Runnable x) 返回一个future。可以用这个future来判断任务是否成功完成。——实现Callable接口