java.util.function 패키지


  • java.util.function패키지에 일반적으로 자주 쓰이는 형식의 메서드를 함수형 인터페이스로 미리 정의해 놓았다.
  • 매개변수와 반환값의 유무에 따라 4개의 함수형 인터페이스가 정의되어 있고, Function 의 변형으로 Predicate가 있다.

 

Runnable : 매개변수도 없고, 반환값도 없음
Supplier<T> : 매개변수는 없고 반환값만 있는 공급자
Consumer<T> : 매개변수만 있고, 반환값은 없는 소비자
Function<T,R> : 하나의 매개변수와 하나의 결과를 반환하는 일반적인 함수
Predicate<T> : 조건식을 표현하는데 사용, 하나의 매개변수와 boolean 타입의 반환값

 

  • Predicate는 반환값이 boolean이라는 것만 제외하면 Function과 동일하다.
  • Predicate는 조건식을 함수로 표현히는데 시용된다.

 

Predicate는 Function의 변형으로, 반환타입이 boolean이라는 것만 다르다. Predicate 는 조건식을 람다식(함수)으로 표현하는데 사용된다

 

 

 

 

 

java.util.function 패키지 Quiz


  1. 1~100 범위의 숫자를 반환만 하므로 공급자 > Supplier<Integer>
  2. 매개변수만 있고 반환값을 없으므로 수요자 > Consumer<Integer>
  3. 조건식이 있으므로 Predicate<Integer, Boolean>
  4. 매개변수 하나와 반환값이 있으므로 Function<Integer, Integer>

 

 

매개변수가 2개인 함수형 인터페이스 


  • 매개변수의 개수가 2개인 함수형 인터페이스는 이름 앞에 접두사 "Bi’가 붙는다
  • BiSupplier 는없다. 함수의 반환값은 0개 혹은 1개만 가능하기 때문이다. 반환값이 2개일 수는 없다. 

 

  • BiConsumer : 두개의 매개변수만 있고. 반환값이 없음
  • BiPredicate : 조건식을 표현하는데 사용됨. 매개변수는 둘, 반환값은 boolean
  • BiFunction : 두 개의 매개변수를 받아서 하나의 결과 반환

 

  • 두 개 이상의 매개변수를 갖는 함수형 인터페이스가 필요하다면 직접 만들어서 써야한다.
  • 만일 3개의 매개변수를 갖는 함수형 인터페이스를 선언한다면 다음과 같을 것이다

 

 

 

UnaryOperator와 BinaryOperator


  • Function의 또 다른 변형으로 UnaryOperator와 BinaryOperator 있다.
  • 매개변수의 타입과 반환타입의 타입이 모두 일치한다는 점만 제외하고는 Function과 같다.

예제

  • 예제속 출력된 값을 먼저 보자 
  • 첫번째 출력줄[90, 9, 16, 77...] 등은 난수를 만들어내는 람다식 s가 list로 출력된 결과이다. 
  • 두번째 [90, 16, 54...] 값은 Predicate 조건식에 의해 나누어떨어진 값이 0인 수들만 출력된 것이다.
  • 즉 PrintEvenNum 메소드에 의해 짝수들만 출력되었다.
  • 세번째 [90,0,10,70...] 출력값은 Function f에의해 i의 일의 자리를 없앤후 출력된 결과들이다. 

 

 

 

 

 

 

 

 

 

함수형 인터페이스


  • 람다식을 다루기 위한 인터페이스를 ‘함수형 인터페이스(functional interface)’라고 한다.
  • 단, 함수형 인터페이스에는 오직 하나의 추상 메서드만 정의되어 있어야 한다는 제약이 있다.
  • 그래야 람다식과 인터페이스의 메서드가 1:1로 연결될 수 있기 때문이다.

'@ Functionallnterface' 붙이면. 컴파일러가 함수형 인터페이스를 올바르게 정의하였는지 확인해준다.

 

 

 

  • 메서드의 매개변수가 MyFunction타입이면, 이 메서드를 호출할 때 람다식을 참조하는 참조변수를 매개변수로 지정해야한다는 뜻이다.
  • 또는 참조변수 없이 직접 람다식을 매개변수로 지정하는 것도 가능하다.
  • 참조변수 f의 타입은 참조형이므로 클래스 또는 인터페이스가 가능하다. 또한 람다식과 동등한 메서드가 정의되어 있는 것이어야 한다. 그래야만 참조변수로 익명 객체(람다식)의 메서드를 호출할 수 있기 때문이다.

 

  • MyFunction인터페이스에 정의된 메서드 max()는
  • 람다식 ‘(int a, int b) -〉a 〉b ? a : b’과 메서드의 선언부가 일치한다.

 

 

 

  • 함수형 인터페이스를 정의하기 전, '@Functionallnterface'를 붙이도록 한다.
  • 안 붙여도 에러는 뜨지않으나 컴파일러가 함수형 인터페이스를 올바르게 정의하였는지 확인해준다.

 

 

  • 어노테이션을 붙인 상태에서 인터페이스에 두개이상의 추상메소드를 작성하면 에러줄이 뜬다.
  • 인터페이스는 여러개의 추상메소드를 가질 수 있으나
  • 어노테이션이 하나의 함수형 인터페이스 내에 하나의 추상메소드만 존재하는지 확인해주기 때문이다. 

 

 

  • public abstract를 생략해도 무방한 이유는 
  • 인터페이스 내 모든 메소드는 public  abstract 를 암묵적으로 가지고 있기 때문이다.  

 

 

  • 하지만 이 함수형 인터페이스 MyFunction을 구현하는 main 메소드에서는
  • max 앞에 public int를 붙여주어야 하는데 
  • 오버라이딩 규칙 중 접근제어자는 부모범위보다 더 좁게 못바꾼다는 점 때문이다. 
  • max 앞에 public int를 생략하면 default의 의미를 가지게 되어 범위가 좁아진다.

 

 

  • 람다식(익명객체)를 다루기 위한 참조변수의 타입은 함수형 인터페이스로 한다. 

 

 

  • 람다식의 매개변수와 반환타입이 함수형 인터페이스와 동일해야 한다. 

 

 

함수형 인터페이스 예제


 

 

 

 

 

 

 

 

 

 

 

람다식 


  • 람다식의 도입으로 인해, 이제 자바는 객체지향언어(OOP)인 동시에 함수형 언어가 되었다.
  • 람다식(Lambda expression)은 메서드를 하나의 ‘식(expression)’으로 표현한 것이다.
  • 메서드를 람다식으로 표현하면 메서드의 이름과 반환값이 없어지므로, 람다식을 ‘익명 함수(anonymous function)’이라고도 한다.

  • 모든 메서드는 클래스에 포함되어야 하므로 클래스도 새로 만들어야 하고, 객체도 생성해야만 비로소 이 메서드를 호출할 수 있다.
  • 그러나 람다식은 이 모든 과정없이 오직 람다식 자체만으로도 이 메서드의 역할을 대신할 수 있다.
  • 람다식은 메서드의 매개변수로 전달되어지는 것이 가능하고, 메서드의 결과로 반환될 수도 있다.
  • 람다식으로 인해 메서드를 변수처럼 다루는 것이 가능해진 것이다.
Q. 메서드와 함수의 차이
A. 전통적으로 프로그래밍에서 함수라는 이름은 수학에서 따온 것입니다. 수학의 함수와 개 
념이 유사하기 때문이죠. 그러나 객체지향개념에서는 함수(function)대신 객체의 행위나 동 
작을 의미하는 메서드(method)라는 용어를 사용합니다. 메서드는 함수와 같은 의미이지만, 
특정 클래스에 반드시 속해야 한다는 제약이 있기 때문에 기존의 함수와 같은 의미의 다른 
용어를 선택해서 사용한 것입니다. 그러나 이제 다시 람다식을 통해 메서드가 하나의 독립적 
인 기능을 하기 때문에 함수라는 용어를 사용하게 되었습니다.
  • 즉 함수는 클래스에 독립적이나 메서드는 반드시 클래스에 작성해야하므로 종속적이다. 

 

 

 

람다식 작성하기


  1. 람다식은 메서드에서 이름과 반환타입을 제거하고
  2. 매개변수 선언부와 몸 통{ } 사이
  3. ‘ -> ’를 추가 한다.

 

  • 이때는 ‘문장(statement)’이 아닌 ‘식’이므로 끝에 ‘;’을 붙이지 않는다.

 

  • 람다식에 선언된 매개변수의 타입은 추론이 가능한 경우는 생략할 수 있는데, 대부분의 경우에 생략가능하다.
  • 람다식에 반환타입이 없는 이유도 항상 추론이 가능하기 때문이다.

‘(int a. b) -> a > b ? a : b’와 같이 두 매개변수 중 어느 하나의 타입만 생략하는 것은 허용되지 않는다.

 

  • 선언된 매개변수가 하나뿐인 경우에는 괄호( ) 를 생략할 수 있다.
  • 단, 매개변수의 타입이 있으면 괄호()를 생략할 수 없다.

 

  • 괄호{ } 안의 문장이 하나일 때는 괄호{ }를 생략할 수 있다.
  • 이 때 문장의 끝에 ';'(세미콜론) 을 붙이지 않아야 한다는 것에 주의한다.

 

  • 그러나 괄호{ } 안의 문장이 return문일 경우 괄호를 생략할 수 없다.

 

 

 

 

익명객체 람다식


  • 자바에서 모든 메서드는 클래스내에 포함되어야 한다.
  • 람다식이 메서드와 동등한 것처럼 설명했지만, 사실 람다식은 익명 클래스의 객체와 동등하다.
  • 람다식으로 정의된 익명 객체의 메서드를 어떻게 호출할 수 있을 것인가? 이미 알고 있는 것 처럼 참조변수가 있어야 객체의 메서드를 호출 할 수 있으니 이 익명 객체의 주소를 f라는 참조변수에 저장해 본다.
타입 f = (int a,int b) -> a>b ? a:b;

 

  • 참조변수 f의 타입은 어떤 것이어야 할까?
  • 참조형이니까 클래스 또는 인터페이스 가 가능하다.
  • 그리고 람다식과 동등한 메서드가 정의되어 있는 것이어야 한다.
  • 그래야만 참조변수로 익명 객체(람다식)의 메서드를 호출할 수 있기 때문이다.

 

  • 아래와 같이 max()라는 메서드가 정의된 MyFunction인터페이스가 정의되어 있다고 가정하자

 

 

 

  • 이 인터페이스를 구현한 익명 클래스의 객체는 다음과 같이 생성할 수 있다.

 

 

  • MyFunction인터페이스에 정의된 메서드 max()는 람다식 ‘(int a, int b) -〉a 〉b ? a : b’과 메서드의 선언부가 일치한다. 그래서 위 코드의 익명 객체를 람다식으로 아래와 같이 대체할 수 있다
MyFunction f = (int a,int b) -> a>b ? a:b;
int big = f.max(5,3);

 

 

MyFunction인터페이스를 구현한 익명 객체를 람다식으로 대체가 가능한 이유는

  • MyFunction인터페이스를 구현한 익명 객체의 메서드 max()
  • 람다식의 매개변수의 타입과 개수 그리고 반환값이 일치하기 때문이다
  • 그러므로 람다식을 다루기 위한 인터페이스를 ‘함수형 인터페이스(functional interface)’라고 한다.

 

 

 

 

 

 

 

 

 

wait()과 notify()


  • synchronized로 동기화해서 공유 데이터를 보호하는 것 까지는 좋은데, 특정 쓰레드가 객체의 락을 가진 상태로 오랜 시간을 보내지 않도록 하는 것도 중요하다.
  • 다른 쓰레드들은 모두 해당 객체의 락을 기다리느라 다른 작업들도 원활히 진행되지 않으면 안되기 때문이다.
  • 이러한 상황을 개선하기 위해 고안된 것이 바로 wait()과 notify()이다.

 

 

wait() - 객체의 lock을 풀고 쓰레드를 해당 객체의 waiting pool 에 넣는다.
notify() - waiting pool에서 대기중인 임의의 쓰레드 중 하나를 깨운다.
notifyAll() - waiting pool에서 대기중인 모든 쓰레드를 깨운다.

 

  • 동기화된 임계 영역의 코드를 수행하다가 작업을 더 이상 진행할 상황이 아니면, wait()을 호출하여 쓰레드가 락을 반납하고 기다리게 한다.
  • 그러면 다른 쓰레드가 락을 얻어 해당 객체에 대한 작업을 수행할 수 있게 된다.
  • 나중에 작업을 진행할 수 있는 상황이 되면 notify()를 호출해서, 작업을 중단했던 쓰레드가 다시 락을 얻어 작업을 진행할 수 있게 한다.
  • 단, 오래 기다린 쓰레드가 락을 먼 얻는다는 보장은 없다. 다소 비효율적인 측면이 있다. 
  • notify()가 호출되면, 해당 객체의 대기실에 있던 모든 쓰레드 중에서 임의의 쓰레드만 통지를 받는다.

 

 

 

 

 

 

 

  • 실행결과에서는 2가지 종류의 예외가 발생했는데,
  • 요리사(Cook) 쓰레드가 테이블에 음식을 놓는 도중에, 손님(Customer) 쓰레드가 음식을 가져가려했기 때문에 발생하는 예외(ConcurrentModificationExcetion)
  • 다른 하나는 손님 쓰레드가 테이블의 마지막 남은 음식을 가져가는 도중에 다른 손님 쓰레드가 먼저 음식을 낚아채버려서 있지도 않은 음식을 테이블에서 제거하려했기 때문에 발생하는 예외 (IndexOutOffiomdsException) 이다.
  • 이런 예외들이 발생하는 이유는 여러 쓰레드가 테이블을 공유하는데도 동기화를 하지 않았기 때문이다.

 

 

  • 동기화이후엔 더이상 같은 예외는 발생하지않지만 문제가있다.
  • 요리사 쓰레드가 음식을 추가하지 않고 손님 쓰레드를 계속 기다리게 한다는 것이다. 
  • 그 이유는 손님 쓰레드가 테이블 객체의 lock을 쥐고 기다리기 때문이다.
  • 요리사 쓰레드 가 음식을 새로 추가하려해도 테이블 객체의 lock을 얻을 수 없어서 불가능하다.
  • 이럴 때 사용하는 것이 바로 *wait() & notify()’이다.
  • 손님 쓰레드가 lock을 쥐고 기다리는 게 아니라, wait()으로 lock을 풀고 기다리다가 음식이 추가되면 notify()로 통보를 받고 다시 lock을 얻어서 나머지 작업을 진행하게 할 수 있다.

 

 

 

  • 이전 예제에 wait()과 notify()를 추가하였다.
  • 그리고 테이블에 음식이 없을 때뿐만 아니라, 원하는 음식이 없을 때도 손님이 기다리도록 바꾸었다.
  • wait이 호출된 손님 쓰레드는 락을 반납하고 waitpool에 들어가게 된다. 

 

 

 

 

 

 

 

 

 

 

  • 한 가지 문제가 있다면, 테이블 객체의 waiting pool에 요리사 쓰레드와 손님 쓰레드가 같이 기다린다는 것이다.
  • 그래서 notify()가 호출되었을 때, 요리사 쓰레드와 손님 쓰레드 중에서 누가 통지 를 받을지 알 수 없다.
  • 지독히 운이 나쁘면 요리사 쓰레드는 계속 통지를 받지 못하고 오랫동안 기다리게 되는데,
  • 이것을 ‘기아(starvation) 현상’이라고 한다.
  • 이 현상을 막으려면, notify()대신 notifyAIl()을 사용해야 한다.

 

 

 

 

 

 

 

 

 

 

쓰레드의 동기화 


  • 여러 쓰레드가 동시에 수행되는 멀티쓰레드는 하나가 다른하나에 영향을 미칠 수 있다.
  • 멀티쓰레드 프로세스의 경우 여러 쓰레드가 같은 프로세스 내의 자원을 공유해서 작업하기 때문에 서로의 작업에 영향을 주게 된다.

 

이러한 일이 발생하는 것을 방지하기 위해서 한 쓰레드가 특정 작업을 끝마치기 전까지 
다른 쓰레드에 의해 방해받지 않도록 하는 것이 필요하다. 그래서 도입된 개념이 바로 ‘임 
계 영역(critical section)’과 ‘잠금(락,lock)’이다

공유 데이터를 사용하는 코드 영역을 임계 영역으로 지정해놓고, 공유 데이터(객체)가 
가지고 있는 lock을 획득한 단 하나의 쓰레드만 이 영역 내의 코드를 수행할 수 있게 한 
다. 그리고 해당 쓰레드가 임계 영역 내의 모든 코드를 수행하고 벗어나서 lock을 반납해 
야만 다른 쓰레드가 반납된 lock을 획득하여 임계 영역의 코드를 수행할 수 있게 된다

 

 

  • 이처럼 한 쓰레드가 진행 중인 작업을 다른 쓰레드가 간섭하지 못하도록 막는 것을 ‘쓰레드의 동기화(synchronization)’라고 한다.
  • 동기화하려면 간섭받지 않을 문장들을 하나의 영역, 즉 임계 영역으로 묶으면 된다.
  • 이 묶는 행위는 synchronized 라는 키워드로 할 수 있다.
  • 임계영역은 락이 걸려 하나의 영역에 하나의 쓰레드만 출입 가능하게 된다. 

 

 

 

synchronized 를 이용한 동기화 


  • synchronized 로 임계영역을 설정하는 방법 두가지는 
  • 1. 메서드 전체를 임계영역으로 지정 (voide 앞에 synchronized 키워드 넣기)
  • 2. 특정 영역을 임계영역으로 지정하는 방법이 있다. synchronized(참조변수)
  • 첫 번째 방법은 메서드 앞에 synchronized를 붙이는 것인데, synchronized를 붙이면 메서드 전체가 임계 영역으로 설정된다
  • 두 번째 방법은 메서드 내의 코드 일부를 블럭{} 으로 감싸고 블럭 앞에 ‘synchronized (참조변수)’를 붙이는 것인데, 이때 참조변수는 탁을 걸고자하는 객체를 참조하는 것이어 야 한다.
  • 블럭을 synchronized 블럭이라고 부르며, 이 블럭의 영역 안으로 들어가면서 부터 쓰레드는 지정된 객체의 lock을 얻게 되고, 이 블럭을 벗어나면 lock을 반납한다.
  • 임계 영역은 멀티쓰레드 프로그램의 성능을 좌우하기 때문에 가능하면 메서드 전체에 락을 거는 것보다 synchronized블럭으로 임계 영역을 최소화해서 보다 효율적인 프로그 램이 되도록 노력해야한다.
  • 즉, 임계영역은 한번에 한 쓰레드만 들어갈 수 있는 만큼 그 영역 최소화 될 수록 좋다.

 

두 방법 모두 lock의 획득과 반납이 모두 자동적으로 이루어지므로 우리가 해야 할 일은 그저 임계 영역만 설정해주는 것뿐이다.

 

 

  • synchronized 하지않았을때는 한 쓰레드에 다른 쓰레드가 영향을 미쳐 마이너스 잔고가 뜨게 된다. 
  • 그 이유는 한 쓰레드가 if문의 조건식을 통과하고 출금하기 바로 직전에 다른 쓰레드가 끼어들 어서 출금을 먼저 했기 때문이다.
  • 즉, synchronized 키워드가 없을 때 이런 결과가 출력될 수 있다. 
  • withdraw메서드에 synchronized키워드를 붙이기만 하면 간단히 동기화가 된다.

 

 

 

  • 그러나 synchronized  키워드를 적어 해당 메소드를 임계영역으로 만들어주면 
  • 잔고가 마이너스로 출력되는 결과가 해결된다. 

 

 

  • 예제에서도 synchronized 키워드가 없으면 한쓰레드가 다른 쓰레드에 영향을 미쳐 잔고가 마이너스로 출력된다. 

 

 

 

 

 

 

 

 

 

 

 

 

 

join()


  • join은 작업하지않고 지정된 시간동안 다른 쓰레드가 작업하는 것을 기다리는 메소드다. 
  • 쓰레드 자신이 하던 작업을 잠시 멈추고 다른 쓰레드가 지정된 시간동안 작업을 수행하도록할 때 join()을 사용한다.
  • join()은 시간이 얼마나 됐든 작업이 끝날때까지 기다리며
  • join(long millis)는 정해진 시간동안 기다린다. 
  • join()도 sleep()처럼 interrupt()에 의해 대기상태에서 벗어날 수 있으며,
  • join()이 호출 되는 부분을 try-catch문으로 감싸야 한다.
  • join( )은 여러모로 sleep( )과 유사한 점이 많은데,
  • sleep()과 다른 점은 join()은 현재 쓰레드가 아닌 특정 쓰레드에 대해 동작하므로 static메서드가 아니라는 것이다

 

 

  • 참고로 콘솔창의 결과가 한줄로 나온다면 word wrap 버튼을 눌러 한화면안에 출력되도록 할 수 있다. 

 

 

 

  • join 메소드가 th1, th2 가 실행될동안 기다리는 작업을 했기에 
  • 콘솔창 마지막 소요시간 출력을 통해 
  • 해당 main 메소드는 가장 마지막에 실행을 마무리짓고 종료된 것을 확인할 수 있다. 

 

 

  • join() 메소드가 주석처리되면 main 메소드가 소요시간을 가장먼저 출력 후 종료되고
  • th1 과 th2가 차례로 실행된다. 
  • 소요시간은, join이 실행되기전 현재시간을 startTime 으로 받고 
  • 쓰레드가 종료되는 시점의 시간을 받아 앞선 startTime을 빼는 것이다. 
  • 종료시간 - 시작시간 = 소요시간

 

 

 

join() - 예시


  • 먼저 sleep()을 이용해서 10초마다 한 번씩 가비지 컬렉션을 수행하는 쓰레드를 만든 다음,
  • 쓰레드를 생성해서 데몬 쓰레드로 설정하였다
  • 데몬쓰레드는 반복문 while 로 작성되어있으며 값을 true 로 지정해 무한루프를 돌게한다. 
  • 무한루프를 도는 동안 10초를 기다리고, 만약 interrupt 등의 방해를 받았다면 예외를 출력한다. 
  • 일반쓰레드가 없으면 데몬쓰레드는 자동종료되므로 무한루프를 탈출하여 gc()를 수행한다.  
  • 가비지 컬렉터는 사용하지않고있는 객체를 제거해주는 역할을 한다. 

 

 

  • 만약 예외가 발생하는 경우, 무한루프를 벗어나 gc()를 수행한다. 

 

 

  • 쓰레드 gc를 깨우는 것뿐만 아니라 join()을 이용해서 쓰레드 gc 가 작업할 시간을 어느 정도 주고
  • main쓰레드가 기다리도록 해서, 사용할 수 있는 메모리 가 확보된 다음에 작업을 계속하는 것이 필요하다.

 

 

 

  • 이때 for문 내에 try~catch 문안에서 join 메서드가 필요하다. 

 

 

 

 

yield()


  • yield()는 쓰레드 자신에게 주어진 실행시간을 다음 차례의 쓰레드에게 양보(yield)한다.
  • 남는 시간을 다음 쓰레드에게 양보하고, 현재 쓰레드는 실행 대기 상태가 되는 메서드이다.  
  • static 메서드이므로 다른 쓰레드에게 양보하도록 하지못하며 자기 자신(현재쓰레드) 에게만 적용된다. 
  • yield 와 interrupt를 적절히 사용하면 응답성과 효율성을 높일 수 있다. 

 

  • 만약 필드값을 suspended값을 true 로 둔다고 가정해보자.
  • 즉 잠시 실행을 멈추게 한 상태라면, 쓰 레드는 주어진 실행시간을 그저 while문을 의미없이 돌면서 낭비하게 될 것이다. 이런 상황을 ‘바쁜 대기상태(busy-waiting)’이라고 한다.
  • 이때는 while 내부에서 무한 루프를 돌되 if문안에선 작업이 수행되지않을것이다. 
  • 이때 하는 일 없이 무한루프만 돌게되는데 이런 현상을 busy-waiting 이라고 한다. 

 

 

  • 해결법은 예외에 현재쓰레드에대한 yield를 호출하는 것이다. 
  • 그러면 남는 시간동안 무한루프에 갇히지 않고 남는 시간을 양보할 수 있게 된다.
  • 즉, 같은 경우에 yield()를 호출해서 남은 실행시간을 while문에서 낭비하지 않고 다른 쓰레드에게 양보(yield)하게 되므로 더 효율적이다.
  • 다만 yield 는 OS 스케줄러에게 통보하는 것이지 반드시 원하는 만큼 시간을 효율적으로 관리 할 수 있게 됨을 의미하지 않음을 알아야 한다.   

 

 

 

 

 

 

 

 

 

 

 

suspend(), resume(), stop()


  • 쓰레드의 실행을 각각 일시정지, 재개, 완전정지 시킨다.
  • suspend()는 sleep()처럼 쓰레드를 멈추게 한다.
  • suspend()에 의해 정지된 쓰레드는 resume()을 호출해야 다시 실행대기 상태가 된다.
  • stop()은 호출되는 즉시 쓰레드가 종 료된다.
  • 단 교착상태 (deadlock)를 일으키기 쉽게 작성되어있으므로 deprecated 되었기에 사용이 권장되지않는다.
  • ‘deprecated’의 의미는 ‘전에는 사용되었지만, 앞으로 사용하지 않을 것을 권장한다.’이다.
  • ‘deprecated’된 메서드는 하위 호환성을 위해서 삭제하지 않는 것일 뿐이므로 사용해서는 안 된다

 

 

  • 다음은 각각의 메서드를 테스트 하는 예제이다.
  • 별을 하나찍는 th1의 suspend()를 호출시 두개, 세개의 별만 찍히는 것을 확인할 수 있다.
  • sleep(2000)은 쓰레드 를 2초가 멈추게 하지만, 2초 후에 바로 실행상태가 아닌 실행대기상태가 된다.

 

 

  • 별을 하나찍는 th2의 suspend()를 호출시 세개의 별만 찍히는 것을 확인할 수 있다.

 

 

  • 별을 하나찍는 th1의  resume()를 호출시켜 재개시, 하나의 별이 다시 찍히기 시작함을 확인할 수 있다.

 

  • 현재 쓰레드의 2초의 sleep을 건뒤 별을 세개 찍는 th3의 stop 메서드 호출시 
  • sleep이 걸리기 전 2초간 th3의 세개의 별이두번 찍힘을 확인할 수 있다. 

 

 

  • 해당 메서드 들이 deprecated 된 상태기 때문에 쓰레드를 조금 변경시켜 MyThread 라는 쓰레드 생성 후 테스트 한다.
  • 단, 여기서 쓰레드의 동작이 모두 끝났어도 서버가 종료되지않는 현상을 볼 수 있는데
  • 이는 쓰레드의 필드 앞에 volatile 을 붙여주면 해결된다. 

 

 

  • volatile 키워드가 붙으면 해당 메서드는 가변적인 상태라는 뜻이다
  • 어떠한 필드, 변수를 생성시 각각 ram에 원본이, cpu에 복사본이 할당되는데 
  • volatile키워드가 붙음은 이 복사본을 사용하지않고 원본만을 참고하겠다는 의미이다.
  • 그럼으로서 값이 true, false로 변경될때마다 원본-복사본이 아니라 원본만을 참고하여 빠른 반영이 가능해진다. 

 

 

 

 

 

 

 

sleep()


  • sleep() 은 쓰레드의 메서드로서 현재 쓰레드를 지정된 시간동안 멈추게 한다.
  • '현재' 쓰레드인 이유는 해당 메서드가 static 이기 때문이다. 
  • 즉 다른, 특정 쓰레드를 멈추게 하는 것은 불가능하며 자기자신만 가능하다. 
  • 그러므로 변수명.sleep() 으로 작성하는게 아니라 클래스명인 Tread.sleep() 으로 작성한다. 
  • 반드시 try~catch 문으로 예외처리를 해주어야 한다. 
  • long mills는 천분의 일초 단위로서 3초를 의도하고싶다면 3000 으로 작성해야 한다. 

 

 

  • 예제는 쓰레드가 총 세개인 상태다. main, th1, th2

 

 

  • 쓰레드 th1과 th2에 대해 start()를 호출하자마자 ‘th1.sleep(2000)’을 호출하여 쓰레드 th1이 2초 동안 작업을 멈추고 일시정지 상태에 있도록 하였으니까 쓰레드 th1이 가장 늦 게 종료되어야 하는데 결과에서는 제일 먼저 종료되었다.
  • 그 이유는 sleep()이 항상 현재 실행 중인 쓰레드에 대해 작동하기 때문이다. static 이기 때문이다.
  • ‘th1.sleep(2000)’과 같이 호출하였어도 실제로 영향을 받는 것은 main메서드를 실행하는 main 쓰레드이다.
  • 그래서 sleep()은 static으로 선언되어 있으며 참조변수를 이용해서 호출하기 보다는 ‘Thread.sleep(2000);’과 같이 해야 한다.

 

 

  • try~catch... 문을 작성해 sleep을 걸어주지 않으면 현재 메서드(<<main종료>>)가 가장먼저 끝나버리는걸 확인할 수 있다.
  • 즉, th1, th2,main 쓰레드가 차례로 종료되어야 맞는데 main이 가장먼저 끝나버린 것이다.

 

 

  • 예외처리를 해주면 다시 main 쓰레드가 가장 마지막에 종료된 것을 확인할 수 있다.

 

 

  • 반드시 예외처리를 해주어야 하는 이유는 sleep 메서드가 InterruptedException으로 예외처리를 하며
  • InterruptedException가 Exception 클래스를 상속하고 있기 때문이다. 

 

 

  • try~catch 처리를 해주지않으면 에러가 발생한다.

 

 

 

 

 

interrupt()


  • 대기상태인 쓰레드를 실행대기 상태로 변경하는 메서드이다. 
  • 여기서 대기상태작업중단을 의미하며 sleep(), join(), wait() 등이 있다. 
  • interrupt() : 쓰레드의 interrupted 상태를 false -> true 로 변경
  • isInterrupted() : 쓰레드의 interrupted 상태 반환
  • interrupted() : 현재 쓰레드의 interrupted 상태를 반환하며, false로 초기화, static
  • 단지 멈추라고 요청 만 하는 것일 뿐 쓰레드를 강제로 종료시키지는 못한다.
  • interrupt()는 그저 쓰레드의 interrupted상태(인스턴스 변수)를 바꾸는 것일 뿐이다 (true > false)
  • interrupted()는 쓰레드에 대해 interrupt()가 호출되었는지 알려준다.
  • interrupt() 가 호출되지 않았다면 false를,interrupt() 가 호출되었다면 true를 반환한다
  • 예를 들면, 브라우저에서 어떤 파일을 다운로드 받다가 로딩이 너무 길어질시 취소버튼을 누르는 예시를 생각해볼 수 있다.
  • 우리가 취소를 누르면, 쓰레드는 이를 interrupt 상태로 받아 들이고, 메서드 내에서 다운로드를 위한 반복문을 빠져나와 쓰레드가 종료되는 것이다. 

  • interrupt() 가 호출되면,interrupted( )의 결과가 false에서 true로 바뀌어 while문을 벗 어나게 된다

 

 

  • 다음 예시는, ThreadEx9_1 이라는 카운트를 세도록하는 별개의 쓰레드를 만든후 
  • 1) 실행시 쓰레드를 start하여 2) 카운트가 도는 중 3) I/O 입출력이 발생하면 4) 쓰레드를 멈추도록 하는 로직이다.
  • 이때 interrupt를 호출하면 interrupted 상태가 true가 된다. 일종의 방해/중단을 받았기 때문이다.

 

 

  • 이때 interrupted를 호출하면 첫번째출력값은 true지만 두번째 출력값은 false 이다.
  • 첫번째는 방해받았음을 나타내지만 두번째는 그사이 해당메서드가 true->false 설정하는 작업을 했기때문이다.
  • 이때 interrupted 객체는 th1.이 아니라 Tread로 작성함에 유의한다.
  • interrupted 는 static 메서드로서 현재 쓰레드만을 설정할 수있다. 즉, 다른 쓰레드에 영향을 미치지 못한다. 

 

 

 

 

 

 

 

 

 

 

 

 

 

데몬 쓰레드(Demon thread)


  • 쓰레드의 종류에는 일반쓰레드와 데몬쓰레드가 있는데 데몬쓰레드는 보조적인 역할을 하는 쓰레드를 말한다.
  • 데몬 쓰레드는 다른 일반 쓰레드(데몬 쓰레드가 아닌 쓰레드)의 작업을 돕는 보조적인 역할을 수행하는 쓰레드이다.
  • 보조적인 역할이다보니 일반쓰레드가 종료되면 자동으로 종료된다.
  • 그 이유는 데몬 쓰레드는 일반 쓰레드의 보조역할을 수행하므로 일반 쓰레드가 모두 종료되고 나면 데몬 쓰레드의 존재의 의미가 없기 때문이다
  • 데몬 쓰레드의 예로는 가비지 컬렉터, 자동저장, 화면 자동갱신등이 다.
  • while 이나 for문등의 무한루프를 이용해서 만든다. 즉, 무한루프와 조건문을 이용해서 실행 후 대기하고 있다가 특정 조건이 만족 되면 작업을 수행하고 다시 대기하도록 작성한다
  • 무한루프를 사용하지만 보조적인 쓰레드이다보니 일반쓰레드가 종료되면 무한히 돌지않고 함께종료된다.

  • 데몬 쓰레드는 일반 쓰레드의 작성방법과 실행방법이 같다.
  • 다만 쓰레드를 생성한 다음, 실행하기 전에 setDaemon(true)호줄하기만 하면 된다.
  • 그리고 데몬 쓰레드가 생성 한 쓰레드는 자동적으로 데몬 쓰레드가 된다는 점도 알아두자

 

 

 

  • isDaemon() : 쓰레드가 데몬 쓰레드인지 확인한다. 데몬쓰레드일시 TRUE 반환
  • setDaemon(boolean on) : 쓰레드를 데몬쓰레드 혹은 일반 쓰레드로 변경, 설정한다. true 일시 데몬쓰레드가 된다.
  • setDaemon은 반드시 start()를 호출하기전에 작성되어야하며 그렇지않을시 에러를 반환한다.

 

 

 

  • Runnable 인터페이스 구현 시 run 메소드가 있는 러너블 인터페이스를 객체생성하여 Thread 에 대입해야 한다.
  • setDaemon() 이 true 이며 start() 보다 앞서있음에 유의한다. 
  • 무한루프가 작성된 데몬쓰레드는 일반쓰레드가 종료되면 함께 종료된다. 
  • 3초마다 변수 autoSave의 값을 확인해서 그 값이 true이면, autoSave()를 호출하는 일 을 무한히 반복하도록 작성된 쓰레드이다. 
  • 만일 이 쓰레드를 데몬 쓰레드로 설정하지 않았다면, 이 프로그램은 강제종료하지 않는 한 영원히 종료되지 않을 것이다

 

 

  • setDaemon이 start() 이전에 작성되지않으면 보조쓰레드가 아닌 일반쓰레드로 간주된다. 
  • 즉, 일반쓰레드는 별개의 쓰레드 이므로 일반쓰레드가 종료된 이후에도 종료되지않고 계속 돌게된다.
  • setDaemon메서드는 반드시 start()를 호출하기 전에 실행되어야한다.
  • 그렇지 않으면 IllegalThreadStateException 이 발생한다. 

 

 

 

쓰레드의 상태


  • 쓰레드 프로그래밍이 어려운 이유는 동기화(synchronization)와 스케줄링(scheduling) 때문이다.
  • 효율적인 멀티쓰레드 프로그램을 만들기 위해서는 보다 정교한 스케줄링을 통해 프로세스에게 주어진 자원과 시간을 여러 쓰레드가 낭비없이 잘 사용하도록 프로그래밍 해야 한다.
  • 쓰레드의 상태는 NEW, RUNNABLE, BLOCKED, WATING, TIMED_WAIRING, TERMINATED 가 있다.
  • 생성(NEW)된 쓰레드는 > START() 가 되어야 실행대기(RUNNABLE) 상태에 놓이고 > 차례로 실행되게 된다. > 이후 실행대기와 실행을 반복하다가 > STOP()과같은 상태가 떨어지면 > 소멸(TERMINATED) 하게 된다. 

 

 

  • 이때 쓰레드를 일종의 일시정지 상태에 놓을 수 있는데 바로 WAITING과 BLOCKED 이다.
  • 일시정지(suspend), 잠자기(sleep), 기다리기(wait, join), 입출력 대기(I/O block)등이 있다.
  • TIMED_WAITING은 시간이 미리 정해진 일시정지이다.

 

 

  • 일시정지와 대비되는 것은 재개(time-out, resume, notify)와 깨우기(interrupt) 등이다.

 

더보기

 

① 쓰레드를 생성하고 start()를 호출하면 바로 실행되는 것이 아니라 실행대기열에 저장되어 자신의 차례가 될 때까지 기다려야 한다. 실행대기열은 큐 (queue)와 같은 구조로 먼저 실행대기 열에 들어온 쓰레드가 먼저 실행된다.

 

② 실행대기상태에 있다가 자신의 차례가 되면 실행상태가 된다.

 

③ 주어진 실행시간이 다되거나 yield()를 만나면 다시 실행대기상태가 되고 다음 차례의 쓰레드가 실행상태가 된다.

 

④ 실행 중에 suspend(), sleep(), wait(), join(), I/O block에 의해 일시정지상태가 될 수 있다. I/O block은 입출력작업에서 발생하는 지연상태를 말한다. 사용자의 입력을 기다리는 경우를 예로 들 수 있는데,이런 경우 일시정지 상태에 있다가 사용자가 입력을 마치면 다시 실행대기 상태가 된다.

 

⑤ 지정된 일시정지시간이 다되거나(time-out), notify(), resume(), in te rrup t)가 호출되면 일시 정지상태를 벗어나 다시 실행대기열에 저장되어 자신의 차례를 기다리게 된다.

 

⑥ 실행을 모두 마치거나 stop( )이 호출되면 쓰레드는 소멸된다.

 

 

 

 

 

 

 

 

 

 

쓰레드의 실행제어 메서드 종류


  • 유일하게 static 메서드인 sleep() 과 yield() : 쓰레드 자기 자신에게만 호출이 가능하다.
  • 이는 자기 자신에게만 호출이 가능함을 의미한다.
  • 즉, 자거나 양보하는건 자기 스스로에게만 적용되는 것
  • 쓰레드의 스케줄링을 잘하기 위해서는 쓰레드의 상태와 관련 메서드를 잘 알아야 한다. 

 

 

sleep(long millis)


  • 일정시간동안 쓰레드를 엄추게 한다.
  • sleep()에 의해 일시정지 상태가 된 쓰레드는 지정된 시간이 다 되거나 interrupt()가 호출되면(InterruptedException발생), 잠에서 깨어나 실행대기 상태가 된다.
  • 그래서 sleepO을 호출할 때는 항상 try-catch문으로 예외를 처리해줘야 한다. 
  • static 메서드로 현재 쓰레드에서만 동작한다.

 

interrupt()와 interrupted() 


  • 쓰레드의 작업을 취소한다.
  • 진행 중인 쓰레드의 작업이 끝나기 전에 취소시켜야할 때 사용한다.
  • interrupt()는 쓰레드에게 작업을 멈추라고 요청한다.
  • 단지 멈추라고 요청 만 하는 것일 뿐 쓰레드를 강제로 종료시키지는 못한다.
  • interrupt()는 그저 쓰레드의 interrupted상태(인스턴스 변수)를 바꾸는 것일 뿐이다.

 

suspend( ), resume( ), stop ()


  • suspend()는 sleep()처럼 쓰레드를 멈추게 한다.
  • suspend()에 의해 정지된 쓰레드는 resume()을 호출해야 다시 실행대기 상태가 된다.
  • stop()은 호출되는 즉시 쓰레드가 종 료된다.
  • 이 메서드들은 교착상태 (deadlock)를 일으키기 쉽게 작성되어있으므로 사용이 권장되지 않는다.
  • 그래서 이 메서드들은 모두 ‘deprecated’되었다. (권장이지 강제는 아님)

 

yield()


  • 다른 쓰레드에게 양보한다.
  • yield()는 쓰레드 자신에게 주어진 실행시간을 다음 차례의 쓰레드에게 양보(yield)한다.
  • static 메서드로 현재 쓰레드에서만 동작한다.

 

join()


  • 다른 쓰레드의 작업을 기다린다.
  • 쓰레드 자신이 하던 작업을 잠시 멈추고 다른 쓰레드가 지정된 시간동안 작업을 수행하도 록할 때 join()을 사용한다
  • 시간을 지정하지 않으면, 해당 쓰레드가 작업을 모두 마칠 때까지 기다리게 된다.
  • join()도 sleep()처럼 interrupt()에 의해 대기상태에서 벗어날 수 있으며, join()이 호출 되는 부분을 try-catch문으로 감싸야 한다.
  • join( )은 여러모로 sleep( )과 유사한 점이 많다.
  • sleep()과 다른 점은 join()은 현재 쓰레드가 아닌 특정 쓰레드에 대해 동작하므로 static메서드가 아니라는 것이다.
  • * sleep 과 yield은 static 메서드로 현재 쓰레드에서만 동작한다.

 

 

 

 

 

* 참고하면 좋은 글

- https://velog.io/@yummygyudon/JAVA-쓰레드-Thread-7luq8ydu

 

[JAVA] 쓰레드 ( Thread ) ④

🏃‍♂️ 들어가기 앞서.. > 본 게시물은 스터디 활동 중에 작성한 게시물로 자바의 정석-기초편 교재를 학습하여 정리하는 글입니다. ※ 스터디 Page : 〔투 비 마스터 : 자바〕 **해당 교재의

velog.io

 

 

 

 

 

쓰레드의 우선순위


  • 쓰레드는 우선순위(priority)라는 속성(멤버변수)을 가지고 있는데, 이 우선순위의 값에 따라 쓰레드가 얻는 실행시간이 달라진다.
  • 쓰레드가 수행하는 작업의 중요도에 따라 쓰레드의 우선순위를 서로 다르게 지정하여 특정 쓰레드가 더 많은 작업시간을 갖도록 할 수 있다.
  • 쓰레드가 가질 수 있는 우선순위의 범위는 1〜 10이며 숫자가 높을수록 우선순위가 높다
  • setPriority() : 쓰레드의 우선순위를 지정한 값으로 임의로 설정한다.
  • getPriority() : 알고자하는 쓰레드의 우선순위를 값으로 가져온다.
void setPriority (int newPriority) 	// 쓰레드의우선순위를 지정한값으로 변경한다. 
int get Priority() 		// 쓰레드의 우선순위를 반환한다 •

 

  • 우선순위가 설정되지않은 쓰레드는 기본적으로 5의 우선순위를 갖는다. 
  • 쓰레드의 우선순위는 쓰레드를 생성한 쓰레드로부터 상속받는다. main메서드를 수행하는 쓰레드는 우선순위가 5이므로 main메서드 내에서 생성하는 쓰레드의 우선순위는 자동적으로 5가 된다.
  • 다만 이는 자바 가상 머신(JVM) 에 의한 우선순위 '희망' 일뿐이지 반드시 우선순위를 갖는 건 아니라는 점을 기억해야 한다. 
  • OS 스케줄러가 참고하는, 그저 확인할 수 있는 정도의 우선순위일뿐 실제적인 우선순위는 그때그때 달라진다. 
  • 그저 쓰레드에 높은 우선순위를 주면 더 많은 실행시간과 실행기회를 갖게 될 것이라고 기대하는 것이다.
  • 차라리 쓰레드에 우선순위를 부여하는 대신 작업에 우선순위를 두어 PriorityQueue에 저장해 놓고, 우선순위가 높은 작업이 먼저 처리되도록 하는 것이 나을 수 있다
  • A의 우선순위가 높은 경우 바의 길이와 순서를 살펴보면 좀더 길고, 앞서며 A의 작업이 더 먼저 끝난다. 

 

 

  • 우선순위를 설정하지않은 쓰레드 th1의 우선순위는 기본적으로 5로 설정되어있다.
  • th1에비해 th2의 우선순위가 높음에도 콘솔창을 보면 th2가 더 늦게 끝난다.
  • 앞서 본 것처럼 우선순위가 희망사항일뿐 항상 반영되는 것은 아니란 점을 확인할 수 있다.

 

 

  • 정말 우선순위를 설정하고싶다면 작업관리자에서 직접 설정해줄 수 있다. 
  • ctrl+alt+Del 작업관리자 실행 후 > 세부정보 탭 클릭 > 마우스 우클릭 > 우선순위 설정

 

 

 

 

 

쓰레드 그룹 


  • 쓰레드 그룹은 서로 관련된 쓰레드를 그룹으로 다루기 위한 것으로, 폴더를 생성해서 관련된 파일들을 함께 넣어서 관리하는 것처럼 쓰레드 그룹을 생성해서 쓰레드를 그룹으로 묶어서 관리할 수 있다.
  • 모든 쓰레드는 반드시 쓰레드 그룹에 포함되어 있어야 한다.
  • 쓰레드 그룹을 지정하는 생성자를 사용하지 않은 쓰레드는 기본적으로 자신을 생성한 쓰레드와 같은 쓰레드 그룹에 속하게 된다
  • 그동안 그룹을 설정하지않고 쓰레드를 사용한 것은 main 쓰레드 그룹에 속해있었기 때문이다. 
  • 우리가 생성하는 모든 쓰레드 그룹은 main쓰레드 그룹의 하위 쓰레드 그룹이 되며, 쓰레드 그룹을 지정하지 않고 생성한 쓰레드는 자동적으로 main쓰레드 그룹에 속하게 된 다.
  • 쓰레드는 자신을 생성한 쓰레드의 그룹과 우선순위를 상속받는다. 
  • getThreadGroup() : 속해있는 쓰레드 그룹을 반환하여 알아보고자 할때 사용

 

 

 

 

 

쓰레드 그룹의 메서드 


 

 

 

 

 

 

 

 

 

 

+ Recent posts