본문 바로가기

# 02/Java

[윤성우 열혈자바] 34-3. 쓰레드를 생성하는 더 좋은 방법

반응형

쓰레드  모델



쓰레드의 생성과 소멸은 리소스 소모가 많은 작업이다.

그런데 쓰레드 풀은 쓰레드의 재활용을 위한 모델이다.








쓰레드  기반의 예제 1

class ExecutorsDemo {

   public static void main(String[] args) {

      Runnable task = () -> {    // 쓰레드에게 시킬 작업 - Runnable 구현한 인스턴스 생성

         int n1 = 10;

         int n2 = 20;

         String name = Thread.currentThread().getName();

         System.out.println(name + ": " + (n1 + n2));

      };


      ExecutorService exr = Executors.newSingleThreadExecutor();

      exr.submit(task);    // 쓰레드 풀에 작업을 전달

     

      System.out.println("End " + Thread.currentThread().getName());

      exr.shutdown();    // 쓰레드 풀과  안에 있는 쓰레드의 소멸

 // 예전 모델에서는 쓰레드가 종료되면 자동으로 소멸됬는데, 쓰레드 풀은 계속 대기중임.

 // 쓰레드 소멸했다고 해서 바로 소멸되는게 아니라 할당된 쓰레드가 모두 종료되면 그 때 소멸됨!

   }

}








쓰레드 풀의 유형

• newSingleThreadExecutor

   안에 하나의 쓰레드만 생성하고 유지한다.

 

• newFixedThreadPool(3) - 예

   안에 인자로 전달된 수의 쓰레드를 생성하고 유지한다.


• newCachedThreadPool - 실험적이여서 확실히 좋다고는 말못함...

   안의 쓰레드의 수를 작업의 수에 맞게 유동적으로 관리한다.






쓰레드  기반의 예제 2

public static void main(String[] args) {

   Runnable task1 = () -> {

      String name = Thread.currentThread().getName();

      System.out.println(name + ": " + (5 + 7));

   };

   Runnable task2 = () -> {

      String name = Thread.currentThread().getName();

      System.out.println(name + ": " + (7 - 5));

   };

   

   ExecutorService exr = Executors.newFixedThreadPool(2);

   exr.submit(task1);

   exr.submit(task2);

   exr.submit(() -> {

      String name = Thread.currentThread().getName();

      System.out.println(name + ": " + (5 * 7));

   });

     

   exr.shutdown();

}








Callable & Future

public interface Runnable {

   void run();

};

@FunctionalInterface

public interface Callable<V> {

    V call() throws Exception;

}

둘의 가장 큰 차이점은 반환형의 유무이다. 
Runnalble 메소드는 반환형이 없고, Callable 메소드는 반환형을 지정할 수 있다!!







Callable & Future 관련  

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

   Callable<Integer> task = () -> {

      int sum = 0;

      for(int i = 0; i < 10; i++)

         sum += i;

      return sum;

   };

   

   ExecutorService exr = Executors.newSingleThreadExecutor();

   Future<Integer> fur = exr.submit(task);

   

   Integer r = fur.get();             // 쓰레드의 반환  획득 - 쓰레드가 완료된후 반환된 값이 저장됨!

   System.out.println("result: " + r);

   exr.shutdown();

}


1~20까지 더하고 싶으면 1~10까지 더하는 쓰레드와 11~20까지 더하는 쓰레드의 반환값을 get해서 더해주면 동시에 실행가능!








synchronized 대신하는 ReentrantLock


class MyClass {

   ReentrantLock criticObj = new ReentrantLock();            // 자물쇠

   void myMethod(int arg) {

      criticObj.lock() ;    // 문을 잠근다.

      .... //  쓰레드에 의해서만 실행되는 영역

      criticObj.unlock();    // 문을 연다.

   }

}



class MyClass {

   ReentrantLock criticObj = new ReentrantLock();

   void myMethod(int arg) {

      criticObj.lock() ;    // 문을 잠근다.

      try {

         ....    //  쓰레드에 의해서만 실행되는 영역

      } finally {

         criticObj.unlock();    // 문을 연다.

      }     unlock 호출이 생략되는 것을 막기 위해 try-finally 권고됨!

   }

}








컬렉션 인스턴스 동기화


public static <T> Set<T> synchronizedSet(Set<T> s)

public static <T> List<T> synchronizedList(List<T> list)

public static <K, V> Map<K, V> synchronizedMap(Map<K, V> m)

public static <T> Collection<T> synchronizedCollection(Collection<T> c)


List<String> lst = Collections.synchronizedList(new ArrayList<String>());








컬렉션 인스턴스 동기화의 


Runnable task = () -> {

   synchronized(lst) {

      ListIterator<Integer> itr = lst.listIterator();

      while(itr.hasNext())

         itr.set(itr.next() + 1);

   }

};



class SyncArrayList {

   public static List<Integer> lst =

      Collections.synchronizedList(new ArrayList<Integer>());

   

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

      for(int i = 0; i < 16; i++)

         lst.add(i);

      System.out.println(lst);

   

      Runnable task = () -> {

         ListIterator<Integer> itr = lst.listIterator();

         while(itr.hasNext())

            itr.set(itr.next() + 1);

      };

     

      ExecutorService exr = Executors.newFixedThreadPool(3);

      exr.submit(task);

      exr.submit(task);

      exr.submit(task);

     

      exr.shutdown();

      exr.awaitTermination(100, TimeUnit.SECONDS);    // 쓰레드가 종료될때까지 최대 100초 기다리겠다는 뜻임.

      System.out.println(lst);

   }

}



ArrayList의 원소들을 하나씩 꺼내서 1씩 증가키는 쓰레드를 3개를 실행했는데 제대로 동작하지 않았다.

이유는, ArrayList 인스턴스에는 동기화를 해줘서 ArrayList를 통해 접근하면 동기화가 되겠지만

쓰레드는 반복자를 통해 접근하고 있다.

반복자도 하나의 인스턴스로 반복자를 통한 인스턴스는 동기화 영향을 받지 않는다.

따라서 코드를 위의 빨간색 네모칸처럼 고쳐줘야 한다!!



반응형