서버는 소켓을 열어놓고 클라이언트가 연결하기를 기다린다. 클라이언트는 소켓에 연결해 요청을 보낸다.
-> 주로 시간을 보내는 곳 : I/O(소켓 사용, 데이터베이스 연결, 가상 메모리 스와핑 기다리기), 프로세서(수치 계산, 정규 표현식 처리, 가비지 컬렉션)
둘 중 하나가 지배적으로 차지하고 있다. 프로세서에서 시간을 보내면 하드웨어를 추가해 성능을 올리면 된다. I/O 연산에서 시간을 보내면 동시성이 성능을 높여준다.
결론 : 단일스레드 시스템을 다중스레드 시스템으로 변환해 시스템 성능을 높인다.
public class IdGenerator {
int lastIdUsed;
public int incrementValue() {
return ++lastIdUsed;
}
}
메서드에서 반환값은 lastIdUsed 값과 동일하다. 하지만 스레드 두개로 실행하면 다양한 결과가 나오게 된다.
=> synchronized로 선언하면 문제가 해결된다.
스레드가 N개이면 가능한 경로 수는 N!이다.
원자적 연산 : 중단이 불가능한 연산 (ex. 메모리에 값을 할당하는 연산)
전처리 증가 연산자(++)는 중단이 가능하다.
: 스레드 풀을 관리하고, 풀 크기를 자동으로 조정하며, 필요하다면 스레드를 재사용한다.
락을 거는 쪽보다 문제를 감지하는 쪽이 거의 항상 효율적이다.
스레드 두 개가 인스턴스 하나를 공유하게 되면 예외가 발생할 가능성이 있다.
해결 방안
1. 실패를 용인한다.
때로는 실패해도 괜찮도록 프로그램을 조정할 수 있다.
2. 클라이언트-기반 잠금
각 클라이언트는 synchronized 키워드를 이용해 객체에 락을 건다. 하지만 서버를 사용하는 모든 프로그래머가 락을 기억해 객체에 걸었다 풀어야 하기 때문에 위험하다.
3. 서버-기반 잠금
다중 스레드를 고려하게 클라스 API를 설계한다.
=> 코드 중복이 줄어든다. 성능이 좋아진다. 오류가 발생할 가능성이 줄어든다. 스레드 정책이 하나이다. 공유 변수 범위가 줄어든다.
다중스레드 환경이 단일스레드 환경과 비교해 처리율이 세 배가 높다.
어떠한 애플리케이션이 생선과 갱신이라는 연산 두개를 수행한다.
만약, 풀 크기가 사용자 수가 많다면 데드락이 발생하게 된다.
해결책
: 디버깅 문을 추가해 사태를 파악해야 한다.
아래 네 조건을 모두 충족해야 데드락이 발생한다.
여러 스레드가 한 자원을 공유하나 그 자원은 여러 스레드가 동시에 사용하지 못하며 개수가 제한적일 때 상호 배제 조건을 만족한다.
한 스레드가 자원을 점유하면 작업을 마칠 때까지 내놓지 않는다.
스레드는 다르 스레드에게 자원을 빼앗지 못한다.
스레드가 점유하고 필요한게 얽혀서 사용할 수 없는 경우이다.
동시에 사용해도 괜찮은 자원을 사용한다. 스레드 수 이상으로 자원 수를 늘린다. 자원을 점유하기 전에 필요한 자원이 모두 있는지 확인한다.
자원을 점유하기 전에 어느 하나라도 점유하지 못하면 지금가지 점유한 자원을 몽땅 내놓고 처음부터 시작한다.
기아와 라이브락이 발생할 수 있다.
다른 스레드로부터 자원을 뺏어온다.
스레드에 자원을 똑같은 순서로 할당하게 만든다.
모든 스레드가 일정 순서에 동의하고 그 순서로만 자원을 할당한다.