kiwi

자바 동기화 클래스

by 키위먹고싶다

멀티 스레드 환경에서 하나의 프로세스는 다수의 스레드에 의해 실행된다.  스레드가 사용하는 독립적인 자원을 제외하고 스레드끼리 공유하는 자원 [ 스택 영역 제외 ] 을 사용하려면 동기화 작업이 반드시 필요하다. 

 

동기화

- 스레드들에게 하나의 자원에 대한 처리권한을 주거나 순서를 조정해주는 기법.

 

자바는 스레드의 동기화를 위해 concurrent package에 3개의 동기화 클래스를 제공한다.

 

public class ConCurrency {

    public static void main(String[] args) {

        Thread thread = new Thread(() -> {

            System.out.println("Thread Start");

            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        });

        thread.start();

        try {
            thread.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("Thread Finish");
    }
}

join()을 사용해서 메인스레드를 대기시킨다. 메인스레드와 동기화 시킬 수 있지만 스레드 여러개를 동시에 시작하게 할 순 없다. 

 

1. java.util.concurrent.Semaphore

세마포어는 permits size를 제공하여 acquire() 메서드로 permits 허용시키고 release()로 permits 반환한다.

public class ConCurrency {

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

        AtomicInteger atomicInteger = new AtomicInteger(0); //초기값 0
        Semaphore semaphore = new Semaphore(10);

        for (int i=0; i<10; i++) {
            semaphore.acquire();
            atomicInteger.getAndIncrement();
        }

        semaphore.acquire();
        System.out.println(atomicInteger.get());
    }
}

11번째 acquire() 를 호출할 경우 대기 상태에 빠지므로 11이 출력되지 않는다.

 

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;

public class ConCurrency {

    public static void main(String[] args) {

        Semaphore semaphore = new Semaphore(10);
        ExecutorService service = Executors.newFixedThreadPool(5);

        for (int i=0; i<20; i++) {
            int n = i;
            service.execute(() -> {
                try {
                    semaphore.acquire();
                    System.out.println(n);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }

        System.out.println("finish");

//        finish
//        4
//        3
//        0
//        2
//        1
//        8
//        7
//        6
//        5
//        9
    }
}

메인스레드 제어도 못하고 세마포어 크기가 10이므로 20번 acquire()할 경우 대기상태에 빠진다. 

 

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;

public class ConCurrency {

    public static void main(String[] args) {

        Semaphore semaphore = new Semaphore(10);
        ExecutorService service = Executors.newFixedThreadPool(5);

        for (int i=0; i<20; i++) {
            int n = i;
            service.execute(() -> {
                try {
                    semaphore.acquire();
                    System.out.println(n);
                    semaphore.release();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }

        System.out.println("finish");

//        0
//        1
//        finish
//        3
//        2
//        6
//        5
//        9
//        11
//        12
//        8
//        7
//        15
//        16
//        17
//        18
//        4
//        19
//        14
//        13
//        10

    }
}

release()로 permits()을 줄여서 20번 실행할 수 있지만 메인스레드 제어가 안된다.

 

2. java.util.concurrent.CountDownLatch

하나 이상의 스레드가 다른 스레드에서 수행중인 일련의 작업이 완료될때까지 대기 할 수 있다.

 

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ConCurrency {

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

        CountDownLatch latch = new CountDownLatch(10);
        ExecutorService service = Executors.newFixedThreadPool(5);

        for (int i=0; i<5; i++) {
            int n = i;
            service.execute(() -> {
                latch.countDown();
                System.out.println(n);
            });
        }

        latch.await();
        System.out.println("finish");
        
//        결과
//        0
//        4
//        3
//        1
//        2
    }
}

countDown()이 10번 실행되지 않으면 메인쓰레드에서 await() 해도 finish가 찍히지 않는다.  

 

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ConCurrency {

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

        CountDownLatch latch = new CountDownLatch(10);
        ExecutorService service = Executors.newFixedThreadPool(5);

        for (int i=0; i<10; i++) {
            int n = i;
            service.execute(() -> {
                latch.countDown();
                System.out.println(n);
            });
        }

        latch.await();
        System.out.println("finish");

//        결과
//        1
//        3
//        0
//        7
//        8
//        2
//        9
//        6
//        5
//        4
//        finish


    }
}

10번 countDown() 실행되면 await()으로 중단됐던 코드가 다시 진행되므로 finish 출력된다.  

 

3. java.util.concurrent.CyclicBarrier

public class ConCurrency {

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

        CyclicBarrier cyclicBarrier = new CyclicBarrier(10);
        ExecutorService service = Executors.newFixedThreadPool(5);

        for (int i=0; i<5; i++) {
            int n = i;
            service.execute(() -> {
                try {
                    cyclicBarrier.await();
                    System.out.println(n);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            });
        }
        
        System.out.println("finish");
        
//        결과
//        finish
        
    }
}

CountDownLatch는 카운트가 소모되기전에 await()을 호출할 경우 해당 스레드를 제외한 나머지 스레드들은 여전히 실행되지만 CyclicBarrier는 어느 스레드가 await()을 만나도 이후에 실행될 모든 스레드들도 멈춘다.

 

 

'java' 카테고리의 다른 글

인터프리터 VS JIT 타이밍  (0) 2023.04.30
[java] concurrent package 정리  (0) 2022.08.19
자바의 메모리 구조  (0) 2022.04.08
가비지컬렉터(Garbage Collector)  (0) 2022.02.05
JVM 클래스로더  (0) 2022.01.22

블로그의 정보

kiwi

키위먹고싶다

활동하기