JUC 동시 프로그래밍 (1) Thread, TreadPoolExecutor, BlockingQueue, 동기화, 잠금, JUC 보조 클래스

멀티스레드를 생성하는 방법

스레드 상속

  1. 방법 1: Thread 클래스를 상속하고 단계를 만듭니다.
    • Thread를 상속하고 run() 메서드를 다시 작성하도록 하위 클래스 MyThread를 정의합니다.
    • MyThread 클래스의 객체 생성
    • 스레드 객체의 start() 메서드를 호출하여 스레드를 시작합니다. (run() 메서드는 시작 후에 실행됩니다.)

이 생성 방법은 이미 Thread 클래스를 상속했기 때문에 다른 클래스를 상속할 수 없으며 이는 확장에 도움이 되지 않습니다.

  1. 방법 2: Runnable 인터페이스를 구현하는 클래스를 선언합니다.
    • 정의 스레드 작업 클래스 MyRunnable을 정의하여 Runnable 인터페이스를 구현하고 run() 메서드를 재정의합니다.
    • MyRunnable 객체 생성
    • 처리를 위해 MyRunnable 개체를 Thread에 넘겨줍니다.
    • Thread 객체의 start 메소드를 호출하여 시작합니다.

실행 가능한 인터페이스 구현

두 번째 방법은 인터페이스만 구현하므로 계속해서 클래스를 상속하고 인터페이스를 구현할 수 있어 확장성이 더 강해집니다. 그러나 단점은 프로그래밍을 위한 추가 패키징 계층이 있다는 것입니다(스레드 객체를 구성하려면 실행 가능한 객체를 스레드에 전달해야 합니다).
여기에 이미지 설명을 삽입하세요
방법 1 구현:

public class MyThread extends Thread{
    
    
    @Override
    public void run() {
    
    
        for (int i = 0; i < 5; ++i) {
    
    
            System.out.println("子线程执行输出:" + i);
        }
    }
}
 Thread t1 = new MyThread();
 t1.start();

방법 2 구현:

Thread t = new Thread(new Runnable() {
    
    
    @Override
    public void run() {
    
    
        for (int i = 0; i < 10; i++) {
    
    
            System.out.println(i);
        }
    }
});
for (int i = 0; i < 10; i++) {
    
    
    System.out.println("主线程:" + i);
}

물론, 약식으로 람다 표현식을 사용할 수도 있습니다.

 Thread t = new Thread(() -> {
    
    
     for (int i = 0; i < 10; i++) {
    
    
         System.out.println( "子线程" + i);
     }
 });
 for (int i = 0; i < 10; i++) {
    
    
     System.out.println("主线程:" + i);
 }
  1. 방법 3
    처음 두 가지 생성 방법에 문제가 있습니다.
    • 다시 작성된 run() 메서드는 결과를 직접 반환할 수 없습니다.
    • 스레드 실행 결과를 반환해야 하는 비즈니스 시나리오에는 적합하지 않습니다.

Callable 인터페이스를 구현하는 클래스를 정의하고 이를 FutureTask로 어셈블합니다.

jdk5는 위의 기능을 구현하기 위해 Callable 및 FutureTask 인터페이스를 사용합니다.

단계 생성:

  • Callable 인터페이스를 구현하기 위한 클래스를 정의하고, 호출 메소드를 다시 작성하고, 수행할 작업을 캡슐화합니다.
  • FutureTask를 사용하여 Callable 개체를 스레드 작업 개체로 캡슐화합니다.
  • 처리를 위해 스레드 작업 개체를 Thread에 넘겨줍니다.
  • Thread의 시작 메소드를 호출하여 스레드를 시작하고 작업을 실행합니다.
  • 스레드가 실행된 후 FutureTask의 get() 메서드를 사용하여 작업 실행 결과를 가져옵니다.

세 번째 방법 구현:

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class ThreadDemo3 {
    
    
    public static void main(String[] args) {
    
    
        Callable<String> call1 = new MyCallable(100000);
        FutureTask<String> f1 = new FutureTask<>(call1);
        Thread t1 = new Thread(f1);
        t1.start();

        Callable<String> call2 = new MyCallable(100000);
        FutureTask<String> f2 = new FutureTask<>(call2);
        Thread t2 = new Thread(f2);
        t2.start();

        try {
    
    
            // 如果f1没有执行完毕,那么get这里会等待,直至完成
            String r1 =  f1.get();
            System.out.println("第一个结果:" + r1);
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
            // 如果f2没有执行完毕,那么get这里会等待,直至完成
        try {
    
    
            String r2 =  f2.get();
            System.out.println("第二个结果:" + r2);
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }
}

class MyCallable implements Callable<String> {
    
    
    private int n;
    public MyCallable (int n) {
    
    
        this.n = n;
    }
    @Override
    public String call() throws Exception {
    
    
        int sum = 0;
        for (int i = 0; i <= n; i++) {
    
    
            sum += i;
        }
        return "子线程执行的结果是:" + sum;
    }
}

공통 API

여기에 이미지 설명을 삽입하세요
여기에 이미지 설명을 삽입하세요

네이티브 API

스레드의 절전 메서드가 호출되면 현재 실행 중인 스레드가 시스템 타이머 및 스케줄러의 정밀도와 정확성에 따라 지정된 밀리초 동안 절전(실행을 일시적으로 중지) 상태로 만듭니다. 임의의 스레드가 현재 스레드를 인터럽트하는 경우 현재 스레드의 인터럽트 상태를 지우기 위해 InterruptedException이 발생합니다.

방해하다

  • 스레드가 인터럽트()를 호출할 때 스레드가 정상적인 활성 상태에 있으면 스레드의 인터럽트 플래그가 true로 설정됩니다. 그 이상은 아닙니다. 인터럽트 플래그가 설정된 스레드는 영향을 받지 않고 계속해서 정상적으로 실행됩니다. 즉, Interrupt()는 인터럽트 플래그만 설정할 뿐 실제로 스레드를 중단하지는 않으며 호출된 스레드의 협력이 필요합니다. 마치 사람에게 닥치라고 말하지만 결국 그 사람이 닥칠지 말지는 그 사람의 협조에 달려 있는 것과 같습니다.
  • 스레드가 차단 상태(예: 절전, 대기, 조인 등)에 있는 경우 다른 스레드에서 현재 스레드 객체의 Interrupt() 메서드를 호출하면 스레드는 즉시 차단 상태를 종료하고 인터럽트 상태가 종료됩니다. 플래그가 지워지고 InterruptedException 예외가 발생합니다.
  • 비활성 스레드의 경우 Interrupt()를 호출해도 효과가 없습니다.

가입하다

Join 메소드를 사용하면 한 스레드가 다른 스레드에 합류하기 전에 실행될 수 있습니다. 이 스레드가 실행되는 동안 다른 스레드는 차단 상태에 들어갑니다. 물론 Join 입력 매개변수를 지정하고(실행 대기 시간 초과 기간 지정) 대기할 수도 있습니다. 스레드가 종료될 때까지 최대 몇 밀리초 동안 시간 제한이 0이면 영원히 기다리는 것을 의미합니다.

public class demo1 {
    
    
    public static void main(String[] args) {
    
    
        Thread t1 = new Thread(() -> {
    
    
            try {
    
    
                TimeUnit.SECONDS.sleep(2);
                System.out.println("+++++++++++++");
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        });
        t1.start();
        try {
    
    
            t1.join();
            System.out.println("ok");
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
    }
}

생산하다

양보(yield)의 문자 그대로의 의미는 포기하다입니다. 이 메서드를 호출하면 현재 스레드가 프로세서의 현재 사용을 포기할 의사가 있음을 스케줄러에 표시하며, 스케줄러는 이를 무시해도 됩니다.

Yield는 CPU를 과도하게 사용하는 스레드 간의 상대적인 진행 상황을 개선하기 위한 경험적 시도입니다. 항복 방법을 사용할 때 일반적으로 두 가지 사용 시나리오가 있습니다.

  • 실제로 원하는 효과가 있는지 확인하기 위해 수율의 사용은 상세한 분석 및 벤치마킹과 결합되어야 하지만 이 접근 방식은 거의 사용되지 않습니다. 디버깅이나 테스트 목적으로 유용할 수 있으며 경쟁 조건으로 인한 버그를 재현하는 데 도움이 될 수 있습니다.
  • java.util.concurrent.locks 패키지와 같은 동시성 제어 구조를 설계할 때 유용할 수도 있습니다.
public class TestYield {
    
    
    public static void main(String[] args) {
    
    
        MyThread thread1 = new MyThread("thread-1");
        MyThread thread2 = new MyThread("thread-2");
        thread1.start();
        thread2.start();
    }
 
    private static class MyThread extends Thread {
    
    
        public MyThread(String name) {
    
    
            super(name);
        }
        @Override
        public void run() {
    
    
            for (int i = 1; i <= 5; i++) {
    
    
                if (i % 2 == 0) {
    
    
                    Thread.yield();
                    System.out.println(getName() + ":" + i);
                }
            }
        }
    }
}

TreadPoolExecutor

스레드 풀은 스레드를 재사용할 수 있는 기술이다. 새 스레드를 생성하는 오버헤드가 매우 높기 때문에 스레드 풀을 사용하면 스레드를 재사용하고 프로그램 성능을 향상시킬 수 있습니다.
여기에 이미지 설명을 삽입하세요

스레드 풀 개체 가져오기

JDK5.0부터 스레드 풀을 나타내는 인터페이스가 제공되었습니다.
ExecutorService는 스레드 풀 개체를 어떻게 얻습니까?

  • 방법 1: ExecutorService의 구현 클래스 ThreadPoolExecutor를 사용하여 스레드 풀 개체를 자체 생성합니다. 이 방법이 가장 유연합니다.

  • 방법 2: 실행자(스레드 풀용 도구 클래스)를 사용하여 다양한 특성을 가진 스레드 풀 개체를 반환하는 메서드를 호출합니다.

실행자 내장 스레드 풀

1. newCachedThreadPool은
캐시 가능한 스레드 풀을 생성합니다. 스레드 풀의 길이가 처리 요구량을 초과하면 유휴 스레드를 유연하게 재활용할 수 있으며, 재활용 가능한 스레드가 없으면 새 스레드가 생성됩니다. 이러한 유형의 스레드 풀의 특징은
생성되는 작업자 스레드 수에 거의 제한이 없으므로(실제로는 제한이 있으며 개수는 정수입니다. MAX_VALUE) 스레드 풀에 스레드를 유연하게 추가할 수 있습니다. .

오랫동안 스레드 풀에 작업이 제출되지 않은 경우, 즉 작업자 스레드가 지정된 시간(기본적으로 1분) 동안 유휴 상태인 경우 작업자 스레드는 자동으로 종료됩니다. 종료 후 새 작업을 제출하면 스레드 풀이 작업자 스레드를 다시 생성합니다. CachedThreadPool을 사용할 경우 작업 수 제어에 주의해야 합니다. 그렇지 않으면 동시에 실행되는 스레드 수가 많아 시스템 OOM이 발생할 수 있습니다.

2. newFixedThreadPool은
지정된 수의 작업자 스레드로 스레드 풀을 생성합니다. 작업자 스레드는 작업이 제출될 때마다 생성되며 작업자 스레드 수가 초기 최대 스레드 풀 수에 도달하면 제출된 작업이 풀 큐에 저장됩니다.

FixThreadPool은 프로그램 효율성을 향상시키고 스레드 생성에 따른 오버헤드를 절약할 수 있는 장점을 지닌 전형적이고 우수한 스레드 풀입니다. 그러나 스레드 풀이 유휴 상태인 경우, 즉 스레드 풀에 실행 가능한 작업이 없는 경우 작업자 스레드를 해제하지 않으며 특정 시스템 리소스도 점유하게 됩니다.

3. newSingleThreadExecutor는
단일 스레드 실행자를 생성합니다. 즉, 작업을 실행하기 위해 고유한 작업자 스레드만 생성하고 작업을 실행하는 데 유일한 작업자 스레드만 사용하여 모든 작업이 지정된 순서(FIFO, LIFO, 우선순위) . 이 스레드가 비정상적으로 종료되면 다른 스레드가 그 자리를 대신하여 순차적 실행을 보장합니다. 단일 작업자 스레드의 가장 큰 특징은 작업이 순차적으로 실행되도록 보장되며 특정 시간에 여러 스레드가 활성화되지 않는다는 것입니다.
4. newScheduleThreadPool은
고정 길이 스레드 풀을 생성하고 타이밍 및 주기적인 작업 실행을 지원하며 타이밍 및 주기적인 작업 실행을 지원합니다.

ThreadPoolExecutor

여기에 이미지 설명을 삽입하세요
임시 스레드는 언제 생성되나요?

  • 새 작업이 제출되면 코어 스레드가 사용 중이고 작업 큐도 가득 차고 임시 스레드도 생성될 수 있으며 그런 다음에야 임시 스레드가 생성됩니다.
    언제부터 작업 거부를 시작하나요?
  • 코어 스레드와 임시 스레드 모두 사용 중이고 작업 큐도 가득 차서 새 작업이 오면 작업이 거부됩니다.

여기에 이미지 설명을 삽입하세요

스레드 풀은 실행 가능한 작업을 처리합니다.

        ExecutorService pool = new ThreadPoolExecutor(3, 5, 6, TimeUnit.SECONDS, new ArrayBlockingQueue<>(5), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());

        Runnable target = () -> {
    
    
            try {
    
    
                Thread.sleep(100000);
                System.out.println(Thread.currentThread().getName() + "输出");
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        };
        // 三个核心线程
        pool.execute(target);
        pool.execute(target);
        pool.execute(target);
        // 五个在等待队列
        pool.execute(target);
        pool.execute(target);
        pool.execute(target);
        pool.execute(target);
        pool.execute(target);
        // 等待队列满了,新加两个临时线程
        pool.execute(target);
        pool.execute(target);
        
        // 拒绝任务,抛出异常
        pool.execute(target);

    }

스레드 풀은 호출 가능한 작업을 처리합니다.

여기에 이미지 설명을 삽입하세요
실행은 실행 가능한 작업을 실행하고 제출은 호출 가능한 작업을 프로세스합니다.

BlockingQueue 차단 큐

BlockingQueue는 멀티스레딩에서 데이터를 효율적이고 안전하게 "전송"하는 방법에 대한 문제를 해결하는 FIFO(선입선출) 대기열입니다.
여기에 이미지 설명을 삽입하세요
차단 대기열에는 요소 추가 및 제거를 위한 4가지 API가 있습니다.

  • 비차단, 부울 반환
    • 예외가 발생합니다: add(), Remove(), element()
    • 예외는 발생하지 않습니다: Offer(), poll(), peek()
  • 차단하다
    • 항상 차단됨: put(), take()
    • 대기 시간을 설정하고 시간 초과 시 반환할 수 있습니다: Offer(e, timeout, 단위), poll(timeout, 단위)

여기에 이미지 설명을 삽입하세요

LinkedBlockingQueue 연결 목록 차단 큐

이중 연결 목록 차단 큐.

FixThreadPool 및 SingleThreadExector의 경우 사용하는 차단 대기열은 용량이 Integer.MAX_VALUE인 LinkedBlockingQueue이며 이는 무제한 대기열로 간주될 수 있습니다.

FixedThreadPool 스레드 풀의 스레드 개수는 고정되어 있으므로
특별히 많은 수의 스레드를 추가하여 작업을 처리할 수 있는 방법은 없으며, 이때 작업을 저장하려면 LinkedBlockingQueue와 같이 용량 제한이 없는 차단 대기열이 필요합니다.

여기서 주의해야 할 점은 스레드 풀의 작업 큐는 절대 꽉 차지 않기 때문에 스레드 풀은 코어 스레드 수만큼만 스레드를 생성하므로 이때 최대 스레드 수는 스레드 풀에 의미가 없습니다. 코어 스레드 수를 기반으로 하는 다중 스레드 생성을 트리거하지 않습니다.

동기 대기열 동기 대기열

동기 큐는 요소를 저장하지 않습니다. 요소가 삽입되어 있는 한 요소를 꺼내서 가져와야 합니다.

해당 스레드 풀은 CachedThreadPool입니다. 스레드 풀 CachedThreadPool의 최대 스레드 수는 Integer의 최대값으로 스레드 수는 무한히 확장될 수 있음을 알 수 있다.
CachedThreadPool은 기존 스레드 풀인 FixThreadPool과 정반대인데,FixedThreadPool의 경우 블로킹 큐의 용량이 무제한인데 여기서 CachedThreadPool의 스레드 수를 무한정 확장할 수 있으므로 CachedThreadPool 스레드 풀은 필요하지 않다. 작업을 저장하는 작업 큐는 작업이 제출되면 별도로 저장하지 않고 바로 스레드로 전달되거나 새 스레드를 생성하여 실행하기 때문입니다.

SynchronousQueue<String> bq = new SynchronousQueue();
new Thread(() -> {
    
    
    try {
    
    
        bq.put("1");
        System.out.println(Thread.currentThread().getName());
        bq.put("1");
        System.out.println(Thread.currentThread().getName());
        bq.put("1");
        System.out.println(Thread.currentThread().getName());
    } catch (InterruptedException e) {
    
    
        e.printStackTrace();
    }
}, "A").start();

new Thread(() -> {
    
    
    try {
    
    
        TimeUnit.SECONDS.sleep(2);
        bq.take();
        System.out.println(Thread.currentThread().getName());
        bq.take();
        System.out.println(Thread.currentThread().getName());
        bq.take();
        System.out.println(Thread.currentThread().getName());
    } catch (Exception e){
    
    
        e.printStackTrace();
    }
}, "B").start();
}

DelayedWorkQueue 지연 차단 대기열

세 번째 블로킹 큐는 DelayedWorkQueue이며, 이에 대응하는 스레드 풀은 ScheduledThreadPool과 SingleThreadScheduledExecutor이다.이 두 스레드 풀의 가장 큰 특징은 일정 시간 이후에 작업을 실행하거나 일정한 간격으로 작업을 실행하는 등 작업의 실행을 지연시킬 수 있다는 점이다. ...

DelayedWorkQueue의 특징은 내부 요소를 넣은 시간에 따라 정렬하는 것이 아니라 지연 길이에 따라 작업을 정렬하는 것이며 내부 용도는 "힙" 데이터 구조입니다. 스레드 풀 ScheduledThreadPool 및 SingleThreadScheduledExecutor가 DelayedWorkQueue를 선택하는 이유는 그들 스스로 시간을 기준으로 작업을 실행하고, 지연 대기열은 작업 실행을 용이하게 하기 위해 시간별로 작업을 정렬할 수 있기 때문입니다.

동기화됨

1,并发就是多线程操作同一个资源。

2、在Java中,线程就是一个单独的资源类,没有任何附属操作。资源中包含并发操作的属性、方法。

동시성 사례 - 멀티 스레드 티켓 구매의 예:
내 예에서 공용 리소스 클래스는 Ticket이고 왼쪽은 남은 티켓 수이고 cnt는 기록된 판매 티켓 수이며 sale() 메소드는 누가 그것을 구입했는지 인쇄합니다. 잔여 티켓이 있는 경우 티켓 1장, 현재 남은 티켓과 전체 판매 티켓을 표시합니다.

class Ticket {
    
    
    int left;
    int cnt = 0;
    public int getLeft() {
    
    
        return left;
    }

    public void setLeft(int left) {
    
    
        this.left = left;
    }

    public Ticket(int n) {
    
    
        this.left = n;
    }
    public void sale() {
    
    
        try {
    
    
            Thread.sleep(100); // 模拟卖票耗时
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
        if (left > 0) {
    
    
            ++cnt;
            --left;
            System.out.println("线程:" + Thread.currentThread().getName() + "剩余:" + left + ",共卖出:" + cnt);
        }
    }
}

기본 기능에서 투표 수, 티켓을 구매하는 사람 수, 각 사람의 티켓 구매 시도 횟수를 설정합니다.

public static void main(String[] args) {
    
    
    int ticketNum = 8, people = 4, chance = 5;
    Ticket t = new Ticket(ticketNum);
    System.out.println("开售前:---------" + t.getLeft());
    for (int i = 0; i < people; ++i) {
    
    
        new Thread(() -> {
    
    
            for (int j = 0; j < chance; ++j) {
    
    
                t.sale();
            }
        }, Integer.toString(i)).start();
    }
    try {
    
    
        Thread.sleep(3000);
    } catch (InterruptedException e) {
    
    
        e.printStackTrace();
    }
}

결과:
여기에 이미지 설명을 삽입하세요

동기화 솔루션

동기화의 동기화 구현의 기초: Java의 모든 객체는 잠금으로 사용될 수 있습니다. 구체적으로 다음 세 가지 형태를 취합니다.
1. 对于普通同步方法,锁是当前实例对象。
2. 对于静态同步方法,锁是当前类的Class对象。
3. 对于同步方法块,锁是Synchonized括号里配置的对象。

sale()을 직접 동기 메서드로 만들자. 즉, 각 스레드가 sale() 메서드에 액세스하면 현재 메서드가 있는 인스턴스(즉, 공용 리소스)의 객체를 잠그고, 그러면 하나의 스레드만 잠긴다. 한 번에 작동할 수 있으며 다른 스레드는 기다려야 합니다.
여기에 이미지 설명을 삽입하세요
여기에 이미지 설명을 삽입하세요

자물쇠 자물쇠

잠금은 세 단계를 사용합니다.

  1. 잠금 만들기: Lock lk = new ReentrantLock();
  2. 잠금 받기: lk.lock
  3. try - catch - finally, 동시성 제어가 필요한 비즈니스 로직을 try에 작성하고, finally에서 잠금을 해제하여 예외 발생 시 잠금이 정상적으로 해제되도록 합니다.lk.lock().
class Ticket2 {
    
    
    int left;
    int cnt = 0;
    Lock lk = new ReentrantLock();
    public int getLeft() {
    
    
        return left;
    }

    public void setLeft(int left) {
    
    
        this.left = left;
    }

    public Ticket2(int left) {
    
    
        this.left = left;
    }

    public void sale() {
    
    
        lk.lock();
        try {
    
    
            if (left > 0) {
    
    
                ++cnt;
                --left;
                System.out.println("线程:" + Thread.currentThread().getName() + "剩余:" + left + ",共卖出:" + cnt);
            }
        }
        catch (Exception e) {
    
    
            e.printStackTrace();
        }finally {
    
    
            lk.unlock();
        }
    }
}

ReentrantLock은 동시성 안전성도 보장할 수 있습니다.
여기에 이미지 설명을 삽입하세요

동기화 잠금과 잠금 잠금의 차이점

  1. 동기화는 Java 내장 키워드이고 Lock은 Java 클래스입니다.
  2. 동기화됨은 잠금 상태를 획득할 수 없으며, 잠금은 잠금 획득 여부를 확인할 수 있습니다.
  3. 동기화는 자동으로 잠금을 해제하므로 잠금은 수동으로 잠가야 하며, 잠금이 해제되지 않으면 교착상태가 발생합니다.
  4. 동기화되면 잠금을 획득하지 못한 스레드는 영원히 대기합니다. 잠금 잠금에는 잠금을 획득하려고 시도하는 메커니즘이 있으며 반드시 영원히 기다릴 필요는 없습니다.
  5. 동기화는 재진입 및 불공정이며 잠금 잠금은 재진입 및 공정한 잠금을 설정할 수 있습니다. 즉, 하나는 수정할 수 없는 내장 키워드이고 다른 하나는 사용자 정의됩니다.
  6. 동기화는 소량의 코드 동기화 문제를 잠그는 데 적합하고, 잠금은 많은 수의 동기화 코드를 잠그는 데 적합합니다.

생산자 소비자 문제

동기화된 구현

스레드 A와 B는 동일한 변수 num을 작동하고, A는 num + 1을 허용하고,
B는 num - 1을 허용하며, 두 개는 교대로 사용됩니다.

여기서 A가 완료되면 B에 알려야 하고 작업이 완료되면 B에 알려야 스레드 동기화를 실현할 수 있습니다. 이는 A가 생성된 후 소비를 위해 B에 넘겨준 다음 이를 알리는 것과 같습니다. B의 소비가 완료된 후 A.

생산-소비 모델 프로그래밍 3부작을 완성하세요.

  • 대기: 조건이 충족되지 않으면 while 루프가 대기합니다.
  • 사업 : 조건이 충족되면 사업을 실행합니다.
  • 알림: 업무가 완료된 후 다른 스레드에 알립니다.

멤버 변수 num이 있는 Data 리소스 클래스를 구축하고 하나는 +1을 실행하고 다른 하나는 -1을 실행하는 두 개의 동기화 메소드를 구축합니다. 기본 메소드에서 두 스레드 A와 B를 시작하고 +1과 ​​-를 연산해 봅니다. 각각 1개:

class Data {
    
    
    private int num = 0;

    public synchronized void increase() {
    
    
        while (num != 0) {
    
    
            try {
    
    
                this.wait();
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        }
        ++num;
        System.out.println(Thread.currentThread().getName() +":->"+ num);
        this.notifyAll();
    }

    public synchronized void decrease() {
    
    
        while (num != 1) {
    
    
            try {
    
    
                this.wait();
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        }
        --num;
        System.out.println(Thread.currentThread().getName() +":->"+ num);
        this.notifyAll();
    }
}

    public static void main(String[] args) {
    
    
        Data d = new Data();
        new Thread(() -> {
    
    
            for (int i = 0; i < 5; i++) {
    
    
                d.increase();
            }
        }, "A").start();

        new Thread(() -> {
    
    
            for (int i = 0; i < 5; i++) {
    
    
                d.decrease();
            }
        }, "B").start();
    }
}

잠금 버전 구현 - 조건

여기에 이미지 설명을 삽입하세요
조건 변수를 작성하기 위해 잠금을 사용할 수 있습니다. 조건은 wait(), inform() 및 informAll과 유사한 wait() 메소드와 signal() 및 signalAll() 메소드를 제공합니다.

주요 단계:

  • 1. ReentrantLock() lk를 생성하고 lk의 조건을 가져옵니다.
  • 2. 잠금: lk.lock()
  • 3、시도-잡기-최종
    • try에 비즈니스 로직을 작성하세요.
      • 대기: 조건이 충족되지 않으면 while 루프가 대기합니다. Condition.await();
      • 사업 : 조건이 충족되면 사업을 실행한다.
      • 알림: 업무가 완료된 후 다른 스레드에 알림: Condition.signalAll();
    • 최종적으로 잠금을 해제합니다: lk.unlock();
class Data2 {
    
    
    private int num = 0;
    Lock lk = new ReentrantLock();
    Condition condition = lk.newCondition();
    
    public  void increase() {
    
    
        lk.lock();
        try {
    
    
            while (num != 0) condition.await();
            ++num;
            System.out.println(Thread.currentThread().getName() +":->"+ num);
            condition.signalAll();
        } catch (Exception e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            lk.unlock();
        }
    }

    public  void decrease() {
    
    
        lk.lock();
        try {
    
    
            while (num != 1) condition.await();
            --num;
            System.out.println(Thread.currentThread().getName() +":->"+ num);
            condition.signalAll();
        } catch (Exception e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            lk.unlock();
        }
    }
}

정확한 알림 깨우기를 구현하는 조건

여기에 이미지 설명을 삽입하세요

호출 가능

결과를 얻기 위해 반환 값이 있는 비동기 요청에 사용됩니다.
첫 번째 단계는 자신만의 Callable 객체를 구축하고 Callable 인터페이스를 구현하는 것입니다. 예상되는 결과 유형을 식별하려면 일반 매개변수가 필요합니다.

class  MyCall implements Callable<Integer> {
    
    
    int a, b;

    public MyCall(int a, int b) {
    
    
        this.a = a;
        this.b = b;
    }

    @Override
    public Integer call() throws Exception {
    
    
        TimeUnit.SECONDS.sleep(2);
        return a + b;
    }
}

Callable 객체는 FutureTask와 함께 패키징됩니다. 즉, 향후 실행될 태스크로 패키징됩니다.

MyCall call = new MyCall(3, 4);
FutureTask future = new FutureTask(call);

여기에 이미지 설명을 삽입하세요
RunnableFuture 복합 인터페이스는 FutureTask에서 구현됩니다. 즉, Runnable의 구현이 있으므로 시작하기 위해 Thead에 넣을 수 있습니다.

        new Thread(future).start();
        Integer a = 0;
        try {
    
    
            a = (Integer) future.get();
        } catch (InterruptedException | ExecutionException e) {
    
    
            e.printStackTrace();
        }
        System.out.println(a.toString());

이 future.get()은 차단됩니다!

JUC에서 일반적으로 사용되는 보조 수업

CountDownLatch(카운트다운 타이머)

CountDownLatch를 사용하면 모든 스레드의 작업이 실행될 때까지 카운트 스레드를 한곳에서 차단할 수 있습니다.

장면을 시뮬레이션해 보세요. 교실에 6명의 학생이 있고, 모든 학생들이 떠난 후에야 문을 닫을 수 있습니다!

public static void main(String[] args) {
    
    
    // 1、统计num个线程的倒计时器
    int num = 6;
    CountDownLatch cn = new CountDownLatch(num);
    for (int i = 0; i < num; ++i) {
    
    
        new Thread(()->{
    
    
            try {
    
    
                Thread.sleep(100);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "离开");
            // 2、线程结束前,倒数一下
            cn.countDown();
        }, String.valueOf(i)).start();
    }
    try {
    
    
        // 3、等待所有线程结束
        cn.await();
    } catch (InterruptedException e) {
    
    
        e.printStackTrace();
    }
    System.out.println("关门!");
}

여기에 이미지 설명을 삽입하세요

CyclicBarrier(순환 울타리)

CyclicBarrier는 CountDownLatch와 매우 유사합니다. 또한 스레드 간 기술적인 대기를 구현할 수도 있습니다. 수행할 작업은 스레드 그룹이 장벽(동기화 지점이라고도 함)에 도달하면 마지막 스레드가 장벽에 도달할 때까지 스레드 그룹을 차단하는 것입니다. 그런 다음에만 가능합니다. 문이 열리고 장벽에 의해 차단된 모든 스레드는 계속 작동합니다.

현재 시나리오는 역전되어 교사가 지정된 숫자에 도달하기 전에 도착하는 사람의 수가 문을 열도록 허용한다고 가정합니다.

1단계: CyclicBarrier를 생성하고, 충족할 스레드 수와 모든 스레드가 도착한 후 실행될 실행 가능 개체를 지정합니다.

CyclicBarrier cb = new CyclicBarrier(num, () -> {
    
    
   System.out.println("开门!");
});

2단계: 각 스레드 실행이 끝나기 전에 cb.await();다른 스레드가 동기화될 때까지 기다립니다.

public static void main(String[] args) {
    
    
    // 1、等待的人数到达num后,才开门!
    int num = 6;
    CyclicBarrier cb = new CyclicBarrier(num, () -> {
    
    
        System.out.println("开门!");
    });
    for (int i = 0; i < num; ++i) {
    
    
        new Thread(()->{
    
    
            System.out.println(Thread.currentThread().getName() + "到达");
            try {
    
    
                // 2、线程结束前,需等待其他线程同步
                cb.await();
            } catch (Exception e) {
    
    
                e.printStackTrace();
            }
        }, String.valueOf(i)).start();
    }
}

여기에 이미지 설명을 삽입하세요

세마포어 세마포어 - 여러 스레드가 동시에 액세스할 수 있도록 허용합니다.

동기화 및 ReentrantLock 모두 한 번에 하나의 스레드만 리소스에 액세스하도록 허용합니다.Semaphore(信号量)可以指定多个线程同时访问某个资源。

세마포에는 공정 모드와 불공정 모드의 두 가지 모드가 있습니다.

  • 공정 모드: 획득이 호출되는 순서는 FIFO에 따라 라이센스를 획득한 순서입니다.
  • 불공정 모드: 선점형

건설 방법:

public Semaphore(int permits) {
    
    
        sync = new NonfairSync(permits);
    }
public Semaphore(int permits, boolean fair) {
    
    
    sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}

이 두 가지 구성 방법 모두 허가 횟수를 제공해야 하며, 두 번째 구성 방법은 공정 모드인지 불공정 모드인지 지정할 수 있으며 기본값은 불공정 모드입니다.

여기에 이미지 설명을 삽입하세요

가장 일반적으로 사용되는 시나리오는 리소스가 제한되어 있는 경우, 주차 공간을 확보하는 장면을 시뮬레이션하는 것과 같이 지정된 수의 스레드만 특정 리소스에 동시에 액세스하도록 허용되는 것입니다.

1단계:
리소스 상황 시뮬레이션: 주차 공간 수, 사용자는 10개의
int num = 3, total = 6;
Semaphore semaphore = new Semaphore(num);

2단계:
try -catch -final:
try: semaphore.acquire(); // 리소스 획득
finally: semaphore.release(); // 리소스 해제

public static void main(String[] args) {
    
    
    // 1、num个车位,而用户有total 个
    int num = 3, total = 6;
    Semaphore sm = new Semaphore(num);
    for (int i = 0; i < total; ++i) {
    
    
        new Thread(()->{
    
    
            try {
    
    
                sm.acquire();
                System.out.println(Thread.currentThread().getName() + "抢到车位");
                TimeUnit.SECONDS.sleep(1);
                System.out.println(Thread.currentThread().getName() + "离开车位");
            } catch (Exception e) {
    
    
                e.printStackTrace();
            } finally {
    
    
                sm.release();
            }
        }, String.valueOf(i)).start();
    }
}

동시에 3명의 사용자만 주차 공간을 이용할 수 있습니다.
여기에 이미지 설명을 삽입하세요

ReadWriteLock 읽기-쓰기 잠금

읽기-쓰기 잠금을 사용하여 사용자 정의 캐시를 구현하고 쓰기 시 하나의 작업만 허용됩니다.

1단계: 읽기-쓰기 잠금 정의:
private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
2단계: 읽기 작업 기능 정의:
읽기 전에 읽기 잠금 추가
readWriteLock.readLock().lock();
읽은 후 잠금 해제
단계 3: 쓰기 작업 기능()을 정의하여
쓰기 시 쓰기 잠금을 추가하고 쓰기 후에 잠금을 해제합니다.

class MyCache {
    
    
    private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    private Map<String, Object> mp = new HashMap<>();

    public void put(String s, Object o) {
    
    
        readWriteLock.writeLock().lock();
        try {
    
    
            mp.put(s, o);
            System.out.println(Thread.currentThread().getName() + "插入:" + s);
        } catch (Exception exception) {
    
    
            exception.printStackTrace();
        } finally {
    
    
            readWriteLock.writeLock().unlock();
        }
    }

    public Object get(String s) {
    
    
        Object ans = null;
        readWriteLock.readLock().lock();
        try {
    
    
            ans = mp.get(s);
            System.out.println(Thread.currentThread().getName() + "查询:" + s);
        } catch (Exception exception) {
    
    
            exception.printStackTrace();
        } finally {
    
    
            readWriteLock.readLock().unlock();
        }
        return ans;
    }
}

4가지 주요 기능 인터페이스

기능적 인터페이스는 하나의 추상 메서드만 정의하는 인터페이스이거나 @FunctionalInterface라는 주석이 달린 인터페이스입니다. 기본 메소드가 있을 수 있습니다.
네 가지 기능 인터페이스는 다음과 같습니다.

  • 기능적 기능 인터페이스(Function): 하나의 인터페이스 입력은 함수의 입력이고 다른 하나는 함수의 출력입니다.
  • 소비자 함수 인터페이스(Consumer): 인터페이스 입력은 함수의 입력이고 함수의 반환은 부울 값입니다.
  • 공급 기능 인터페이스(공급업체)
  • 조건부 기능 인터페이스(조건부)

기능/기능

기능적 인터페이스: 하나의 인터페이스 입력은 함수의 입력이고 다른 하나는 함수의 출력입니다.

@FunctionalInterface
public interface Function<T, R> {
    
    

    /**
     * Applies this function to the given argument.
     *
     * @param t the function argument
     * @return the function result
     */
    R apply(T t);
    // ... 两个默认函数和一个静态函数
}

어설션 유형

결정 인터페이스: 인터페이스 입력은 함수의 입력 함수이고 함수의 반환은 부울 값입니다.

public interface Predicate<T> {
    
    

    /**
     * Evaluates this predicate on the given argument.
     *
     * @param t the input argument
     * @return {@code true} if the input argument matches the predicate,
     * otherwise {@code false}
     */
    boolean test(T t);
    // ...三个默认函数和一个静态函数
}

소비 유형

소비자 인터페이스: 입력만 있고 출력은 없음

@FunctionalInterface
public interface Consumer<T> {
    
    

    /**
     * Performs this operation on the given argument.
     *
     * @param t the input argument
     */
    void accept(T t);
    // ...一个默认方法
}

공급 유형

공급 인터페이스: 출력만, 입력 없음

@FunctionalInterface
public interface Supplier<T> {
    
    

    /**
     * Gets a result.
     *
     * @return a result
     */
    T get();
}
        Consumer consumer = (str) -> {
    
    
            System.out.println(str);
        };
        consumer.accept("Happy");
    }

사용 사례 - 스트림 스트리밍 프로그래밍

    /**
    有5个用户,筛选
     1、ID 必须是偶数
     2、年龄必须大于23
     3、用户名转大写字母
     4、倒序排序
     5、只需要一个用户
     **/
    public static void main(String[] args) {
    
    
        List<User> list = new ArrayList<>();
        Collections.addAll(list,
                new User(0, 22, "lzy"),
                new User(1, 20, "blzy"),
                new User(2, 25, "azy"),
                new User(3, 24, "czy"),
                new User(4, 24, "dzy"),
                new User(5, 24, "ezy"),
                new User(6, 24, "fzy"),
                new User(7, 24, "gsy"));
        list.stream().filter(e -> {
    
    return e.getId() % 2 == 1;})
                     .filter(e -> {
    
    return e.getAge() > 23;})
                     .map(e -> {
    
    return e.getUsername().toUpperCase();})
                     .sorted(Comparator.reverseOrder())
                     .limit(1)
                     .forEach(System.out::println);

    }

추천

출처blog.csdn.net/baiduwaimai/article/details/132083149