Lecture on Java Concurrency Day 4 on Feb 18, 2009. (in Korean)
Lectures are 4 days in all.
See http://javadom.blogspot.com/2011/06/lecture-on-java-concurrency-day-4.html
2. Day 4 (9am, Feb 18, 2009) Thread Pool Lock and Condition CAS (Compare And Set)
3.
4.
5.
6.
7.
8.
9.
Editor's Notes
쓰레드 풀은 서블릿 서버 , EJB 서버 등의 구현에 종종 사용되는 구조이다 . 주인 / 일꾼 모델 (boss/worker model, master/slave model) 에서 많이 사용되는데 이 형태의 쓰레드 모델에서 주인 쓰레드는 쓰레드 풀에서 쉬고 있는 일꾼 쓰레드를 골라 작업을 넘겨준다 . 쓰레드 풀 역시 Executors 클래스의 팩토리 메쏘드를 사용하여 다음과 같이 Executor 처럼 사용할 수 있다 . ExecutorService executor = Executors.newCachedThreadPool(); 지원하는 쓰레드 풀은 재사용되고 필요할 경우 생성하는 방식의 캐시 쓰레드 풀 , 고정 크기 쓰레드 풀 , 쓰레드 실행을 스케줄할 수 있는 스케줄링 쓰레드 풀 등이 있다 . ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) FixedThreadPool : unbounded LinkedBlockingQueue SingleThreadExecutor : unbounded LinkedBlockingQueue CachedThreadPool : SynchronousQueue
Queue holds tasks for thread pool a.Unbounded LinkedBlockingQueue -. Tasks are unbounded -. Even if thread pool size get larger than core size, but the task never get saturated since the task queue is unbounded. -. So, the thread pool doesn ’ t(need not) enlarge its pool size above core size (so, max size is not meaningful in this case) b.Bounded BlockingQueues -. Tasks are bounded -. If the thread pool size get larger than core size, and the task get saturated then the thread pool will grow (up to the max pool size) -. Large queue size with small (max) pool size : saves CPU usage, OS resources, context switch overhead but may lead to low thruput -. Small queue size with large (max) pool size : make CPU busier, but can encounter scheduling overhead (which can also lead to low thruput) c. SynchronousQueue -. CachedThreadPool uses this -. Not a real queue -. A mechanism for managing handoff between threads -. When task arrives, if no thread is waiting and the pool size is less than max pool size, then the pool grows. (create a new thread) otherwise, the task is rejected according to the saturation policy. -. SynchronousQueue impl of Java 6 is three times faster than that of Java 5
Lock 인터페이스는 자바의 동기화 블럭 표현 방식인 synchronized 블럭 대신에 명시적인 잠금 획득과 잠금 해제를 사용하는 방식이다 . C/C++ 에서 제공하는 잠금 방식과 유사한 방식으로 코드 차원에서 잠금을 반드시 풀어줘야 한다는 제약이 있지만 블럭 구조가 아닌 방식 , 즉 필요에 따라 잠금 획득과 잠금 해제가 완전히 다른 메쏘드나 블럭에서 처리될 수도 있으며 , 잠금을 획득할 때 tryLock() 을 사용할 수 있다는 장점이 있다 . Condition 인터페이스는 자바의 동기화 이벤트 표현 방식인 wait(), notify() 메쏘드에 대응하는 개념으로 wait(), notify() 메쏘드들이 대응하는 synchronized 블럭 안에서 동작하듯이 Condition 객체는 대응하는 Lock 객체를 통해 동작한다 . Condition 객체를 사용할 때의 장점으로는 하나의 Lock 객체에서 여러 개의 Condition 객체를 사용할 수 있다는 점을 들 수 있다 . class BoundedBuffer { final Lock lock = new ReentrantLock(); final Condition notFull = lock.newCondition(); final Condition notEmpty = lock.newCondition(); final Object[] items = new Object[100]; int putptr, takeptr, count; public void put(Object x) throws InterruptedException { lock.lock(); try { while (count == items.length) notFull.await(); items[putptr] = x; if (++putptr == items.length) putptr = 0; ++count; notEmpty.signal(); } finally { lock.unlock(); } } public Object take() throws InterruptedException { lock.lock(); try { while (count == 0) notEmpty.await(); Object x = items[takeptr]; if (++takeptr == items.length) takeptr = 0; --count; notFull.signal(); return x; } finally { lock.unlock(); } } }
Class AtomicInteger int addAndGet(int delta) Atomically adds the given value to the current value. boolean compareAndSet(int expect, int update) Atomically sets the value to the given updated value if the current value the expected value. int decrementAndGet() Atomically decrements by one the current value. double doubleValue() float floatValue() int get() Gets the current value. int getAndAdd(int delta) Atomically adds the given value to the current value. int getAndDecrement() Atomically decrements by one the current value. int getAndIncrement() Atomically increments by one the current value. int getAndSet(int newValue) Atomically sets to the given value and returns the old value. int incrementAndGet() Atomically increments by one the current value. int intValue() void lazySet(int newValue) Eventually sets to the given value. long longValue() void set(int newValue) Sets to the given value. java.lang.String toString() Returns the String representation of the current value. boolean weakCompareAndSet(int expect, int update) Atomically sets the value to the given updated value if the current value the expected value.
Answer 1 : core size 0, max size INFINITE is the setting of CachedThreadPool which uses SynchronousQueue as its queue implementation which is in reality no queue. if the setting has queue of unbounded, then no queue saturation will occur, so the thread pool will never increase its size (so, the thread pool size will always be zero, and just the queue grows infinitely..) // no thread will be executed ThreadPoolExecutor executor = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>()); executor.execute(new Runnable() { public void run() { System.out.println(&quot;do INFINITE&quot;); } }); Answer 2 : Executor.execute 가 호출될 때마다 다음에 의해 결정 1. 현재 쓰레드 개수가 core 크기보다 작을 경우 새 쓰레드를 만들어 실행 2. 현재 쓰레드 개수가 core 크기보다 크고 max 크기보다 작을 경우 2.1 queue 가 full 이면 새 쓰레드를 만들어 실행 ( 이때 core size 보다 커짐 ) 2.2 queue 가 full 이 아니면 그냥 queue 에 넣어둠 따라서 fixed thread pool 인 경우 core 크기와 max 크기가 같으므로 , queue full 이 나면 saturation policy 에 의해 결정하면 됨 . 1 번 문항처럼 core size 가 0 이고 queue 크기가 무한대이면 thread pool 크기를 늘릴 일이 없게 됨