static 변수와 수명
인스턴스 변수와 static 변수의 소유·수명을 비교하고 Member 생성 수처럼 정말 공유할 상태만 클래스에 둡니다.
인스턴스 필드는 객체마다 하나씩 존재합니다.
static 필드는 클래스에 하나만 존재해 모든 인스턴스가 공유합니다.
이 장의 예제는 한 JVM 안에서 같은 클래스 정의를 사용하는 단일 스레드 실행입니다. java StaticMemberCount를 새로 실행하면 새 실행의 카운터는 다시 0에서 시작하며, 이전 실행의 3을 파일처럼 저장하지 않습니다. 더 정확히 JVM은 클래스 이름과 그 클래스를 정의한 로더를 함께 구분합니다(JVMS의 클래스 생성·로딩). 지금은 별도 클래스 로더를 다루지 않으므로, 같은 Member 클래스로 만든 객체들이 값을 공유한다는 범위에 집중합니다.
어느 쪽이 더 편한지가 아니라 값이 “개별 객체의 상태”인지 “클래스 전체의 상태”인지에 따라 선택해야 합니다.
인스턴스 카운터의 한계
public final class InstanceCountBug {
public static void main(String[] args) {
Member first = new Member("[email protected]");
Member second = new Member("[email protected]");
Member third = new Member("[email protected]");
System.out.println(first.count);
System.out.println(second.count);
System.out.println(third.count);
}
private static final class Member {
String email;
int count;
Member(String email) {
this.email = email;
count++;
}
}
}1
1
1세 객체는 각각 자기 count 필드를 0에서 1로 증가시킵니다.
전체 생성 수라는 요구와 저장 위치가 맞지 않습니다.
한 객체의 회원 나이는 인스턴스마다 달라야 하지만, 해당 클래스의 인스턴스가 지금까지 몇 개 생성됐는지는 하나의 공유 값이어야 합니다.
별도 Counter 객체를 모든 생성자에 전달해 공유할 수도 있습니다.
의존성이 명시되는 장점이 있지만 단순한 클래스 전체 카운터에는 static 필드가 직접적인 선택입니다.
static 필드 접근
public final class StaticMemberCount {
public static void main(String[] args) {
new Member("[email protected]");
new Member("[email protected]");
new Member("[email protected]");
System.out.println("created=" + Member.createdCount);
}
private static final class Member {
private static int createdCount;
private final String email;
Member(String email) {
this.email = email;
createdCount++;
}
}
}created=3createdCount는 Member 인스턴스마다 만들어지지 않고 클래스에 하나만 존재합니다.
어느 생성자 호출이 증가시켜도 같은 값을 바꿉니다.
접근할 때 first.createdCount처럼 인스턴스를 통해 쓸 수도 있지만, 클래스 소유라는 사실을 가리므로 Member.createdCount가 명확합니다.
인스턴스 상태와 공유 상태
public final class InstanceAndStaticState {
public static void main(String[] args) {
Member first = new Member("[email protected]", 40);
Member second = new Member("[email protected]", 50);
first.celebrateBirthday();
System.out.println(first.describe());
System.out.println(second.describe());
System.out.println("created=" + Member.createdCount());
}
private static final class Member {
private static int createdCount;
private final String email;
private int age;
Member(String email, int age) {
this.email = email;
this.age = age;
createdCount++;
}
void celebrateBirthday() {
if (age >= 120) throw new IllegalStateException("maximum age");
age++;
}
String describe() {
return email + "=" + age;
}
static int createdCount() {
return createdCount;
}
}
}[email protected]=41
[email protected]=50
created=2first의 생일을 반영해도 second 객체의 age는 변하지 않습니다.
두 값은 서로 다른 힙 객체의 인스턴스 필드입니다.
한 회원의 age 변화와 클래스의 생성 누계
두 new로 createdCount=2가 된 후 first의 birthday만 age 40→41로 바뀌고 second 50/누계 2는 유지됩니다. 객체별 필드와 공유 누계의 실제 변화 대상을 비교할 수 있습니다.
InstanceAndStaticState에서 두 객체 생성 후 첫 회원의 생일을 반영해도 둘째 age 50과 클래스 생성 누계 2는 그대로인 상태 비교다.
age는 객체별 값이고 createdCount는 공유 누계다
첫 회원의 생일은 첫 age만 바꾸며 두 번째 회원과 생성 누계는 그대로입니다.
| 시점 | first의 age | second의 age | Member.createdCount |
|---|---|---|---|
| 두 Member 생성 뒤 | 40 | 50 | 2 |
first.celebrateBirthday() 뒤 | 41 | 50 · 그대로 | 2 · 그대로 |
InstanceAndStaticState · 같은 실행의 두 시점
- 두 Member 생성 뒤
first.age=40,second.age=50,Member.createdCount=2입니다.- first의 생일 호출 뒤
first.age=41만 바뀝니다.second.age=50과Member.createdCount=2는 그대로입니다.
first는 [email protected], second는 [email protected]인 별개 객체입니다. createdCount는 생성자에서 증가한 누계이지 현재 살아 있는 객체 수가 아닙니다. 지역 참조가 사라지거나 객체가 회수 대상이 되어도 이 코드에는 누계를 자동 감소시키는 연산이 없습니다.
이 예의 조건
- createdCount는 공유 생성 누계이며 생일 횟수가 아닙니다.
관계도의 보충 설명
| 구분 | 내용 |
|---|---|
| 2 | Member 클래스의 createdCount 2 하나와 서로 다른 Kim/Lee 인스턴스를 구분한다. Kim의 생일은 40→41, Lee는 50 유지/생성 누계 2 유지이다. 점선은 동일 클래스 소속이며 호출이나 객체 참조가 아니다. |
| 구분 | 내용 |
|---|---|
| 설명 | Member 클래스의 createdCount 2 하나와 서로 다른 Kim/Lee 인스턴스를 구분한다. Kim의 생일은 40→41, Lee는 50 유지/생성 누계 2 유지이다. 점선은 동일 클래스 소속이며 호출이나 객체 참조가 아니다. |
| 전체 읽기 정보 | 한 회원의 age 변화와 클래스의 생성 누계 한 회원의 age 변화와 클래스의 생성 누계 InstanceAndStaticState에서 두 객체 생성 후 첫 회원의 생일을 반영해도 둘째 age 50과 클래스 생성 누계 2는 그대로인 상태 비교다. age는 객체별 값이고 createdCount는 공유 누계다 첫 회원의 생일은 첫 age만 바꾸며 두 번째 회원과 생성 누계는 그대로입니다. InstanceAndStaticState · 동일 실행의 생일 호출 전후 시점 first의 age second의 age Member.createdCount 두 Member 생성 뒤 40 50 2 first.celebrateBirthday() 뒤 41 50 · 그대로 2 · 그대로 InstanceAndStaticState · 같은 실행의 두 시점 두 Member 생성 뒤 first.age=40 , second.age=50 , Member.createdCount=2 입니다. first의 생일 호출 뒤 first.age=41 만 바뀝니다. second.age=50 과 Member.createdCount=2 는 그대로입니다. first 는 [email protected] , second 는 [email protected] 인 별개 객체입니다. createdCount 는 생성자에서 증가한 누계이지 현재 살아 있는 객체 수가 아닙니다. 지역 참조가 사라지거나 객체가 회수 대상이 되어도 이 코드에는 누계를 자동 감소시키는 연산이 없습니다. |
| 관찰·상황 | 값·보충 정보 | 변화·이유 |
|---|---|---|
| 두 생성 Kim 40과 Lee 50의 객체를 만듭니다. |
|
|
| 첫 생일 Kim의 생일이 자기 age를 증가시킵니다. |
|
|
| 누계 확인 생일 뒤에도 클래스 누계는 2입니다. |
|
|
한 회원의 age 변화와 클래스의 생성 누계
두 new로 createdCount=2가 된 후 first의 birthday만 age 40→41로 바뀌고 second 50/누계 2는 유지됩니다. 객체별 필드와 공유 누계의 실제 변화 대상을 비교할 수 있습니다.
InstanceAndStaticState에서 두 객체 생성 후 첫 회원의 생일을 반영해도 둘째 age 50과 클래스 생성 누계 2는 그대로인 상태 비교다.
age는 객체별 값이고 createdCount는 공유 누계다
첫 회원의 생일은 첫 age만 바꾸며 두 번째 회원과 생성 누계는 그대로입니다.
| 시점 | first의 age | second의 age | Member.createdCount |
|---|---|---|---|
| 두 Member 생성 뒤 | 40 | 50 | 2 |
first.celebrateBirthday() 뒤 | 41 | 50 · 그대로 | 2 · 그대로 |
InstanceAndStaticState · 같은 실행의 두 시점
- 두 Member 생성 뒤
first.age=40,second.age=50,Member.createdCount=2입니다.- first의 생일 호출 뒤
first.age=41만 바뀝니다.second.age=50과Member.createdCount=2는 그대로입니다.
first는 [email protected], second는 [email protected]인 별개 객체입니다. createdCount는 생성자에서 증가한 누계이지 현재 살아 있는 객체 수가 아닙니다. 지역 참조가 사라지거나 객체가 회수 대상이 되어도 이 코드에는 누계를 자동 감소시키는 연산이 없습니다.
이 예의 조건
- createdCount는 공유 생성 누계이며 생일 횟수가 아닙니다.
관계도의 보충 설명
| 구분 | 내용 |
|---|---|
| 2 | Member 클래스의 createdCount 2 하나와 서로 다른 Kim/Lee 인스턴스를 구분한다. Kim의 생일은 40→41, Lee는 50 유지/생성 누계 2 유지이다. 점선은 동일 클래스 소속이며 호출이나 객체 참조가 아니다. |
| 구분 | 내용 |
|---|---|
| 설명 | Member 클래스의 createdCount 2 하나와 서로 다른 Kim/Lee 인스턴스를 구분한다. Kim의 생일은 40→41, Lee는 50 유지/생성 누계 2 유지이다. 점선은 동일 클래스 소속이며 호출이나 객체 참조가 아니다. |
| 전체 읽기 정보 | 한 회원의 age 변화와 클래스의 생성 누계 한 회원의 age 변화와 클래스의 생성 누계 InstanceAndStaticState에서 두 객체 생성 후 첫 회원의 생일을 반영해도 둘째 age 50과 클래스 생성 누계 2는 그대로인 상태 비교다. age는 객체별 값이고 createdCount는 공유 누계다 첫 회원의 생일은 첫 age만 바꾸며 두 번째 회원과 생성 누계는 그대로입니다. InstanceAndStaticState · 동일 실행의 생일 호출 전후 시점 first의 age second의 age Member.createdCount 두 Member 생성 뒤 40 50 2 first.celebrateBirthday() 뒤 41 50 · 그대로 2 · 그대로 InstanceAndStaticState · 같은 실행의 두 시점 두 Member 생성 뒤 first.age=40 , second.age=50 , Member.createdCount=2 입니다. first의 생일 호출 뒤 first.age=41 만 바뀝니다. second.age=50 과 Member.createdCount=2 는 그대로입니다. first 는 [email protected] , second 는 [email protected] 인 별개 객체입니다. createdCount 는 생성자에서 증가한 누계이지 현재 살아 있는 객체 수가 아닙니다. 지역 참조가 사라지거나 객체가 회수 대상이 되어도 이 코드에는 누계를 자동 감소시키는 연산이 없습니다. |
| 관찰·상황 | 값·보충 정보 | 변화·이유 |
|---|---|---|
| 두 생성 Kim 40과 Lee 50의 객체를 만듭니다. |
|
|
| 첫 생일 Kim의 생일이 자기 age를 증가시킵니다. |
|
|
| 누계 확인 생일 뒤에도 클래스 누계는 2입니다. |
|
|
createdCount는 어느 객체를 통해 묻지 않고 클래스 메서드로 읽습니다.
static 필드의 수명은 특정 인스턴스 참조가 사라지는 것과 연결되지 않습니다.
클래스가 로드돼 사용하는 동안 공유됩니다.
객체가 가비지 컬렉션 대상이 되어도 단순 생성 누계는 자동 감소하지 않습니다.
“현재 살아 있는 객체 수”를 static 카운터만으로 정확히 재려는 시도는 GC 시점과 수명 추적 문제 때문에 훨씬 어렵습니다.
공유 상태의 결합
static 변경 가능 상태는 어디서든 클래스 이름으로 바꿀 수 있어 호출 순서에 따라 결과가 달라질 수 있습니다.
Member.createdCount = 0;필드를 public으로 열면 한 기능이 다른 기능의 카운터를 초기화할 수 있습니다.
private으로 숨기고 필요한 읽기·초기화 정책만 메서드로 제한합니다.
테스트나 여러 회원 저장소가 서로 독립적이어야 한다면 static 공유가 오히려 요구에 맞지 않습니다.
예를 들어 MemberRegistry마다 발급 번호가 1부터 시작해야 한다면 카운터를 MemberRegistry 인스턴스 필드로 둡니다.
public final class PerRegistrySequence {
public static void main(String[] args) {
MemberRegistry personal = new MemberRegistry();
MemberRegistry work = new MemberRegistry();
System.out.println(personal.nextId());
System.out.println(personal.nextId());
System.out.println(work.nextId());
}
private static final class MemberRegistry {
private int sequence;
int nextId() {
sequence++;
return sequence;
}
}
}1
2
1sequence를 static으로 만들었다면 work의 첫 번호가 3이 됩니다.
예시
그 뒤 personal.nextId()를 한 번 더 호출하면 원래 인스턴스 필드 프로그램은 3을 출력합니다. personal에 남아 있던 2만 증가하기 때문입니다. static으로 바꾼 별도 프로그램에서는 앞 세 호출의 공유 값이 3이므로 4입니다. 두 프로그램을 각각 처음부터 실행한 비교이며, 하나의 실행 중에 필드 종류를 바꾸는 과정이 아닙니다.
공유 범위가 요구와 같은지 확인해야 합니다.
레지스트리별 sequence와 static 파생 비교
같은 세 호출에서 인스턴스 sequence는 1·2·1, 별도 static 버전은 1·2·3을 반환합니다. 두 독립 프로그램의 지역/공유 상태를 대응 단계로 비교할 수 있습니다.
PerRegistrySequence의 실제 1,2,1 결과와 sequence 선언만 static으로 바꾼 독립 파생 프로그램의 1,2,3 결과를 같은 세 호출로 비교한다.
인스턴스 필드는 저장소마다 독립 번호를 만든다
같은 세 호출을 비교하면 원문은 1·2·1, static 파생 프로그램은 1·2·3입니다.
| 호출 | 원문: private int sequence | 파생 비교: private static int sequence |
|---|---|---|
첫 personal.nextId() | 1 · personal의 필드 | 1 · 클래스 공유 필드 |
둘째 personal.nextId() | 2 · personal의 필드 | 2 · 같은 공유 필드 |
첫 work.nextId() | 1 · work의 독립 필드 | 3 · 앞 호출의 누계가 이어짐 |
PerRegistrySequence · 서로 다른 두 프로그램 비교
- 첫 personal.nextId(): 원문은 personal의 필드가 1, static 파생형은 클래스 공유 필드가 1입니다.
- 둘째 personal.nextId(): 원문은 personal의 필드가 2, static 파생형은 같은 공유 필드가 2입니다.
- 첫 work.nextId(): 원문은 work의 독립 필드에서 1, static 파생형은 공유 누계가 이어져 3입니다.
static 파생 비교는 원문 실행 결과가 아닙니다. sequence 선언에 static만 추가하고 나머지 세 호출은 유지한 별도 파생 비교입니다. 두 실행의 상태를 서로 이어 붙이지 않습니다.
이 예의 조건
- 인스턴스 원문과 static 파생형은 각 초기 0의 독립 실행입니다.
관계도의 보충 설명
| 구분 | 내용 |
|---|---|
| 2 | 원 인스턴스 필드와 static 파생형은 각각 초기 0의 독립 실행이다. 동일한 personal 두 번/work 한 번의 반환은 1,2,1과 1,2,3이며 다른 실행의 상태를 이어 붙이지 않는다. |
| 29 | 같은 호출 순서 · 독립 실행, 각 초깃값은 0 |
| 구분 | 내용 |
|---|---|
| 설명 | 원 인스턴스 필드와 static 파생형은 각각 초기 0의 독립 실행이다. 동일한 personal 두 번/work 한 번의 반환은 1,2,1과 1,2,3이며 다른 실행의 상태를 이어 붙이지 않는다. |
| 전체 읽기 정보 | 레지스트리별 sequence와 static 파생 비교 레지스트리별 sequence와 static 파생 비교 PerRegistrySequence의 실제 1,2,1 결과와 sequence 선언만 static으로 바꾼 독립 파생 프로그램의 1,2,3 결과를 같은 세 호출로 비교한다. 인스턴스 필드는 저장소마다 독립 번호를 만든다 같은 세 호출을 비교하면 원문은 1·2·1, static 파생 프로그램은 1·2·3입니다. PerRegistrySequence · 같은 호출 순서, 서로 다른 두 프로그램 호출 원문: private int sequence 파생 비교: private static int sequence 첫 personal.nextId() 1 · personal의 필드 1 · 클래스 공유 필드 둘째 personal.nextId() 2 · personal의 필드 2 · 같은 공유 필드 첫 work.nextId() 1 · work의 독립 필드 3 · 앞 호출의 누계가 이어짐 PerRegistrySequence · 서로 다른 두 프로그램 비교 첫 personal.nextId(): 원문은 personal의 필드가 1, static 파생형은 클래스 공유 필드가 1입니다. 둘째 personal.nextId(): 원문은 personal의 필드가 2, static 파생형은 같은 공유 필드가 2입니다. 첫 work.nextId(): 원문은 work의 독립 필드에서 1 , static 파생형은 공유 누계가 이어져 3 입니다. static 파생 비교는 원문 실행 결과가 아닙니다. sequence 선언에 static 만 추가하고 나머지 세 호출은 유지한 별도 파생 비교입니다. 두 실행의 상태를 서로 이어 붙이지 않습니다. |
| 관찰·상황 | 값·보충 정보 | 변화·이유 |
|---|---|---|
| 첫 personal personal.nextId를 처음 호출합니다. |
|
|
| 둘째 personal personal을 다시 호출합니다. |
|
|
| work 호출 다른 work 객체를 처음 호출합니다. |
|
|
레지스트리별 sequence와 static 파생 비교
같은 세 호출에서 인스턴스 sequence는 1·2·1, 별도 static 버전은 1·2·3을 반환합니다. 두 독립 프로그램의 지역/공유 상태를 대응 단계로 비교할 수 있습니다.
PerRegistrySequence의 실제 1,2,1 결과와 sequence 선언만 static으로 바꾼 독립 파생 프로그램의 1,2,3 결과를 같은 세 호출로 비교한다.
인스턴스 필드는 저장소마다 독립 번호를 만든다
같은 세 호출을 비교하면 원문은 1·2·1, static 파생 프로그램은 1·2·3입니다.
| 호출 | 원문: private int sequence | 파생 비교: private static int sequence |
|---|---|---|
첫 personal.nextId() | 1 · personal의 필드 | 1 · 클래스 공유 필드 |
둘째 personal.nextId() | 2 · personal의 필드 | 2 · 같은 공유 필드 |
첫 work.nextId() | 1 · work의 독립 필드 | 3 · 앞 호출의 누계가 이어짐 |
PerRegistrySequence · 서로 다른 두 프로그램 비교
- 첫 personal.nextId(): 원문은 personal의 필드가 1, static 파생형은 클래스 공유 필드가 1입니다.
- 둘째 personal.nextId(): 원문은 personal의 필드가 2, static 파생형은 같은 공유 필드가 2입니다.
- 첫 work.nextId(): 원문은 work의 독립 필드에서 1, static 파생형은 공유 누계가 이어져 3입니다.
static 파생 비교는 원문 실행 결과가 아닙니다. sequence 선언에 static만 추가하고 나머지 세 호출은 유지한 별도 파생 비교입니다. 두 실행의 상태를 서로 이어 붙이지 않습니다.
이 예의 조건
- 인스턴스 원문과 static 파생형은 각 초기 0의 독립 실행입니다.
관계도의 보충 설명
| 구분 | 내용 |
|---|---|
| 2 | 원 인스턴스 필드와 static 파생형은 각각 초기 0의 독립 실행이다. 동일한 personal 두 번/work 한 번의 반환은 1,2,1과 1,2,3이며 다른 실행의 상태를 이어 붙이지 않는다. |
| 29 | 같은 호출 순서 · 독립 실행, 각 초깃값은 0 |
| 구분 | 내용 |
|---|---|
| 설명 | 원 인스턴스 필드와 static 파생형은 각각 초기 0의 독립 실행이다. 동일한 personal 두 번/work 한 번의 반환은 1,2,1과 1,2,3이며 다른 실행의 상태를 이어 붙이지 않는다. |
| 전체 읽기 정보 | 레지스트리별 sequence와 static 파생 비교 레지스트리별 sequence와 static 파생 비교 PerRegistrySequence의 실제 1,2,1 결과와 sequence 선언만 static으로 바꾼 독립 파생 프로그램의 1,2,3 결과를 같은 세 호출로 비교한다. 인스턴스 필드는 저장소마다 독립 번호를 만든다 같은 세 호출을 비교하면 원문은 1·2·1, static 파생 프로그램은 1·2·3입니다. PerRegistrySequence · 같은 호출 순서, 서로 다른 두 프로그램 호출 원문: private int sequence 파생 비교: private static int sequence 첫 personal.nextId() 1 · personal의 필드 1 · 클래스 공유 필드 둘째 personal.nextId() 2 · personal의 필드 2 · 같은 공유 필드 첫 work.nextId() 1 · work의 독립 필드 3 · 앞 호출의 누계가 이어짐 PerRegistrySequence · 서로 다른 두 프로그램 비교 첫 personal.nextId(): 원문은 personal의 필드가 1, static 파생형은 클래스 공유 필드가 1입니다. 둘째 personal.nextId(): 원문은 personal의 필드가 2, static 파생형은 같은 공유 필드가 2입니다. 첫 work.nextId(): 원문은 work의 독립 필드에서 1 , static 파생형은 공유 누계가 이어져 3 입니다. static 파생 비교는 원문 실행 결과가 아닙니다. sequence 선언에 static 만 추가하고 나머지 세 호출은 유지한 별도 파생 비교입니다. 두 실행의 상태를 서로 이어 붙이지 않습니다. |
| 관찰·상황 | 값·보충 정보 | 변화·이유 |
|---|---|---|
| 첫 personal personal.nextId를 처음 호출합니다. |
|
|
| 둘째 personal personal을 다시 호출합니다. |
|
|
| work 호출 다른 work 객체를 처음 호출합니다. |
|
|
회원 저장소의 static 후보
다음은 클래스 전체에 하나여도 의미가 안정적입니다.
- 모든 Member가 공유하는 최대 허용 나이 상수
- 상태를 사용하지 않는 입력 정규화 함수
- 진단 목적의 전체 생성 누계
- 프로그램 수준에서 하나로 합의한 버전 문자열
반대로 다음은 인스턴스에 두는 편이 자연스럽습니다.
- 각 레지스트리의
members와size - 각 회원의
email과age - 레지스트리별 목표와 발급 순서
- 사용자별 설정과 집계
공유가 필요하다는 이유만으로 static부터 선택하지 않습니다.
공유 객체를 생성자에 전달하면 소유 관계와 교체 가능성이 더 명시적인 경우도 있습니다.
연습 문제
MemberRegistry 인스턴스마다 addedCount를 갖고, 모든 레지스트리가 성공적으로 추가한 전체 회원 수는 static globalAddedCount로 세세요.
두 레지스트리에 각각 2개와 1개를 추가해 값을 출력합니다.
해설을 열기 전에 호출마다 (personal, work, global)을 기록하세요. 시작 (0, 0, 0)에서 각 add()가 자기 인스턴스 필드 하나와 클래스 필드 하나를 늘립니다. 마지막에 work에 한 번 더 추가하면 어떤 세 값이 될지도 예측합니다. 여기의 add()는 실제 회원 배열이나 실패 분기가 없는 횟수 모델입니다.
해설 보기
public final class LocalAndGlobalCounts {
public static void main(String[] args) {
MemberRegistry personal = new MemberRegistry();
MemberRegistry work = new MemberRegistry();
personal.add();
personal.add();
work.add();
System.out.println("personal=" + personal.addedCount());
System.out.println("work=" + work.addedCount());
System.out.println("global=" + MemberRegistry.globalAddedCount());
}
private static final class MemberRegistry {
private static int globalAddedCount;
private int addedCount;
void add() {
addedCount++;
globalAddedCount++;
}
int addedCount() {
return addedCount;
}
static int globalAddedCount() {
return globalAddedCount;
}
}
}personal=2
work=1
global=3개별 필드는 수신자마다 다르고 static 필드는 두 수신자의 성공 횟수를 합칩니다.
두 숫자의 의미와 수명이 다릅니다.
추가 확인의 결과는 personal=2, work=2, global=4입니다. 기존 personal의 2는 work 호출의 수신 객체가 아니므로 바뀌지 않습니다. static이 자동으로 스레드 안전성을 주지는 않습니다. 두 ++는 이 단일 스레드 예제에서 순서대로 실행되며, 여러 스레드가 동시에 정확한 누계를 갱신하는 방법은 동시성 장에서 별도로 다룹니다.
각 add가 바꾸는 로컬 카운터와 공유 카운터
personal의 두 add와 work의 한 add에서 수신자의 addedCount와 공유 globalAddedCount가 각각 증가합니다. 공유 누계는 0→1→2→3으로 이어집니다. 단일 스레드의 실제 호출 순서에 따라 수신자와 공유 값의 변화를 비교합니다.
LocalAndGlobalCounts의 personal.add 두 번과 work.add 한 번이 끝난 뒤 각 인스턴스 addedCount와 클래스 globalAddedCount에 남는 값을 비교한다. 원문에는 실패 분기가 없다.
각 호출은 자기 개수와 클래스의 공유 누계를 늘린다
personal에 두 번, work에 한 번 추가하면 개별 값은 2·1이고 공유 값은 3입니다.
| 완료한 호출 | personal.addedCount | work.addedCount | MemberRegistry.globalAddedCount |
|---|---|---|---|
첫 personal.add() | 1 | 0 | 1 |
둘째 personal.add() | 2 | 0 | 2 |
첫 work.add() | 2 | 1 | 3 |
LocalAndGlobalCounts · 각 호출 완료 뒤
- 첫 personal.add(): personal의
addedCount=1, work의addedCount=0, 클래스의globalAddedCount=1입니다. - 둘째 personal.add(): personal은
2, work는0, 공유 누계는2입니다. - 첫 work.add(): personal은
2, work는1, 공유 누계는3입니다.
원문 add()는 수신자의 addedCount++ 다음에 globalAddedCount++를 실행합니다. 실패·취소 분기는 없으며 두 증가가 동시적이거나 스레드 안전하다는 뜻도 아닙니다. 최종 출력은 personal=2, work=1, global=3입니다.
이 예의 조건
- 원문은 순차 단일 스레드 실행이며 ++가 자동으로 스레드 안전하지 않다.
관계도의 보충 설명
| 구분 | 내용 |
|---|---|
| 2 | 초기 개별 0/0, 공유 0→personal 첫 add 1/0/1→둘째 2/0/2→work 첫 2/1/3이다. 개별 addedCount와 클래스 globalAddedCount를 구별하며 원 add에는 실패 분기가 없고 원자성을 주장하지 않는다. |
| 4 | personal=0 |
| 5 | work=0 |
| 8 | personal=1 |
| 9 | work=0 |
| 13 | work=0 |
| 22 | 순차 단일 스레드 모델 · 원 add에는 실패 분기 없음 |
| 구분 | 내용 |
|---|---|
| 설명 | 초기 개별 0/0, 공유 0→personal 첫 add 1/0/1→둘째 2/0/2→work 첫 2/1/3이다. 개별 addedCount와 클래스 globalAddedCount를 구별하며 원 add에는 실패 분기가 없고 원자성을 주장하지 않는다. |
| 전체 읽기 정보 | 각 add가 바꾸는 로컬 카운터와 공유 카운터 각 add가 바꾸는 로컬 카운터와 공유 카운터 LocalAndGlobalCounts의 personal.add 두 번과 work.add 한 번이 끝난 뒤 각 인스턴스 addedCount와 클래스 globalAddedCount에 남는 값을 비교한다. 원문에는 실패 분기가 없다. 각 호출은 자기 개수와 클래스의 공유 누계를 늘린다 personal에 두 번, work에 한 번 추가하면 개별 값은 2·1이고 공유 값은 3입니다. LocalAndGlobalCounts · 각 add() 호출이 끝난 뒤 완료한 호출 personal.addedCount work.addedCount MemberRegistry.globalAddedCount 첫 personal.add() 1 0 1 둘째 personal.add() 2 0 2 첫 work.add() 2 1 3 LocalAndGlobalCounts · 각 호출 완료 뒤 첫 personal.add(): personal의 addedCount=1 , work의 addedCount=0 , 클래스의 globalAddedCount=1 입니다. 둘째 personal.add(): personal은 2 , work는 0 , 공유 누계는 2 입니다. 첫 work.add(): personal은 2 , work는 1 , 공유 누계는 3 입니다. 원문 add() 는 수신자의 addedCount++ 다음에 globalAddedCount++ 를 실행합니다. 실패·취소 분기는 없으며 두 증가가 동시적이거나 스레드 안전하다는 뜻도 아닙니다. 최종 출력은 personal=2 , work=1 , global=3 입니다. |
| 관찰·상황 | 값·보충 정보 | 변화·이유 |
|---|---|---|
| 초깃값 두 Registry와 공유 누계를 0으로 둡니다. |
|
|
| 첫 personal personal.add를 호출합니다. |
|
|
| 둘째 personal 같은 객체를 다시 호출합니다. |
|
|
| 첫 work work.add를 호출합니다. |
|
|
각 add가 바꾸는 로컬 카운터와 공유 카운터
personal의 두 add와 work의 한 add에서 수신자의 addedCount와 공유 globalAddedCount가 각각 증가합니다. 공유 누계는 0→1→2→3으로 이어집니다. 단일 스레드의 실제 호출 순서에 따라 수신자와 공유 값의 변화를 비교합니다.
LocalAndGlobalCounts의 personal.add 두 번과 work.add 한 번이 끝난 뒤 각 인스턴스 addedCount와 클래스 globalAddedCount에 남는 값을 비교한다. 원문에는 실패 분기가 없다.
각 호출은 자기 개수와 클래스의 공유 누계를 늘린다
personal에 두 번, work에 한 번 추가하면 개별 값은 2·1이고 공유 값은 3입니다.
| 완료한 호출 | personal.addedCount | work.addedCount | MemberRegistry.globalAddedCount |
|---|---|---|---|
첫 personal.add() | 1 | 0 | 1 |
둘째 personal.add() | 2 | 0 | 2 |
첫 work.add() | 2 | 1 | 3 |
LocalAndGlobalCounts · 각 호출 완료 뒤
- 첫 personal.add(): personal의
addedCount=1, work의addedCount=0, 클래스의globalAddedCount=1입니다. - 둘째 personal.add(): personal은
2, work는0, 공유 누계는2입니다. - 첫 work.add(): personal은
2, work는1, 공유 누계는3입니다.
원문 add()는 수신자의 addedCount++ 다음에 globalAddedCount++를 실행합니다. 실패·취소 분기는 없으며 두 증가가 동시적이거나 스레드 안전하다는 뜻도 아닙니다. 최종 출력은 personal=2, work=1, global=3입니다.
이 예의 조건
- 원문은 순차 단일 스레드 실행이며 ++가 자동으로 스레드 안전하지 않다.
관계도의 보충 설명
| 구분 | 내용 |
|---|---|
| 2 | 초기 개별 0/0, 공유 0→personal 첫 add 1/0/1→둘째 2/0/2→work 첫 2/1/3이다. 개별 addedCount와 클래스 globalAddedCount를 구별하며 원 add에는 실패 분기가 없고 원자성을 주장하지 않는다. |
| 4 | personal=0 |
| 5 | work=0 |
| 8 | personal=1 |
| 9 | work=0 |
| 13 | work=0 |
| 22 | 순차 단일 스레드 모델 · 원 add에는 실패 분기 없음 |
| 구분 | 내용 |
|---|---|
| 설명 | 초기 개별 0/0, 공유 0→personal 첫 add 1/0/1→둘째 2/0/2→work 첫 2/1/3이다. 개별 addedCount와 클래스 globalAddedCount를 구별하며 원 add에는 실패 분기가 없고 원자성을 주장하지 않는다. |
| 전체 읽기 정보 | 각 add가 바꾸는 로컬 카운터와 공유 카운터 각 add가 바꾸는 로컬 카운터와 공유 카운터 LocalAndGlobalCounts의 personal.add 두 번과 work.add 한 번이 끝난 뒤 각 인스턴스 addedCount와 클래스 globalAddedCount에 남는 값을 비교한다. 원문에는 실패 분기가 없다. 각 호출은 자기 개수와 클래스의 공유 누계를 늘린다 personal에 두 번, work에 한 번 추가하면 개별 값은 2·1이고 공유 값은 3입니다. LocalAndGlobalCounts · 각 add() 호출이 끝난 뒤 완료한 호출 personal.addedCount work.addedCount MemberRegistry.globalAddedCount 첫 personal.add() 1 0 1 둘째 personal.add() 2 0 2 첫 work.add() 2 1 3 LocalAndGlobalCounts · 각 호출 완료 뒤 첫 personal.add(): personal의 addedCount=1 , work의 addedCount=0 , 클래스의 globalAddedCount=1 입니다. 둘째 personal.add(): personal은 2 , work는 0 , 공유 누계는 2 입니다. 첫 work.add(): personal은 2 , work는 1 , 공유 누계는 3 입니다. 원문 add() 는 수신자의 addedCount++ 다음에 globalAddedCount++ 를 실행합니다. 실패·취소 분기는 없으며 두 증가가 동시적이거나 스레드 안전하다는 뜻도 아닙니다. 최종 출력은 personal=2 , work=1 , global=3 입니다. |
| 관찰·상황 | 값·보충 정보 | 변화·이유 |
|---|---|---|
| 초깃값 두 Registry와 공유 누계를 0으로 둡니다. |
|
|
| 첫 personal personal.add를 호출합니다. |
|
|
| 둘째 personal 같은 객체를 다시 호출합니다. |
|
|
| 첫 work work.add를 호출합니다. |
|
|