상세한 Java 동시 프로그래밍 및 작업 스케줄링

1부: 동시 프로그래밍의 기초

동시 프로그래밍에 대해 논의하기 전에 먼저 몇 가지 기본 개념과 원리를 이해해야 합니다.

스레드는 무엇입니까?

스레드는 컴퓨터 운영 체제의 기본 실행 단위 중 하나입니다. 이는 프로세스의 일부이자 운영 체제 스케줄링의 기본 단위입니다. 프로세스는 각각 자체 코드를 실행하는 여러 스레드를 포함할 수 있지만 프로세스의 메모리와 기타 리소스를 공유합니다.

스레드는 일반적으로 프로세스보다 가볍고, 더 빠르게 생성 및 삭제될 수 있으며, 시스템 리소스를 더 효율적으로 사용합니다. 여러 스레드가 동시에 실행될 수 있으므로 여러 작업을 동시에 수행할 수 있습니다. 스레드는 단일 CPU에서 실행되거나 여러 CPU에서 동시에 실행되어 병렬 컴퓨팅이 가능합니다.

스레드는 동시 프로그래밍에서 중요한 개념이며 멀티태스킹 애플리케이션을 만드는 데 자주 사용됩니다. 예를 들어, 웹 서버에서 각 요청은 처리를 위해 별도의 스레드에 할당될 수 있으므로 서버는 동시에 여러 요청을 처리할 수 있습니다.

Java에서 스레드는 Thread 클래스를 통해 구현됩니다. 스레드는 병렬로 실행되거나 협력하여 작업을 완료할 수 있습니다.

동시성이란 무엇인가

Java에서 동시성은 일정 기간 내에 동일한 프로그램에서 여러 스레드를 실행하는 것을 의미합니다. 여러 스레드가 동시에 실행되면 일부 리소스(예: 메모리 또는 파일)를 공유할 수 있으며, 이로 인해 데이터 경합 및 기타 문제가 발생할 수 있습니다. Java는 공유 리소스에 올바르게 액세스할 수 있도록 여러 스레드 간의 실행을 조정하고 제어하는 ​​여러 메커니즘을 제공합니다.

동기화란 무엇인가

동기화는 여러 스레드가 동시에 공유 리소스에 액세스하지 않도록 하는 데 사용되는 메커니즘입니다. Java에서는 동기화 키워드를 사용하여 코드 블록을 동기화됨으로 표시할 수 있으므로 언제든지 하나의 스레드만 코드 블록에 액세스할 수 있습니다. 동기화된 키워드는 인스턴스 메서드, 정적 메서드 및 코드 블록에서 사용할 수 있습니다.

뮤텍스가 뭐야?

상호 배제는 언제든지 하나의 스레드만 공유 리소스에 액세스할 수 있도록 하는 데 사용되는 메커니즘입니다. Java에서는 잠금(Lock) 개체를 사용하여 상호 배제를 달성할 수 있습니다. Lock 개체는 하나의 스레드만 잠금을 보유할 수 있도록 보장할 수 있으며, 잠금을 보유하고 있는 스레드가 잠금을 해제한 후에만 다른 스레드가 잠금을 획득하고 공유 리소스에 액세스할 수 있습니다. 동기화된 키워드와 달리 Lock 개체는 재진입 잠금 및 공정 잠금과 같은 고급 기능을 제공할 수 있습니다.

2부: JDK 네이티브 스레드 및 작업 스케줄링

Java는 스레드, 잠금, 세마포어, 차단 대기열 등을 포함한 풍부한 동시 프로그래밍 API를 제공합니다. 이 섹션에서는 JDK의 기본 스레드 및 작업 예약에 대해 심층적으로 소개합니다.

스레드 생성

Java에서 스레드를 생성하는 방법에는 두 가지가 있습니다. 하나는 Thread 클래스를 상속하는 것이고, 다른 하나는 Runnable 인터페이스를 구현하는 것입니다.

public class MyThread extends Thread {
    public void run() {
        // 线程执行的代码
    }
}

public class MyRunnable implements Runnable {
    public void run() {
        // 线程执行的代码
    }
}

// 创建线程并启动
MyThread thread = new MyThread();
thread.start();

MyRunnable runnable = new MyRunnable();
Thread thread = new Thread(runnable);
thread.start();

스레드 상태

Java에서 스레드에는 신규, 실행 중, 차단됨, 대기 중, 시간 제한 대기, 종료됨 등 여러 상태가 있습니다.

  • NEW: 새로운 상태, 스레드 객체가 생성되었지만 스레드를 시작하기 위해 start() 메서드가 호출되지 않았습니다.
  • RUNNABLE: 실행 상태, 스레드가 실행 중이거나 CPU 시간 분할을 기다리고 있습니다.
  • BLOCKED: 차단된 상태, 스레드가 배타적 잠금을 획득하기 위해 대기 중이거나 I/O 작업이 완료되기를 기다리고 있습니다.
  • WAITING: 대기 상태, 스레드는 다른 스레드의 알림을 기다리고 있으며 무기한 대기 상태입니다.
  • TIMED_WAITING: 시간 초과 대기 상태. 스레드는 다른 스레드의 알림을 기다리고 있지만 최대 일정 시간 동안 기다립니다.
  • TERMINATED: 종료된 상태, 스레드가 실행을 완료했거나 중단되었습니다.

스레드 상태 다이어그램:

+----------------+     +----------------+     +-----------------+
| NEW            | --> | RUNNABLE       | --> | BLOCKED         |
|                |     |                |     |                 |
|                |     |                |     | waiting for     |
|                |     |                |     | monitor lock    |
+----------------+     +----------------+     +-----------------+
       |                      |                           |
       |                      |                           |
       |                      |                           |
       |                      |                           |
       V                      V                           V
+----------------+     +----------------+     +-----------------+
| TERMINATED     | <-- | TIMED_WAITING  | <-- | WAITING         |
|                |     |                |     |                 |
|                |     |                |     | waiting for     |
|                |     |                |     | another thread  |
|                |     |                |     | or I/O to finish|
+----------------+     +----------------+     +-----------------+

스레드 동기화

스레드 동기화는 여러 스레드 간에 공유 리소스에 대한 올바른 액세스 순서를 보장하는 메커니즘입니다. Java는 동기화, 잠금, 세마포어 등을 포함하여 스레드 동기화를 달성하는 다양한 방법을 제공합니다.

동기화됨

동기화는 Java에서 가장 일반적으로 사용되는 스레드 동기화 메커니즘으로, 동시에 하나의 스레드만 공유 리소스에 액세스하도록 보장할 수 있습니다. 동기화는 메소드나 코드 블록에서 사용할 수 있으며 객체 잠금을 획득하여 동기화를 달성합니다.

public class Counter {
    private int count = 0;
    public synchronized void increment() {
        count++;
    }
    public synchronized void decrement() {
        count--;
    }
}

위 코드에서 increment() 및 decrement() 메서드는 동기식이며 Counter 개체의 잠금을 획득한 다음 count++ 또는 count-- 작업을 수행합니다. 이렇게 하면 여러 스레드가 count 변수에 안전하게 액세스할 수 있습니다.

잠그다

잠금은 Java에서 일반적으로 사용되는 또 다른 스레드 동기화 메커니즘으로, 더 많은 기능과 유연성을 제공합니다. 잠금은 동기화를 구현하는 데 사용될 수 있으며 대기/알림 메커니즘을 구현하는 데에도 사용될 수 있습니다. 동기화와 달리 Lock은 Java 언어 수준의 키워드가 아니라 Java 클래스입니다. Lock은 lock(), tryLock(), lockInterruptible(), newCondition() 등을 포함하여 스레드 동기화를 달성하는 다양한 방법을 제공합니다.

public class Counter {
    private int count = 0;
    private Lock lock = new ReentrantLock();
    public void increment() {
        lock.lock();
        try {
            count++;
        } finally {
            lock.unlock();
        }
    }
    public void decrement() {
        lock.lock();
        try {
            count--;
        } finally {
            lock.unlock();
        }
    }
}

위 코드에서는 Lock을 사용하여 increment() 및 decrement() 메서드를 구현했습니다. 잠금 개체를 획득한 다음 count++ 또는 count-- 작업을 수행합니다. 동기화와 달리 잠금을 사용하려면 명시적인 잠금 획득 및 해제가 필요합니다.

신호기

세마포어는 Java에서 일반적으로 사용되는 동기화 도구로, 동시에 공유 리소스에 액세스하는 스레드 수를 제어할 수 있습니다. 세마포어는 라이센스를 획득하고 해제하는 데 일반적으로 사용되는 두 가지 획득() 및 해제() 메서드를 제공합니다.

다음은 여러 스레드가 액세스해야 하는 공유 리소스가 있는 세마포어를 사용하는 간단한 예입니다. 그러나 동시에 두 개의 스레드만 리소스에 액세스할 수 있습니다.

import java.util.concurrent.Semaphore;

public class SemaphoreDemo {
    // 定义 Semaphore,初始许可数为 2
    private static final Semaphore sem = new Semaphore(2);

    public static void main(String[] args) {
        // 创建 5 个线程
        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                try {
                    // 尝试获取许可
                    sem.acquire();
                    System.out.println("Thread " + Thread.currentThread().getName() + " acquired permit.");
                    // 模拟线程执行一段时间
                    Thread.sleep(2000);
                    System.out.println("Thread " + Thread.currentThread().getName() + " releasing permit.");
                    // 释放许可
                    sem.release();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

위의 예에서 세마포어의 초기 권한 번호는 2입니다. 이는 최대 2개의 스레드만 동시에 공유 리소스에 액세스할 수 있음을 의미합니다. 각 스레드는 먼저 acquire() 메서드를 호출하여 허가 획득을 시도하고, 허가가 없으면 허가가 가능할 때까지 스레드를 차단합니다. 스레드가 허가를 받으면 작업을 시뮬레이션하는 코드 조각을 실행한 다음 허가를 해제합니다. 스레드가 허가를 해제하면 다른 스레드가 허가를 획득하고 계속 실행할 수 있는 기회를 갖게 됩니다. 세마포어는 권한 수를 제한하므로 동시에 두 개의 스레드만 공유 리소스에 액세스할 수 있습니다.

스레드 풀

스레드 풀은 일반적으로 사용되는 스레드 관리 방법으로 스레드의 빈번한 생성 및 소멸로 인한 오버헤드를 방지하고 스레드 수를 제어하여 시스템의 안정성과 효율성을 보장할 수 있습니다. Java는 스레드 풀 기능을 구현하기 위해 ThreadPoolExecutor 클래스를 제공합니다.

public class ThreadPoolDemo {
    public static void main(String[] args) {
        ExecutorService executor = Executors.newFixedThreadPool(5);
        for (int i = 0; i < 10; i++) {
            Runnable task = new Task();
            executor.execute(task);
        }
        executor.shutdown();
    }
}

위 코드에서는 크기가 5인 스레드 풀이 생성되고 10개의 작업이 제출됩니다. 각 작업은 Runnable 인터페이스를 구현하는 클래스로, executor.execute(task) 메서드를 실행한 후 스레드 풀은 자동으로 스레드를 예약하여 작업을 실행합니다. executor.shutdown() 메서드를 실행한 후 스레드 풀은 종료되고 모든 작업이 완료될 때까지 기다립니다.

작업 스케줄링

작업 스케줄링은 특정 시간이나 조건에서 지정된 작업을 실행하는 것을 말합니다. Java는 Timer, ScheduledExecutorService 등을 포함한 다양한 작업 예약 방법을 제공합니다.

시간제 노동자

타이머는 Java와 함께 제공되는 작업 스케줄러로, 지정된 시간에 지정된 작업을 실행할 수 있습니다. Timer는 일회성 작업과 주기적 작업을 실행하는 데 사용되는 두 가지 메서드, Schedule() 및 ScheduleAtFixedRate()를 제공합니다.

public class TimerDemo {
    public static void main(String[] args) {
        TimerTask task = new TimerTask() {
            public void run() {
                System.out.println("task is running");
            }
        };
        Timer timer = new Timer();
        timer.schedule(task, 5000);
    }
}

위 코드에서는 Timer 객체와 TimerTask 객체를 생성한 후, 5초 후에 타이머.schedule(task, 5000) 메소드를 호출하여 작업을 실행합니다. Timer는 주기적 작업을 실행하기 위한 ScheduleAtFixedRate()와 같은 다른 메서드도 제공합니다.

ScheduledExecutorService

ScheduledExecutorService는 Java에서 권장되는 작업 스케줄러로 Timer보다 더 유연하고 안정적인 작업 예약 방법을 제공합니다. ScheduledExecutorService는 일회성 작업과 주기적 작업을 실행하는 데 사용되는 두 가지 메서드, Schedule() 및 ScheduleAtFixedRate()를 제공합니다.

public class ScheduledExecutorDemo {
    public static void main(String[] args) {
        Runnable task = new Task();
        ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
        executor.schedule(task, 5, TimeUnit.SECONDS);
        executor.shutdown();
    }
}

위 코드에서는 ScheduledExecutorService 객체와 작업 객체를 생성한 후 executor.schedule(task, 5, TimeUnit.SECONDS) 메서드를 호출하여 5초 후에 작업을 실행합니다. ScheduledExecutorService는 주기적 작업을 실행하기 위한 ScheduleWithFixedDelay()와 같은 다른 메서드도 제공합니다.

타사 클래스 라이브러리 사용

JDK의 자체 스레드 및 작업 스케줄링 기능 외에도 Java에는 동시 프로그래밍 및 작업 스케줄링을 구현하는 데 사용할 수 있는 뛰어난 타사 라이브러리도 많이 있습니다. 일반적인 타사 클래스 라이브러리는 다음과 같습니다.

  • 아파치 커먼즈 랭
  • 구아바
  • 석영
  • 스프링 태스크

여기에서는 Quartz와 Spring Task를 예로 들어 이 두 클래스 라이브러리를 사용하여 동시 프로그래밍 및 작업 스케줄링을 구현하는 방법을 소개합니다.

석영

Quartz는 더욱 풍부하고 유연한 작업 스케줄링 기능을 제공하고 분산 작업 스케줄링 및 클러스터 작업 스케줄링을 지원하는 오픈 소스 작업 스케줄링 프레임워크입니다. Quartz는 일회성 작업과 주기적 작업은 물론 이메일 보내기, 데이터 백업 등과 같은 복잡한 작업을 수행하는 데 사용할 수 있습니다.

석영 기능은 다음과 같습니다:

  1. 높은 유연성: Quartz는 다양한 트리거 유형(예: 단순 트리거, 크론 트리거 등)에 따라 작업을 예약할 수 있으며 작업 실행 우선순위, 실행 시간 및 시간과 같은 속성을 구성할 수도 있습니다.
  2. 높은 신뢰성: Quartz는 작업 실행 중 오류가 발생할 때 자동으로 처리하고 복구할 수 있는 오류 처리 및 복구 메커니즘을 제공합니다.
  3. 분산 및 클러스터 지원: Quartz는 분산 작업 예약 및 클러스터 작업 예약을 지원하며, 이는 작업 안정성과 확장성을 향상하기 위해 여러 컴퓨터에 배포할 수 있습니다.

Quartz는 예약된 작업을 예약합니다.

다음은 작업 예약을 위해 Quartz를 사용하는 Java 예제입니다. Quartz를 사용하여 시간 제한 작업을 예약하고 10초마다 문장을 출력합니다.

import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.Trigger;
import org.quartz.TriggerBuilder;
import org.quartz.impl.StdSchedulerFactory;

import java.util.Date;

public class QuartzDemo implements Job {
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        System.out.println("Hello Quartz! " + new Date());
    }

    public static void main(String[] args) throws SchedulerException, InterruptedException {
        // 创建 Scheduler 工厂
        StdSchedulerFactory factory = new StdSchedulerFactory();
        // 创建 Scheduler
        Scheduler scheduler = factory.getScheduler();
        // 创建 JobDetail
        JobDetail jobDetail = JobBuilder.newJob(QuartzDemo.class).build();
        // 创建触发器
        Trigger trigger = TriggerBuilder.newTrigger()
                .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(10).repeatForever())
                .build();
        // 将任务和触发器加入 Scheduler
        scheduler.scheduleJob(jobDetail, trigger);
        // 启动 Scheduler
        scheduler.start();
        // 让程序休眠 60 秒
        Thread.sleep(60000);
        // 停止 Scheduler
        scheduler.shutdown();
    }
}

 위의 예에서는 먼저 Quartz의 Job 인터페이스를 구현하는 Job 클래스(QuartzDemo)를 생성하고 실행할 작업을 정의하는 Execute() 메서드를 다시 작성합니다.

그런 다음 Quartz의 SchedulerFactory를 사용하여 Scheduler를 생성하고 JobDetail 객체와 Trigger 객체를 생성합니다. JobDetail은 실행해야 할 Job을 정의하고, Trigger는 Job의 트리거 조건을 정의합니다.

마지막으로 Scheduler에 JobDetail과 Trigger를 추가하고 Scheduler를 시작합니다. 프로그램이 60초 동안 절전 모드로 전환된 후 Scheduler를 중지합니다. 프로그램 실행 중에는 Job에 정의된 작업이 10초마다 실행됩니다.

Quartz를 사용한 Cron 표현식

예약된 작업 외에도 Quartz는 다음과 같은 다른 유형의 작업 예약도 제공합니다.

  1. Cron 표현식 트리거: Linux와 유사한 Cron 표현식을 사용하여 작업 실행 시간을 정의하고, 작업의 실행 시간과 빈도를 유연하게 정의할 수 있습니다.
  2. 달력 트리거: 달력을 사용하여 작업 실행 시간을 정의하고 특정 시간을 제외하여 작업을 예약합니다.
  3. 리스너: Quartz는 작업 실행 전후 또는 트리거 실행 전후에 특정 논리를 실행할 수 있는 다양한 리스너를 제공합니다.
  4. 클러스터 작업 스케줄링: Quartz는 여러 스케줄러 인스턴스 간의 클러스터 작업 스케줄링을 지원하여 작업 안정성과 확장성을 향상시킵니다.
  5. 분산 작업 스케줄링: Quartz는 또한 실행을 위해 원격 노드에 작업을 예약할 수 있는 분산 작업 스케줄링을 지원합니다.

다음은 Quartz의 Cron 표현식 트리거를 사용한 작업 예약의 Java 예입니다. Quartz를 사용하여 매일 아침 9시에 한 번 실행되는 시간 제한 작업을 예약합니다.

import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;

import java.util.Date;

public class QuartzDemo implements Job {
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        System.out.println("Hello Quartz! " + new Date());
    }

    public static void main(String[] args) throws SchedulerException, InterruptedException {
        // 创建 Scheduler 工厂
        StdSchedulerFactory factory = new StdSchedulerFactory();
        // 创建 Scheduler
        Scheduler scheduler = factory.getScheduler();
        // 创建 JobDetail
        JobDetail jobDetail = JobBuilder.newJob(QuartzDemo.class).build();
        // 创建触发器
        Trigger trigger = TriggerBuilder.newTrigger()
                .withSchedule(CronScheduleBuilder.cronSchedule("0 0 9 * * ?"))
                .build();
        // 将任务和触发器加入 Scheduler
        scheduler.scheduleJob(jobDetail, trigger);
        // 启动 Scheduler
        scheduler.start();
        // 让程序休眠 60 秒
        Thread.sleep(60000);
        // 停止 Scheduler
        scheduler.shutdown();
    }
}

위의 예에서는 Quartz의 CronScheduleBuilder를 사용하여 매일 오전 9시에 작업을 실행하는 Cron 표현식 트리거를 생성합니다.

Quartz의 Cron 표현식에 대해 자세히 설명

Quartz의 Cron 표현식은 작업 스케줄링 시간을 정의하는 데 사용되는 표현식으로 Linux와 유사한 Cron 구문을 사용하여 작업 실행 시간과 빈도를 정의합니다. Cron 표현식의 구문은 상당히 복잡하지만 몇 가지 기본 구문 규칙을 아는 것만으로도 충분합니다. 다음은 간단한 Cron 표현식의 예입니다.

0 0 0/1 * * ?   // 每小时执行一次

위의 Cron 표현식은 초, 분, 시, 일, 월, 주를 나타내는 6개의 필드로 구성됩니다. 각 필드의 구문 규칙은 다음과 같습니다.

  1. 초: 0~59, 선택 사항.
  2. 분: 0~59를 지정해야 합니다.
  3. 시간: 0~23을 지정해야 합니다.
  4. 일(월중 일): 1~31, 반드시 지정해야 합니다.
  5. 월: 112 또는 JANDEC 중 하나를 지정해야 합니다.
  6. 요일: 07 또는 SUNSAT(0과 7은 모두 일요일을 나타냄) 중 하나를 지정해야 합니다.

그 중 월과 주라는 두 필드는 상호 배타적입니다. 즉, 특정 월이나 특정 주에만 작업을 실행할 수 있습니다.

기본 문법 규칙 외에도 Cron 표현식은 일반적으로 사용되는 기간 및 논리적 조건을 나타내는 데 사용되는 일부 특수 기호 및 키워드도 지원합니다. 다음은 일반적으로 사용되는 특수 기호 및 키워드입니다.

  1. 별표(*): 모든 값을 나타내며 모든 필드에서 사용할 수 있습니다.
  2. 물음표(?): 지정된 값이 없으며 일 및 주 필드에 사용할 수 있음을 나타냅니다.
  3. 슬래시(/): 증분을 지정하는 데 사용됩니다. 예를 들어 */5는 5개 단위마다 실행한다는 의미입니다.
  4. 쉼표(,): 여러 값을 지정하는 데 사용됩니다. 예를 들어 1,2,3은 1, 2, 3의 세 가지 값을 모두 실행할 수 있다는 의미입니다.
  5. 하이픈(-) : 범위를 지정하는데 사용됩니다. 예를 들어 10~15는 10~15 범위 내의 값을 실행할 수 있다는 의미입니다.

다음은 몇 가지 샘플 Cron 표현식입니다.

  • ​​​​:​0 0 0/1 * * ?​ 매시간 실행합니다.
  • ​​​​:​0 0 12 ? * MON-FRI​ 매주 평일 정오 12시에 1회 실행합니다.
  • ​​​​:​0 0 1 ? * SAT#3​ 매월 세 번째 토요일 오전 1시에 실행됩니다.

cron 표현식의 구문은 매우 복잡하므로 특정 요구 사항에 따라 작성해야 하므로 학습 및 사용에 대해서는 공식 문서를 참조하는 것이 좋습니다.

스프링 태스크

Spring Task는 Spring Framework의 Annotation 기반 작업 스케줄링 프레임워크로, 간단한 작업 스케줄링 기능을 제공하고 일회성 작업과 반복 작업을 지원합니다. Spring Task의 주요 구성요소는 작업 스케줄러(TaskScheduler)와 작업 실행기(TaskExecutor)로, 작업 스케줄러는 작업 스케줄링을 관리하고 작업 실행기는 작업 실행을 담당합니다.

Spring Task의 핵심 기능은 다음과 같습니다.

  1. 예약된 작업 및 주기적 작업 지원: Spring Task는 고정 지연, 고정 속도 및 Cron 표현식을 포함한 다양한 작업 트리거 방법을 지원합니다.
  2. 비동기 실행 지원: Spring Task는 메인 스레드를 차단하지 않고 별도의 스레드에서 작업 실행을 지원합니다.
  3. 작업 필터링 지원: Spring Task는 작업 이름, 그룹 이름 및 Cron 표현식을 기반으로 작업 필터링을 지원합니다.

고정 지연 작업, 고정 속도 작업 및 Cron 표현식 작업

다음은 간단한 Spring Task 샘플 코드입니다.

import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

@Component
public class MyTask {

    @Scheduled(fixedDelay = 1000)
    public void myFixedDelayTask() {
        System.out.println("Fixed delay task - " + System.currentTimeMillis());
    }

    @Scheduled(fixedRate = 2000)
    public void myFixedRateTask() {
        System.out.println("Fixed rate task - " + System.currentTimeMillis());
    }

    @Scheduled(cron = "0/5 * * * * ?")
    public void myCronTask() {
        System.out.println("Cron task - " + System.currentTimeMillis());
    }
}

위 코드는 고정 지연 작업, 고정 속도 작업 및 Cron 표현식 작업을 정의하기 위한 세 가지 메서드가 포함된 MyTask라는 Spring 작업을 정의합니다. 이러한 메서드는 모두 Spring Task에서 제공하는 @Scheduled 주석을 사용하여 작업이 트리거되는 방법을 지정합니다.

그중 @Scheduled 주석의fixedDelay 및fixedRate 속성은 각각 고정 지연 및 고정 속도 작업의 트리거 간격을 밀리초 단위로 지정하는 데 사용됩니다. cron 속성은 Cron 표현식 작업의 트리거 시간을 지정하는 데 사용됩니다. 위의 예에서 고정 지연 작업과 고정 속도 작업은 각각 1초와 2초마다 실행되는 반면 cron 표현식 작업은 5초마다 실행됩니다.

Spring Task 기능을 활성화하려면 Spring Boot 애플리케이션의 메인 클래스에 @EnableScheduling 주석을 추가해야 합니다. 이 주석은 애플리케이션이 시작될 때 Spring Task를 자동으로 활성화할 수 있습니다.

사용자 정의 작업 스케줄러 및 작업 실행기

또한 Spring Task에서 제공하는 TaskScheduler 및 TaskExecutor 인터페이스를 사용하여 작업 스케줄러와 작업 실행기를 사용자 정의할 수 있습니다. 이를 통해 작업 예약 및 작업 실행을 보다 유연하게 관리할 수 있습니다. 다음은 TaskScheduler 및 TaskExecutor 인터페이스를 사용하여 작업 스케줄러 및 작업 실행기를 사용자 지정하는 샘플 코드입니다.

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.SimpleAsyncTaskExecutor;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.support.CronTrigger;

@Configuration
public class MyTaskConfig {

    @Bean
    public TaskScheduler taskScheduler() {
        ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
        scheduler.setPoolSize(10);
        scheduler.setThreadNamePrefix("my-task-scheduler-");
        return scheduler;
    }

    @Bean
    public SimpleAsyncTaskExecutor taskExecutor() {
        SimpleAsyncTaskExecutor executor = new SimpleAsyncTaskExecutor();
        executor.setConcurrencyLimit(5);
        return executor;
    }

    @Bean
    public MyTask myTask() {
        return new MyTask();
    }

    public class MyTask {
        public void runTask() {
            System.out.println("Task is running - " + System.currentTimeMillis());
        }
    }

    public void scheduleTask() {
        TaskScheduler scheduler = taskScheduler();
        SimpleAsyncTaskExecutor executor = taskExecutor();
        MyTask task = myTask();
        scheduler.schedule(() -> executor.execute(task::runTask), new CronTrigger("0/5 * * * * ?"));
    }
}

위의 샘플 코드에서는 먼저 작업 스케줄러와 작업 실행기를 각각 사용자 지정하는 데 사용되는 TaskScheduler와 TaskExecutor를 정의합니다. 그 중 TaskScheduler는 스레드 풀 크기, 스레드 이름 접두사 등의 매개 변수를 구성할 수 있는 ThreadPoolTaskScheduler 구현을 사용하고, TaskExecutor는 동시성 제한 등의 매개 변수를 구성할 수 있는 SimpleAsyncTaskExecutor 구현을 사용합니다.

다음으로 특정 작업 로직을 실행하기 위한 runTask() 메서드가 포함된 MyTask 클래스를 정의합니다. 그런 다음 @Bean 주석을 통해 MyTask 클래스를 Spring Bean으로 등록합니다.

마지막으로 작업 예약을 시작하기 위해 ScheduleTask() 메서드를 정의합니다. 이 메서드는 TaskScheduler 및 CronTrigger를 사용하여 작업 예약의 트리거 모드를 정의하고 TaskExecutor를 사용하여 작업을 실행합니다. 이 예에서는 5초마다 작업 일정을 트리거하는 Cron 표현식을 정의합니다. 작업 스케줄링이 트리거되면 MyTask의 runTask() 메서드가 호출되어 특정 작업 로직을 실행합니다.

커스텀 작업 스케줄러와 작업 실행기를 활성화하려면 MyTaskConfig 클래스를 Spring Bean으로 등록하고 @PostConstruct 주석을 통해 ScheduleTask() 메서드를 호출하여 애플리케이션이 시작될 때 작업 스케줄링을 시작해야 한다는 점에 유의해야 합니다.

발문

이 문서에서는 Java에서 동시 프로그래밍 및 작업 예약을 위해 스레드를 사용하는 방법을 설명합니다. 스레드를 사용하면 프로그램의 동시 처리 기능이 향상되고, 작업 스케줄링을 사용하면 프로그램의 자동 처리 기능이 향상됩니다. 이 기사에서는 JDK의 기본 스레드 및 작업 스케줄링 기능은 물론 일반적으로 사용되는 타사 클래스 라이브러리인 Quartz 및 Spring Task를 소개하여 독자에게 도움이 되기를 바랍니다.

스레드와 작업 스케줄링을 사용하는 과정에서 스레드 안전성, 작업 반복 등의 문제에 주의해야 하며, 특히 멀티 스레드 환경에서는 스레드 안전성을 보장하기 위해 잠금을 추가해야 합니다. 동시에 메모리 누수, 리소스 소모 등의 문제에 주의를 기울여야 하며, 시스템 충돌을 피하기 위해 프로그램의 무한 루프와 대량의 스레드 생성을 피하려고 노력해야 합니다.

스레드 및 작업 스케줄링은 Java에서 매우 중요한 기능으로, 핵심 기술과 최적화 기술을 익히고 프로그램 성능과 안정성을 향상시키기 위해서는 세심한 연구와 연습이 필요합니다.

추천

출처blog.csdn.net/bairo007/article/details/132446401