멀티스레딩 학습 1부

Previewfile_2862005448123

좌우명: 하늘이 좋으면 군자는 힘써 힘을 내고, 땅이 약하면 군자는 큰 덕을 가지고 물건을 운반한다.

프로세스 개념 도입의 목적

프로세스 개념을 도입한 주요 목적은 "동시 프로그래밍" 문제를 해결하기 위한 것입니다.
이는 CPU가 멀티 코어 시대에 진입했기 때문입니다.
프로그램의 실행 속도를 더욱 향상시키려면 CPU의 멀티 코어 리소스를 최대한 활용합니다.

사실 다중 프로세스 프로그래밍은 이미 동시 프로그래밍의 문제를 해결할 수 있습니다. 이미 CPU 다중 코어 자원을 사용할 수 있지만
새로운 문제가 발생합니다 => 즉, 프로세스가 너무 무겁습니다(더 많은 자원을 소비하고 느립니다)! ! !
프로세스를 생성하는 것은 상대적으로 비용이 많이 듭니다.
프로세스를 파괴하는 것은 상대적으로 비용이 많이 듭니다.
프로세스를 예약하는 것은 상대적으로 비용이 많이 듭니다.

프로세스가 문제를 해결할 수는 있지만 이 문제에 대한 최적의 솔루션은 아닙니다.
오버헤드가 적고 무게가 가벼운 개념으로 이 "동시 프로그래밍"을 해결할 수 있을 것으로 예상됩니다.
따라서 스레드가 탄생했습니다!!! 스레드도 역시 스레드라고 불리는 "경량 프로세스"는
"동시 프로그래밍"의 문제 해결을 전제로 생성, 파괴 및 스케줄링을 더 빠르게 만듭니다!!!

프로세스는 운영체제의 기본 단위로 , 프로세스를 생성할 때 프로세스에 일부 자원(메모리 자원, 하드디스크 자원 등)을 할당해야 하고,
자원을 할당할 때 시간이 소요된다. 프로세스가 중요하며, 주요 초점은 "자원 할당"입니다. /recycle" on.

스레드가 더 가벼운 이유는 무엇입니까 ???

=> 자원 신청/해제 작업을 저장합니다.

공장 확장을 예로 들어보겠습니다.
계획 1 : 원래 공장과 똑같은 지점 공장(2공장)을 다른 곳에 건설합니다
~~ 이를 위해서는 새로운 부지, 새로운 물류 시스템, 새로운 생산 라인이 필요합니다. ..
=> 다중 프로세스 버전 솔루션

이미지-20230918093634503

옵션 2 : 원래 공장 확장 및 생산 라인 추가~~ 첫 번째 옵션보다 비용이 훨씬 적습니다
~~ 현장 및 물류 시스템을 재사용할 수 있고, 새로운 생산 라인만 필요합니다...
~~ 멀티 스레드 버전 솔루션 => 이때는 1차 생산라인에 대한 자원만 신청하면 되며,
추후 새로운 생산라인이 추가되면 이전 자원을 재사용할 수 있다.

이미지-20230918094220221

스레드와 프로세스의 관계

~~ 프로세스에 스레드가 포함되어 있다는 의미입니다.
프로세스에는 하나의 스레드가 포함될 수도 있고 여러 스레드가 포함될 수도 있습니다. (없을 수는 없습니다.)
첫 번째 스레드만 시작하면 오버헤드가 비교적 크므로 후속 스레드는 문제를 줄여줍니다.

동일한 프로세스의 여러 스레드는 프로세스의 동일한 리소스(주로 메모리파일 설명자 테이블 ) 를 공유합니다.

  • 메모리 ~~ 스레드 1new의 개체는 스레드 2, 3, 4에서 직접 사용할 수 있습니다.
  • 파일 설명 테이블 ~~스레드 1에서 열린 파일은 스레드 2, 3, 4에서 직접 사용할 수 있습니다.

운영체제가 실제로 스케줄링을 할 때 쓰레드 단위로 스케줄링이 된다.
(이전 블로그에서 프로세스 스케줄링에 대해 이야기했는데, 이는 각 프로세스에 쓰레드가 하나만 있는 상황과 동일하다.)

각 프로세스에 여러 스레드가 있는 경우 각 스레드는 CPU에서 독립적으로 예약됩니다.
=> 스레드는 운영 체제 예약 및 실행의 기본 단위입니다.

스레드가 여러 프로세스에 있을 수 있습니까?
아니요! 프로세스와 스레드 간에는 일대다 관계가 있습니다.

하나의 스레드는 하나의 코어에서 실행되는데, 프로세스에 두 개의 스레드가 있는 경우
스레드 1은 코어 A에서 실행되고 스레드 2는 코어 B에서 실행될 수 있습니다
. , 하지만 케어 스레드만

스레드는 PCB로도 기술되며
프로세스는 하나의 PCB에 해당할 수도 있고 여러 개의 PCB에 해당할 수도 있습니다.
앞서 소개한 것처럼 PCB의 상태, 컨텍스트, 우선순위 및 계정 정보는 모두 각 스레드마다 고유합니다. 하지만 같은 프로세스 내의 PCB간에는 pid가 같고, 메모리 포인터와 파일 표현 테이블도 같다
=> 같은 pid 로 같은 프로세스인 것으로 구별된다.

"스케줄링"에 대해 말하면 프로세스와는 아무런 관련이 없습니다.
프로세스는 리소스 할당을 담당합니다.
스레드는 스케줄링과 관련된 모든 것을 담당합니다 .

이론은 프로그래밍에서 우리를 안내하는 것입니다. 이 이론을 이해하지 못한다면,
이 코드가 왜 이렇게 작성되었는지 모르겠습니다!!!

그래픽 이해

초기 요구사항은
웃긴 노인 1명, 닭 100마리 먹기~~ 효율성이 상대적으로 낮습니다.
이미지-20230918154821678

더 빨리 먹는 방법에는 아래와 같이 다중 프로세스 다중 스레드 두 가지 버전이 있습니다 . 닭고기를 먹는 다중 프로세스 방법: ~~ 비용이 상대적으로 높습니다.

이미지-20230918155101436

멀티스레드 치킨 게임:

이미지-20230918155347489여기 웃긴 노인네 숫자를 늘리면 속도가 빨라질 수 있을까요???

이미지-20230918160353714

그런데 계속해서 웃긴 아저씨들이 늘어나면???
이미지-20230918163513486

(1) 멀티스레딩의 경우 여러 명의 웃긴 늙은이들이 같은 치킨을 나눠 먹다가 이때 싸울 수도 있습니다.

이미지-20230918170001294

(2) 멀티스레딩에는 또 다른 상황이 있습니다~~

이미지-20230918163927661

참고: Chrome 브라우저는 다중 프로세스 프로그래밍 모델을 사용해야 합니다(각 탭 페이지는 프로세스입니다)
~~ 목적은 한 페이지가 정지되고 다른 페이지가 사라지는 것을 방지하는 것입니다.

보안 문제는 언제 발생하나요???

여러 실행 흐름이 동일한 공유 리소스에 액세스하는 경우
~~ 스레드 모델은 자연스럽게 리소스 공유입니다. 동일한 리소스(동일 변수)를 놓고 경쟁하는 여러 스레드를 트리거하는 것은 매우 쉽습니다.
~~ 프로세스 모델은 자연스럽게 리소스가 격리되지 않고 격리됩니다. 트리거하기 쉽습니다.
프로세스 간 통신 시 동일한 리소스에 액세스하는 여러 프로세스가 문제를 일으킬 수 있습니다.

멀티스레딩은 효율성을 향상시키지만 멀티 프로세스만큼 안전하지는 않습니다.
하지만 코드를 잘 작성하면 스레드 안전성 문제는 걱정되지 않습니다.
프로세스 모델
직장에서 멀티스레딩을 접할 때 멀티프로세스보다 더 많은 상황이 있습니다.특히 Java 환경에서는 더욱 그렇습니다.

인터뷰에서 프로세스와 스레드의 차이점은 "운영 체제" 모듈에서 가장 자주 묻는 질문입니다. 단 하나도 없습니다!!!




Java에서 다중 스레드 프로그래밍을 수행하는 방법

스레드에 대한 작업은 모두 운영체제에서 제공하는 API입니다.
Java는 크로스 플랫폼 언어입니다. 운영체제에서 제공하는 많은 기능이 JVM에 의해 캡슐화됩니다.
시스템의 기본 API를 배울 필요는 없으며, 배우기만 하면 됩니다. Java에서 제공하는 API입니다.


Java는 어떻게 크로스 플랫폼을 달성합니까???
  • Windows 시스템, Windows 버전의 JVM 구현

  • Linux 시스템은 Linux 버전의 JVM을 구현합니다.

  • Mac 시스템, Mac 버전의 JVM 구현

  • 모든 시스템은 해당 버전의 JVM도 구현합니다.

수많은 다양한 버전의 JVM이 크로스 플랫폼을 지원합니다.
이러한 다양한 JVM은 다양한 시스템의 API를 캡슐화하고 모두 동일한 규칙을 실행하는 바이트코드입니다.
예: 유럽을 여행하고, 다양한 국가에 체크인하고,
도착합니다. 영국 현지인과의 의사소통을 돕기 위해 영국 통역사(영어와 중국어를 모두 구사함)를 고용했고,
프랑스에 도착했을 때 프랑스어 통역사(프랑스어와 중국어를 모두 구사함)를 고용하여 도움을 주었습니다. 현지인;
독일에 도착했을 때 현지인과의 의사소통을 도와줄 독일어 번역가(독일어와 중국어를 모두 구사할 수 있음)를 찾았습니다. ...
Java
는 크로스 플랫폼이므로 여러 플랫폼이 있습니다. 여러 "번역"이 있습니다. ..
~~ 댓글: "힘든 노력은 기적을 낳을 수 있습니다!!!"


Java는 핵심 클래스인 Thread를 멀티 스레드로 운영합니다.

~~ Thread 클래스를 사용할 때 java.lang 아래에 있기 때문에 다른 패키지를 import할 필요가 없습니다.
비슷한 패키지로는 String, StringBuilder, StringBuffer가 있습니다.

스레드를 생성할 때 스레드가 독립적인 실행 스트림이 되기를 바라며 코드 조각을 실행할 수 있어야 합니다.

최초의 멀티스레드 프로그램

코드는 아래와 같이 표시됩니다.

class MyThread extends Thread{
    
    
    @Override
    public void run() {
    
    
        while(true){
    
    
            System.out.println("hello thread");
            // 为了让这里的打印慢点, 方便看, 加个sleep, 休眠 1s
            try {
    
    
                Thread.sleep(1000);
            }catch (InterruptedException e){
    
    
                e.printStackTrace();
            }
        }
    }
}

public class ThreadDemo1 {
    
    
    public static void main(String[] args) {
    
    
        Thread t = new MyThread();
        t.start();

        while(true){
    
    
            System.out.println("hello main");
            try {
    
    
                Thread.sleep(1000);
            }catch (InterruptedException e){
    
    
                e.printStackTrace();
            }
        }
    }
}

작업 결과:

이미지-20230918212013737

운영 체제는 스레드를 스케줄링할 때 " 선점 실행 "을 수행합니다. 어떤 스레드가 먼저 오고 어떤 스레드가 마지막에 오는지는 확실하지 않습니다. 이는 운영 체제 스케줄러의 구체적인 구현 전략에 따라 다릅니다. 우선
순위가 있지만 우선 순위가 있을 수는 없습니다. 응용프로그램 수준에서 수정됨..
응용프로그램(코드) 관점에서 보면 스레드 간의 스케줄링 순서가 "무작위"인 것과 같은 효과가 있습니다. => 커널
자체는 무작위가 아닙니다. 그러나 개입 요소가 너무 많고, 응용 프로그램 첫 번째 계층에서는 세부 사항을 인식할 수 없으므로
무작위로만 간주될 수 있습니다!!!
실행 순서는 커널에 의해 구현되므로 해결책은 없지만 일부 API를 통해 제한적인 개입이 수행될 수 있습니다
. 스레드 안전 문제의 가장 심각한 문제는 선점형 실행과 임의 스케줄링이 원인 입니다 .

여기서 Thread 클래스는 본질적으로 시스템의 스레드를 캡슐화한 것입니다.
각 Thread 개체는 시스템의 스레드(PCB에 해당)에 해당합니다.

메서드 재정의: 상위 클래스에는 메서드가 있고, 하위 클래스에는 이름과 매개변수가 동일한 메서드가 있습니다.
하위 클래스의 메소드는 동적 바인딩 메커니즘에 의해 호출됩니다.
Thead t = new MyThread();
t.run();t는 상위 클래스에 대한 참조이지만 여기서 호출되는 실행은 여전히 ​​하위 클래스의 메소드입니다.(t는 본질적으로 하위 클래스가 가리키는 객체입니다.)

오버로드: 동일한 범위, 동일한 이름을 가진 여러 메서드, 다른 수 또는 유형의 매개 변수,
동일한 클래스 또는 하위 클래스와 상위 클래스 사이...

t.sart();=> 쓰레드를 시작하기 위한 쓰레드 내의 특별한 메소드
Start는 run을 호출하지 않고 쓰레드를 생성한다. 새로운 쓰레드는 run 메소드를 실행한다.
start가 새로운 쓰레드를 생성하는 방법 => 운영체제의 API를 호출하는 것이다.
운영 체제 커널은 새 스레드의 PCB(프로그램 제어 블록)를 생성하고 실행될 명령을 이 PCB에 전달합니다.
PCB가 CPU에서 실행되도록 예약되면 스레드의 run 메소드에 있는 코드도 실행됩니다. .run
메서드의 경우 실행이 완료된 후 새 스레드가 자연스럽게 소멸됩니다.

이 새로운 Thread 객체의 연산은 스레드를 생성하지 않습니다.(여기서 언급하는 스레드는 시스템 커널의 PCB를 의미합니다.) PCB는
start를 호출하여 생성되며, 그래야만 실제 스레드가 있을 수 있습니다.

시작과 실행의 차이점:
시작은 실제로 스레드(시스템에서 생성됨)를 생성하고 스레드는 독립적인 실행 흐름입니다.
실행은 스레드가 수행할 작업을 설명합니다. 메인에서 직접 실행을 호출하면 새 스레드가 없습니다. 이때 생성된 스레드는 모두 단독으로 작동합니다.

PCB는 스레드에 해당합니다. 하나의 스레드는 하나의 PCB에 해당하고, 하나의 프로세스는 여러 개의 PCB에 해당합니다.
프로세스에 스레드가 하나만 있으면 하나의 프로세스가 하나의 PCB에 해당합니다.
동일한 프로세스에 있는 여러 PCB가 동일한 pid를 갖습니다. 프로세스는 다릅니다. .
PCB는 "약어"가 아니라 프로세스/스레드가 어떻게 구현되고 설명되는지를 반영하는 데이터 구조입니다.
PCB는 운영 체제 책에서 언급되는 개념일 뿐입니다. 실제로 이름은 프로세스입니다. Linux의 해당 구조는 task_struct입니다.

위 코드에서 두 스레드를 시각적으로 보는 방법???
JDK와 함께 제공되는 jconsole 도구를 사용하면 현재 Java 프로세스의 모든 스레드를 볼 수 있습니다.
이미지-20230918215407492

참고: JDK => javac 및 java뿐만 아니라 많은 도구가 포함된 Java Development Kit.
이미지-20230918220407913

참고: 프로세스에는 스레드가 포함되어 있습니다. => 스레드를 보려면 먼저 해당 프로세스를 찾은 다음 해당 프로세스에 어떤 스레드가 있는지 확인해야 합니다.

위 그림에서 thread.ThreadDemo1을 클릭하세요.
이미지-20230918220859922

이미지-20230918221747327

JConsole을 열었지만 로컬 프로세스에 아무것도 표시되지 않으면 마우스 오른쪽 버튼을 클릭하고 관리자 권한으로 실행해 보세요.

이미지-20230918224445511



Java에서 스레드를 생성하는 다양한 방법
  1. 스레드를 상속하고 실행 메서드를 재정의합니다.

    1. Thread를 상속하여 스레드 클래스 생성
    2. MyThread 클래스의 인스턴스 생성
    3. start 메서드를 호출하여 스레드 시작

    
    class MyThread extends Thread{
          
          
        @Override
        public void run() {
          
          
            System.out.println("hello thread");
        }
    }
    public class ThreadDemo1 {
          
          
        public static void main(String[] args) {
          
          
            Thread t = new MyThread();
            t.start();
        }
    }
    
  2. Runnable 인터페이스 구현

    1. Runnable 인터페이스 구현
    2. Thread 클래스 인스턴스를 생성하고 Thread 생성자를 호출할 때 Runnable 객체를 대상 매개변수로 사용
    3. start 메소드 호출

    Runnable의 기능은 "실행할 작업"을 기술하는 것이며, run 메소드는 해당 작업의 실행 세부 사항입니다.

    class MyRunnable implements Runnable {
          
          
        @Override
        public void run() {
          
          
            System.out.println("hello thread");
        }
    }
    public class ThreadDemo2 {
          
          
        public static void main(String[] args) {
          
          
            // 这只是描述了一个任务
            Runnable runnable = new MyRunnable();
            // 把任务交给线程来执行
            Thread t = new Thread(runnable);
            t.start();
        }
    }
    

    이런 방식으로 작성하면 장점:
    디커플링. 스레드가 수행하는 작업에서 스레드를 분리하는 것이 목적입니다.
    나중에 코드를 변경하려는 경우 여러 스레드를 사용하거나 여러 프로세스를 사용하거나 스레드 풀을 사용할 필요가 없습니다. , 또는 코루틴... 이때 코드 변경 사항은 비교적 사소합니다.

  3. 익명의 내부 클래스를 사용하고 Thread에서 상속

    1. Thread의 하위 클래스를 만듭니다.(하위 클래스에는 이름이 없습니다.) 그래서 "익명"이라고 합니다.
    2. 하위 클래스의 인스턴스를 만들고 t 참조가 인스턴스를 가리키도록 합니다.

    public class ThreadDemo3 {
          
          
        public static void main(String[] args) {
          
          
            Thread t = new Thread(){
          
          
                @Override
                public void run() {
          
          
                    System.out.println("hello");
                }
            };
            t.start();
        }
    }
    
  4. 익명 내부 클래스를 사용하여 Runnable 구현

    이 작성 방법은 본질적으로 방법 2와 동일하며
    Runnable을 구현하는 작업을 익명의 내부 클래스에 할당하는 구문일 뿐입니다.
    여기서는 클래스가 생성되고 Runnable이 구현되며 클래스의 인스턴스가 생성되고 Thread 생성자에게 전달됩니다.

    public class ThreadDemo4 {
          
          
        public static void main(String[] args) {
          
          
            Thread t =new Thread(new Runnable() {
          
          
                @Override
                public void run() {
          
          
                    System.out.println("hello");
                }
            });
            t.start();
        }
    }
    
  5. 람다식을 써보세요~~ 가장 간단하고 추천하는 작성방법

    람다 표현식을 사용하여 작업을 설명하고
    람다를 Thread 생성자에 직접 전달합니다.
    람다는 한 번 사용되면 소멸되는 익명 함수(이름 없는 함수)입니다.
    Java의 함수는 클래스 없이는 존재할 수 없습니다. 다른 함수와 호환되기 위해서는 언어인 Java Alignment는 형편없는 기능적 인터페이스를 만들고 이를 통해 람다를 구현했습니다.

    public class ThreadDemo5 {
          
          
        public static void main(String[] args) {
          
          
            Thread t = new Thread(()-> {
          
          
                System.out.println("hello");
            });
            t.start();
        }
    }
    

Supongo que te gusta

Origin blog.csdn.net/m0_73740682/article/details/133002382
Recomendado
Clasificación