[읽기 노트] 2 장 : Java 병렬 기초- "실용적인 Java 높은 동시성 프로그래밍"

2 장 : Java 병렬 기본 사항

2.1 프로세스와 스레드

  • 프로세스 : 자원 할당 및 스케줄링을위한 시스템의 기본 단위.
  • 스레드 : 프로그램 실행의 가장 작은 단위.

프로세스는 스레드의 컨테이너입니다.

2.2 스레드의 기본 동작

2.2.1 새 스레드

Thread t1 = new Thread(); //创建线程对象
t1.start();//启动线程

start ()는 새 스레드를 생성하고이 스레드가 run () 메서드를 실행하도록합니다. 따라서 새 스레드가 무언가를 실행해야하는 경우 run () 메서드를 다시 작성해야합니다.

예 :

Thread t1 = new Thread(){
    
    //使用匿名类重写run()方法
	@Override
	public void run(){
    
    
		System.out.println("Hello,I am t1");
	}
};
t1.start();

새 스레드를 시작하기 위해보다 합리적이고 일반적으로 사용되는 방법 : (다음은 "Head First Java"에서 가져온 것입니다)

① 실행 가능 객체 생성 (스레드 작업)

Runnable threadJob = new MyRunnable();

MyRunnable 클래스는 Runnable 인터페이스에서 상속되며이 인터페이스에는 재정의해야하는 run () 메서드가 하나만 있습니다.

② Thread 객체 생성 및 Runnable 할당 (태스크)

Thread myThread = new Thread(threadJob);

③ 시작 스레드

myThread.start();

start () 메서드는 Runnable (태스크)의 run () 메서드를 실행합니다.

전체 예 :

public class MyRunnable implements Runnable{
    
    
	public void run(){
    
    
		go();
	}
	public void go(){
    
    
		System.out.println("Hello,I am myThread!");
	}
}
class ThreadTester{
    
    
	public static void main(String [] args){
    
    
		Runnable threadJob = new MyRunnable(); //①
		Thread myThread = new Thread(threadJob); //②
		myThread.start(); //③
	}
}

2.2.2 스레드 종료

스레드는 스레드를 즉시 종료 할 수있는 stop () 메서드를 제공합니다. 그러나이 방법 은 권장되지 않습니다 .이 방법은 너무 폭력적이어서 스레드 종료의 절반을 강제로 수행해야 일부 데이터 불일치가 발생할 수 있습니다.

올바른 방법은 stopMe () 메서드를 직접 작성하는 것입니다.

예:

public static class ChangeObjectThread extends Thread {
    
    
    volatile boolean stopme = false;

    public void stopMe() {
    
    
        stopme = true;
    }
    @Override
    public void run() {
    
    
        while (true) {
    
    //一直执行,直到调用stopMe方法
            if (stopme) {
    
    
                System.out.println("exit by stop me");
                break;
            }
            synchronized (u) {
    
    
                int v = (int) (System.currentTimeMillis() / 1000);
                u.setId(v);
                try {
    
    
                    Thread.sleep(100);
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
                u.setName(String.valueOf(v));
            }
            Thread.yield();//yield()是让出资源
        }
    }
}

2.2.3 스레드 인터럽트

"스레드 중단은 스레드가 즉시 종료되는 것을 의미하지 않습니다. 대신 대상 스레드에 알림을 보내 누군가가 종료를 원한다는 것을 알려줍니다! 알림을받은 후 대상 스레드를 처리하는 방법은 완전히 작동 중입니다. 결정할 대상 스레드에. "

스레드 중단과 관련된 세 가지 방법 :

public void Thread.interrupt();//中断线程(通知目标线程中断,即设置中断标志位)
public boolean Thread.isInterrupted();//判断是否中断(通过检查中断标志位)
public static boolean Thread.interrupted();//判断是否中断,并清除当前中断状态

예:

public class CreateThread implements Runnable {
    
    
    @Override
    public void run() {
    
    
        while (true) {
    
    
            if (Thread.currentThread().isInterrupted()) {
    
    //如果检测到当前线程被标志为中断状态,则结束执行
                System.out.println("Interrupted");
                break;
            }
            try {
    
    
                Thread.sleep(2000);//休眠2s,当线程休眠中被中断会抛出异常
            } catch (InterruptedException e) {
    
    
                System.out.println("Interrupted When Sleep");
                Thread.currentThread().interrupt();//再次设置终端标记位
            }
            Thread.yield();
        }
    }

    public static void main(String[] args) throws InterruptedException {
    
    

        Thread t1 = new Thread(new CreateThread());
        t1.start();
        Thread.sleep(1000);//休眠1s
        t1.interrupt();//将t1设置为中断状态,需要在run()中进行中断处理
    }
}

참고 : Thread.sleep () 메서드는 중단으로 인해 예외가 발생합니다. 이때 중단 플래그가 지워집니다. 처리되지 않으면 다음주기 시작시 중단을 포착 할 수 없으므로 중단이 발생합니다. 예외 처리 비트에서 플래그가 다시 설정됩니다.

2.2.4 대기 및 알림

wait () 메서드와 notify () 메서드는 Object 클래스에 있으며 모든 객체는이 두 메서드를 호출 할 수 있습니다.

객체 인스턴스에서 wait () 메서드가 호출되면 다른 스레드가 notify () 메서드를 호출 할 때까지 객체가 계속 실행되는 것을 중지하고 대기 상태로 전환됩니다.

wait () 및 notify () 워크 플로 :

스레드가 object.wait ()를 호출하면 객체 객체의 대기 대기열에 들어갑니다. 시스템이 특정 개체 (자원)를 동시에 기다리는 여러 스레드를 실행하기 때문에이 대기 대기열에 여러 스레드가있을 수 있습니다. object.notify ()가 호출되면이 대기 큐에서 스레드 무작위로 선택 하고 깨 웁니다 .

그림 : (참고 : notifyAll () : 대기중인 큐에서 대기중인 모든 스레드를 깨 웁니다.)
여기에 사진 설명 삽입
참고 : wait () 메서드와 notify () 메서드는 해당 동기화 된 명령문, 즉 wait () 호출에 포함됩니다. 또는 notify () 먼저 개체 모니터를 얻고 실행 후 개체 모니터를 해제해야합니다.
여기에 사진 설명 삽입
예:

public class SimpleWN {
    
    
    final static Object object = new Object();
    public static class T1 extends Thread {
    
    
        public void run() {
    
    
            synchronized (object) {
    
    
                System.out.println(System.currentTimeMillis() + ":T1 start!");
                try {
    
    
                    System.out.println(System.currentTimeMillis() + ":T1 wait for object ");
                    object.wait();
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
                System.out.println(System.currentTimeMillis() + ":T1 end! ");
            }
        }
    }

    public static class T2 extends Thread {
    
    
        public void run() {
    
    
            synchronized (object) {
    
    
                System.out.println(System.currentTimeMillis() + ":T2 start! notify one thread ");
                object.notify();
                System.out.println(System.currentTimeMillis() + ":T2 end! ");
                try {
    
    
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
            }
        }
    }

    public static void main(String[] args) {
    
    
        Thread t1 = new T1();
        Thread t2 = new T2();
        t1.start();
        t2.start();
    }
}

동작 결과 :
여기에 사진 설명 삽입
위와 같이 T2가 T1을 깨운 후 T1은 즉시 실행할 수 없지만 T2가 객체의 잠금을 해제 한 후 T1은 성공적으로 객체의 잠금을 획득하여 실행을 계속합니다.

2.2.5 프로세스 일시 중지 및 재개

일시 중단 된 프로세스는 resume () 메서드가 실행을 계속할 때까지 기다려야합니다.

이 메서드 쌍은 권장되지 않습니다 . suspend () 메서드가 스레드를 일시 중단하는 동안 잠금 리소스를 해제하지 않아 교착 상태와 같은 나쁜 상황이 발생할 수 있기 때문입니다.

스레드가 종료 (결합)되고 양보 (수익) 될 때까지 기다립니다.

public final void join() throws InterruptedException
public final synchronized void join(long millis)throws InterruptedException

join () : 무선 대기를 나타내며 대상 스레드가 실행을 마칠 때까지 현재 스레드를 차단합니다.

join (long millis) : 최대 대기 시간을 제공합니다. 대상 스레드가 지정된 시간 이후에도 계속 실행중인 경우 현재 스레드는 대기 할 수 없기 때문에 계속 실행됩니다.

예:

public class JoinMain {
    
    
    public volatile static int i = 0;

    public static class AddThread extends Thread {
    
    
        public void run() {
    
    
            for (i = 0; i < 10000000; i++) ;
        }
    }

    public static void main(String[] args) throws InterruptedException {
    
    
        AddThread at = new AddThread();
        at.start();
        at.join();
        System.out.println(i);
    }
}

위와 같이 메인 함수에서 AddThread를 기다리는 데 join ()을 사용하지 않으면 얻은 i는 0이거나 아주 작은 숫자가 될 가능성이 있지만 join () 이후에는 메인 스레드가 기꺼이 기다릴 것임을 의미합니다. AddThread의 실행을 완료하고 AddThread로 진행합니다. Go, join ()이 반환되면 AddThread가 실행되었으므로 항상 10000000이됩니다.

public static native void yield();

yield () : 현재 스레드가 CPU를 포기하도록합니다. (CPU 포기는 현재 쓰레드가 실행되고 있지 않다는 의미가 아닙니다. 현재 쓰레드는 CPU 포기 후 CPU 자원을 놓고 경쟁하지만 다시 할당 할 수 있는지 여부는 반드시 아닙니다.)

2.3 휘발성 및 Java 메모리 모델 (JMM)

volatile을 사용하여 변수를 선언하면이 변수가 일부 프로그램이나 스레드에 의해 수정 될 가능성이 매우 높다는 것을 가상 머신에 알리는 것입니다. 이 변수가 수정 된 후 응용 프로그램 범위 내의 모든 스레드가 변경 사항을 "확인"할 수 있도록하기 위해 가상 머신은이 변수의 가시성을 보장하기 위해 몇 가지 특별한 방법을 사용해야합니다.

2.4 카테고리 별 관리 : 스레드 그룹

시스템에서 쓰레드의 수가 많고 함수 할당이 비교적 명확한 경우 동일한 함수의 쓰레드를 쓰레드 그룹에 배치 할 수있다.

스레드 그룹 사용 :

// 创建线程组
ThreadGroup tg = new ThreadGroup("PrintGroup");
// 使用Thread构造方法指定线程所属的县城组
Thread t = new Thread(tg,new ThreadTest(),"T1");

완전한 사용 예 :

public class ThreadGroupName implements Runnable {
    
    
	public static void main(String[] args) {
    
    
		ThreadGroup tg = new ThreadGroup("PrintGroup");
		Thread t1 = new Thread(tg,new ThreadGroupName(),"T1");
		Thread t2 = new Thread(tg,new ThreadGroupName(),"T2");
		t1.start();
		t2.start();
		System.out.println(tg.activeCount());
		tg.list();
 	}
 	
	@Override
	public void run() {
    
    
		String groupAndName = Thread.currentThread().getThreadGroup().getName()+ "-" + Thread.currentThread().getName();
		while(true) {
    
    
			System.out.println("I am " + groupAndName);
			try {
    
    
				Thread.sleep(3000);
			} catch (InterruptedException e) {
    
    
				e.printStackTrace();
			}
		}
	}
}

2.5 백그라운드 유지 : 데몬

데몬 스레드는 이름과 마찬가지로 특수한 스레드 입니다. 시스템의 보호자이며 백그라운드에서 일부 시스템 서비스를 자동으로 완료합니다 . 예를 들어 가비지 수집 스레드와 JIT 스레드는 데몬 스레드로 이해할 수 있습니다. 이에 상응하는 것은 사용자 스레드이며 사용자 스레드는이 프로그램이 완료해야하는 비즈니스 작업을 완료하는 시스템의 작업 스레드로 간주 될 수 있습니다 . 사용자 스레드가 모두 완료되면 프로그램이 실제로 할 일이 없음을 의미합니다. 데몬 스레드가 보호 할 개체가 더 이상 존재하지 않으므로 전체 응용 프로그램이 자연스럽게 종료되어야합니다. 따라서 Java 애플리케이션에 데몬 스레드 만있는 경우 Java 가상 머신은 자연스럽게 종료 됩니다.

데몬 스레드 사용 :

public class DaemonDemo {
    
    
	public static class DaemonT  extends Thread{
    
    
    	public void run(){
    
    
	       while (true){
    
    
	           System.out.println("I am Alive");
	           try {
    
    
	               Thread.sleep(1000);
	           } catch (InterruptedException e) {
    
    
	               e.printStackTrace();
	           }
	       }
		}
    }
    public static void main(String[] args) throws InterruptedException {
    
    
        Thread t=new Daemon();
        t.setDaemon(true);//设置守护线程(该设置一定要在start()之前设置,否则设置无效)
        t.start();
        Thread.sleep(2000);
    }
}

위와 같이 t는 데몬 쓰레드로 설정되고 메인 쓰레드 main만이 시스템의 사용자 쓰레드이므로 메인 쓰레드가 2 초간 휴면 후 종료하면 전체 프로그램이 종료된다. t가 데몬 스레드로 설정되지 않은 경우 t 스레드는 주 스레드가 종료 된 후에도 계속 인쇄되며 절대로 끝나지 않습니다.

2.6 스레드 우선 순위

Java의 스레드는 고유 한 우선 순위를 가질 수 있습니다. 우선 순위가 높은 스레드는 자원을 놓고 경쟁 할 때 이점이 있고 자원 을 장악 할 가능성 이 더 높습니다. 물론 이것은 확률 문제 일뿐입니다. 운이 좋지 않으면 우선 순위가 높은 스레드도 선점하지 못할 수 있습니다.

2.7 스레드 안전성의 개념과 동기화 된 키워드

스레드 안전성은 병렬 프로그램의 기초입니다.

동기화 된 키워드를 사용하여 스레드 안전성을 보장 할 수 있습니다.

동기화 된 키워드의 역할은 스레드 간의 동기화를 달성하는 것입니다. 그 작업은 동기화 된 코드를 잠 가서 한 번에 하나의 스레드 만 동기화 블록에 들어갈 수 있도록하여 스레드 간의 보안을 보장하는 것입니다.

동기화 된 키워드는 여러 가지 방법으로 사용할 수 있습니다.

  • 잠금 개체 지정 : 지정된 개체를 잠그고 동기화 코드를 입력하기 전에 지정된 개체의 잠금을 가져옵니다.
  • 인스턴스 메서드에서 직접 작동 : 이는 현재 인스턴스를 잠그고 동기화 코드를 입력하기 전에 현재 인스턴스의 잠금을 얻는 것과 같습니다.
  • 정적 메서드에서 직접 작동 : 이는 현재 클래스를 잠그고 동기화 코드를 입력하기 전에 현재 클래스의 잠금을 얻는 것과 같습니다.

스레드 동기화 및 스레드 안전성 보장에 사용되는 것 외에도 동기화는 스레드 간의 가시성과 순서를 보장 할 수 있습니다. 가시성의 관점에서 동기화는 휘발성의 기능을 완전히 대체 할 수 있지만 사용하기가 그렇게 편리하지는 않습니다. 시퀀스 측면에서 동기화는 한 번에 하나의 스레드 만 동기화 된 블록에 액세스 할 수 있도록 제한하기 때문에 동기화 된 블록의 코드가 순서없이 실행 되더라도 직렬 의미가 일치하는 한 실행 결과는 항상 똑같다. 다른 접근 쓰레드는 락을 획득 한 후 데이터를 읽기 위해 코드 블록에 들어가야하므로 최종 결과는 코드의 실행 과정에 의존하지 않으므로 순서 문제는 자연스럽게 해결됩니다. 스레드는 직렬로 실행됩니다).

2.8 프로그램의 숨겨진 오류

자세한 내용은 "Practical Java High Concurrency Programming"의 P61-P69를 참조하십시오.

추천

출처blog.csdn.net/qq_43424037/article/details/113659484