an object state is it's data
an object's state encompasses any data that can affect its externally visible behavior
객체의 상태는 객체의 데이터이다
객체의 상태는 외부의 객체들이 볼수 있고 또한 그 객체에 영향을 줄 수있는 모든 데이터
what is shared?
a variable could be accessed by multiple threads;
여러 쓰레드에서 접근 가능한 변수들
what is mutable
it's value could change during its life time
life time 동안 변경 될수 있는 변수의 값
what is thread safe?
protect data from uncontrolled concurrent access
데이터를 조절 되지 않은 동시 접속에서 안전하게 보호하는것
rule for thread safe
Don't share the state variable across thread
Make the state variable immutable or
use synchronization whenever accessing the state variable
쓰레드들 끼리 상태 변수를 공유하지 말라
상태 변수를 불변하게 만들거나
상태 변수에 접근 할 때는 언제나 synchronization을 사용해라
designing thread-safe classes, good object-oriented techniques - encapsulation, and clear specification of invariants - are your best friend
where the rule is odd remember "first to make your code right,
and then make if fast"
규칙이 잘 맞지 않을때는 일단 잘돌아가게 만들고 빠르게 만들어라
(동시성 버그는 찾기 힘들고 재현하기 힘들기 때문에 작은량의 퍼포먼스 차이라면 포기하는것도 좋다)
2.1 What is Thread safety?
a class is thread-safe when it continues to behave correctly when accessed from multiple threads
쓰레드 세이프티란 여러개의 쓰레드에서 접근해도 정확하게(의도한대로) 지속적으로 동작하는걸 말한다.
a class is thread-safe if it behave correctly when accessed from multiple threads, regardless of the scheduling or interleaving of the execution of those threads by the runtime environment, and with no additional synchronization or other coordination on the part of the calling code
쓰레드 세이프리란 여러개의 멀티 쓰레드에서 접근할때 정확하게 동작하는걸 말한다.
런타임 상테에서 쓰레드의 스케쥴 또는 interleaving 되면서 실행되는거에 상관없어햔다
또한 호출하는 코드에서 동기화나 다른 조정을 할필요가 없어야 한다.
thread safe class encapsulate any needed synchronization so that clients need not provide their own.
쓰레드 세이프 클래스는 모든 동기화를 encapsulate 해야한다 그러므로 클라이언트가 동기화를 위해 다른 코드를 필요로 하면 안된다.
2.1.1 example:A Stateless Servlet
@TreadSafe public class StatelessFactorizer implements Servlet{ public void service(ServletRequest req, ServletResponse resp) { BigInteger i = extracFromRequest(req); BigInteger[] factors = factor(i); encodeIntoResponse(resp, factors); } }
Stateless objects are always thread-safe
저장 하는 상태가 없으면 쓰레드에 대한 걱정을 하지 않아도 된다.
2.2 Atomicity
read-modify-operation
1.fetch the current value
2.add one to it
3.write the new value back
for example ++count
bad code
@NotThreadSafe public class UnsafeCountingFactorizer implements Servlet { private long count = 0; public long getCount() { return count; } public void service(ServletRequest req, ServletResponse resp) { BigInteger i = extractFromRequest(req); BigInteger[] factors = factor(i); ++count; encodeIntoResponse(resp, factors); } }
if the counter is initially 9, with some unlucky timing each thread could read the value
, see that is it 9, add one to it, and each set the counter to 10
9였다가 어떤 나쁜 순간에 쓰레드 두개가 동시에 9인걸 읽는다
그리고 1을 더해서 돌려 놓는다.
위와 같은 예제를 race condition 이라고 한다.
2.2.1 Race Conditions
A race condition occurs when the correctness of a computation depends on the relative timing or interleaving of multiple threads by the run times;
in other words, when getting the right answer relies on lucky timing
the most common type of race condition is check-then-act, where a potentially stale
observation is used to make a decision on what to do next
레이스 컨티션은 정확한 계산이 런타임에서 멀티플 쓰레드의 타이밍과 인터리빙에
의존해서 이루어 지는걸 말한다
다른말로는 정확한 답을 얻기 위해서는 운좋은 타이밍에 의존한다는것이다.
가장 일반적인 레이스 컨디션은 check-then-act 이다
신선하지 않은 정보(최신 데이터가 아닌)를 보고 다음에 무엇을할지 결정하는것이다.
for example race condition
you observe something to be true(file x doesn't exist) and then take action based on
that observation(create x); but in fact the observation could have become invalid
between the time you observed it and the time you act is(someone else created X in
the meantime), causing a problem(unexpected exception..)
2.2.2Example: Race Condition in lazy initialization
badCode
@NotThreadSafe public class LazyInitRace { private ExpensiveObject instance = null; public ExpensiveObject getInstance() { if (instance == null) instance = new ExpensiveObject(); return instance; } }
Say that thread A and B execute getInstance at the same time.
A see that instance is null, and instantiates a new object. B also checks if instance is
null. whether instance is null at this point depends unpredictably on timing, including the
vagaries of scheduling and how long A takes to instantiate the ExpensiveObject and set
the instance field. if instance is null when B examines it, the two callers to getInstance
may receive two different results, even though getInstance is always supposed to return
the same instance.
A 그리고 B가 동시에 getInstance 를 호출 하면 null 이 나올수 있다
이때 A가만들고 B가 만들면 언제나 하나의 인스턴스만 호출되기를 바랬지만
서로 다른 인스턴스를 돌려 줄수있다
만약 등록하는 객체 같은걸 리턴하는 오브젝트면 다른 오브젝트 등록을 할 수 있다
2.2.3 Compound Actions
Operation A and B are atomic with respect each other if, from the perspective of a thread executing A, when another tread execute B, either all of B has executed or none of it has.
an atomic operation is one that is atomic with respect to all operation, including itself, that operation on the same state
A, B operation 이 원자적이라는 것은 서로 존중 하는걸 의미한다.
B 가 실행되고 있을때 A 를 실행 시킨다면 B 는 전부 실행 되거나
아예 실행되지 않아야 한다. 원자적 operation 이란 하나의 상태에 대해서 자신을 포함한 모든operations 가 존중 받는걸 의미한다.(서로 침범하지 아니함)
what is compound actions?
we refer collectively to check-then-act and read-modify-write sequences as compound actions;
@ThreadSafe public class CountingFactorizer implements Servlet { private final AtomicLong count = new AtomicLong(0); public long getCount() { return count.get(); } public void service(ServletRequest req, ServletResponse resp) { BigInteger i = extractFromRequest(req); BigInteger[] factors = factor(i); count.incrementAndGet(); encodeIntoResponse(resp, factors); } }
where practical, use existing thread-safe objects, like AtomicLong, to mange your class's state. it it simpler to reason about the possible states and state transitions for existing thread-safe objects than it it for arbitrary state variables, and this make it easier to maintain and verify thread safety
실전에서는 제공되는 쓰레드 세이프 오브젝트를 쓰는게 유리하다 왜냐하면 가능한 상태값과
상태의 이행 과정을 알수 있기 때문이다 직적 만들면 제 멋대로인 상태 변수들을 관리해야 한다. 즉 이미 존재한걸 쓰는게 쓰레드 세이프를 검증하기도 쉽고 유지하기도 쉽다.
2.3 Locking
아까 서블렛에 캐쉬를 달자
연속되게 같은 값이 들어온다면 캐쉬된 인수 분해 값을 리턴해주는것
(물론 이방법이 비록 효과적이지 않지만)
badCode
@NotThreadSafe public class UnsafeCachingFactorizer implements Servlet { private final AtomicReferencelastNumber = new AtomicReference (); private final AtomicReference lastFactors = new AtomicReference (); public void service(ServletRequest req, ServletResponse resp) { BigInteger i = extractFromRequest(req); if (i.equals(lastNumber.get())) encodeIntoResponse(resp, lastFactors.get() ); else { BigInteger[] factors = factor(i); lastNumber.set(i); lastFactors.set(factors); encodeIntoResponse(resp, factors);  } } }
the definition of thread safety requires that invariants be preserved of timing or interleaving of operation in multiple thread
쓰레드 정의를 보면 멀티쓰레드 환경에서 타이밍과 잠시멈춤에도 불변해야 한다
잠시 멈춤을 기억하자!
위코드는 레스트 팩터를 바꿔는대 그때 다른쓰레드가 레스트 팩터를 실어 보내면 오류가 발
생한다.
To preserve state consistency, update related stated variables in a single atomic operation.
상태의 정합성을 유지하기 위해서는 서로 관련이 있는 상태 변수들을 한번의 원자적 operation 으로 처리해야 한다.
2.3.1 Intrinsic Locks
synchronized block has two pars: a reference to an object that will serve as the lock
and a block of code to be guard by that lock;
synchronized block 은 두개의 파트로 구성되어 있다 1. 락자체의 역활을할 오브젝트
그리고 그락이 보호하는코드 블락
synchronized (lock) { // Access or modify shared state guarded by lock }1
이락을 intrinsic lock 또는 monitor lock 이라고 부른다.
the only way to acquire an intrinsic lock is to enter a synchronized block or method guard by that lock
내장락을 얻기 위해서는 해당 블락 또는 그락이 막고 있는 메서드에 들어가는 방법밖에 없다.
intrinsic lock act as mutax(or mutual exclusion locks)
2.3.2 Reentrancy
intrinsic locks are reentrant , if a thread tries to acquire lock that it already hols, the request succeeds
내장 락은 재입장이 가능하다 만약 락을 가진 스레드가 다리 그락을 획득 할려고 하면 그
요청은 성공한다.
reentrancy mean that locks are acquired on a per-thread rather than per-invocation
재입장은 락의 획득이 invocation 이 아니라 쓰레드당 관리 될다는걸 알수 있다
구현은 JVM 이 쓰레드가 락을 가지면 owner 와 카운터를 올린다. 재요청 하면 하나를 또 올린다. 락을 나가면 카운터를 내린다. 카운터가 0이 되면 락이 풀린다.
public class Widget { public synchronized void doSomething() { } } public class LoggingWidget extends Widget { public synchronized void doSomething() { System.out.println(toString() + ": calling doSomething"); super.doSomething(); } }
위의 코드를 보면 위젯을 상속해 로깅 위젯을 만들었다
하지만 내장 락이 재입장이 불가능하다면 위와 같은 메서드를 호출할때 데드락이 걸릴것이다.
2.4 Guarding State with locks
For each mutable state variable that may be accessed by more than one thread, all access to that variable must be performed with the same lock held, in this case, we say that the variable is guarded by that lock.
각각의 변경 가능한 상태의 변수들이 만약 한개이상의 쓰레드에서 접근된다면
그 변수에 접근하는 모든 락은 동일한 락으로 방어되어야한다.
이와같은 상황에 우리는 그 변수는 락에의해 방어 되고 있다고 할 수 있다.
every shared, variable should be guarded by exactly one lock. make it clear to maintainers which lock that is
모든 공유 변수들은 단 한하나의 락으로 방어 되어야한다. 그래서 유지 보수자가 어떤락이
그 역활을 하는지 쉽게 알 수 있어야 한다.
For every invariant that involve more than one variable, all the variables involved in that invariant must be guarded by the same lock
불변성에 하나 이상의 변수가 연관된다면 연관된 모든 변수들은 반드시 하나의 락으로
관리 되어야 한다.
liveness concerns the complementary goal that "something good
eventually happens".
2.5 Liveness and Performance
@ThreadSafe public class CachedFactorizer implements Servlet { @GuardedBy("this") private BigInteger lastNumber; @GuardedBy("this") private BigInteger[] lastFactors; @GuardedBy("this") private long hits; @GuardedBy("this") private long cacheHits; public synchronized long getHits() { return hits; } public synchronized double getCacheHitRatio() { return (double) cacheHits / (double) hits; } public void service(ServletRequest req, ServletResponse resp) { BigInteger i = extractFromRequest(req); BigInteger[] factors = null; synchronized (this) { ++hits; if (i.equals(lastNumber)) { ++cacheHits; factors = lastFactors.clone(); } } if (factors == null) { factors = factor(i); synchronized (this) { lastNumber = i; lastFactors = factors.clone(); } } encodeIntoResponse(resp, factors); } }
there is frequently a tension between simplicity and performance. when implementing synchronization policy, resist the temptation to prematurely sacrifice simplicity for the shake of performance
명료함과 성능 사이에서 고민할때가 많다 동기화 정책을 구현할때는 성능을 위해 너무 빨리
명료함을 버리려는 유혹에 빠지지 말자
avoid holing locks during lengthy computation or operations at risk of not completing quickly such as network or console I/O
일찍 끝나지 않는 network 또는 IO 를 하는 오퍼레이션 또는 긴계산 시간이 필요한 코드들은
락을 걸때 피하자