자바의 문자열은 불변이다. String의 함수를 호출을 하면 해당 객체를 직접 수정하는 것이 아니라, 함수의 결과로 해당 객체가 아닌 다른 객체를 반환한다. 그러나 항상 그런 것은 아니다.
대문자로 문자열 생성 후 String의 toUpperCase()를 호출하면 내부 구현에서 lower case의 문자가 발견되지 않으면 기존의 객체를 반환한다.
문자열변수.interen()을 하게되면
해당 문자열과 동일한 값을 가진 문자열을 상수풀에서 찾고 있다면 해당 문자열을 바라보게 하고 없다면 새로 생성하여 반환합니다.
언제 사용될까?
만약 문자열을 == 비교연산으로 비교해야 한다면. intern() 메서드가 사용될 수 있다. 해당 메서드에서 상수풀을 찾아 같은 값을 가지고 있다면 해당 참조를 반환해주기 때문에 속도가 더 빠를 수 있지만 해당 문자열이 상수풀에 없을때는 equals보다 느릴 수 도 있다. 상황에 따라 잘 사용해야할 것 같다!
OutOfMemory : JVM에서 설정된 메모리의 한계를 벗어난 상황일 때 발생. 힙 사이즈가 부족하거나 너무 많은 class를 로드할때, 가용가능한 swap이 없을때 큰 메모리의 native 메서드가 호출될때 등이 있다. 이를 해결하기 위해 dump파일분석, jvm 옵션 수정 등이 있다.
자바는 멀티스레드 환경에서 동기화를 지원하기 위해 가장 기초적인 장치인 ’고유 락(Intrinsic Lock)’을 지원한다. 개발자는 synchronized 키워드를 이용해서 특정 객체의 고유락을 사용해 여러 스레드를 동기화 시킬 수 있다.
Synchronized 블록은 Intrinsic Lock을 이용해서, Thread의 접근을 제어한다.
Java의 synchronized
동일한 객체에 대해 synchronized 블록을 사용하는 두 스레드는 한 번에 하나의 스레드만 내부로 들어갈 수 있고, 이 것이 자바가 제공하는 가장 기본적인 ‘상호배제(Mutual Exclusion)’ 장치이다.
synchronized에는 4가지의 사용법이 있다.
synchronized method
synchronized block
static synchronized method
static synchronized block
4가지 방식의 차이인 lock이 적용되는 범위를 알아보자
1. synchronized method
synchronized method는 클래스의 인스턴스에 대하여 lock을 건다.
하나의 인스턴스에 대하여 2개의 thread가 경합하는 상황
public class Main {
public static void main(String[] args) {
A a = new A();
Thread thread1 = new Thread(()->{
a.run("t1");
});
Thread thread2 = new Thread(()->{
a.run("t2");
});
thread1.start();
thread2.start();
}
}
public class A {
public synchronized void run(String name){
System.out.println(name + "lock");
try{
Thread.sleep(1000);
} catch(InterruptedException e){
e.printStackTrace();
}
System.out.println(name + "unlock");
}
}
순서대로 lock을 획득하고 반납함
실행 결과
t1lock t1unlock t2lock t2unlock
각각의 인스턴스를 만들고 실행 해보자
public class Main {
public static void main(String[] args) {
A a = new A();
A a1 = new A();
Thread thread1 = new Thread(()->{
a.run("t1");
});
Thread thread2 = new Thread(()->{
a1.run("t2");
});
thread1.start();
thread2.start();
}
}
lock을 공유하지 않기 때문에 동기화가 발생하지 않음
실행 결과
t1lock t2lock t1unlock t2unlock
결과
synchronized method는 인스턴스에 대하여 lock을 건다.
인스턴스에 대해서 lock을 건다라는 표현이 인스턴스 접근 자체가 lock이 걸리는 걸까?? 확인해보자
public class Main {
public static void main(String[] args) throws InterruptedException {
A a = new A();
Thread thread1 = new Thread(()->{
a.run("t1");
});
Thread thread2 = new Thread(()->{
a.print("t2");
});
thread1.start();
Thread.sleep(500);
thread2.start();
}
}
public class A {
public void print(String name){
System.out.println(name + " hi");
}
public synchronized void run(String name){
System.out.println(name + "lock");
try{
Thread.sleep(1000);
} catch(InterruptedException e){
e.printStackTrace();
}
System.out.println(name + "unlock");
}
}
synchronized가 적용되지 않은 print() 메서드를 추가하고 lock이 걸린 중간에 호출 해보았더니 run() 메서드의 lock과 상관없이 정상적으로 호출됨
static이 포함된 synchronized method방식은 우리가 일반적으로 생각하는 static 성질을 갖는다. 인스턴스가 아닌 클래스 단위로 lock이 발생한다.
public class Main {
public static void main(String[] args) throws InterruptedException {
A a1 = new A();
A a2 = new A();
Thread thread1 = new Thread(()->{
a1.run("t1");
});
Thread thread2 = new Thread(()->{
a2.run("t2");
});
thread1.start();
thread2.start();
}
}
public class A {
public static synchronized void run(String name){
System.out.println(name + "lock");
try{
Thread.sleep(1000);
} catch(InterruptedException e){
e.printStackTrace();
}
System.out.println(name + "unlock");
}
}
실행 결과
t1lock t1unlock t2lock t2unlock
다른 인스턴스 이지만 클래스 단위로 lock이 발생했다.
만약 static synchronized method와 synchronized method가 섞여있다면 어떨까?
public class Main {
public static void main(String[] args) throws InterruptedException {
A a1 = new A();
A a2 = new A();
Thread thread1 = new Thread(()->{
a1.run("t1");
});
Thread thread2 = new Thread(()->{
a2.print("t2");
});
thread1.start();
thread2.start();
}
}
public class A {
public synchronized void print(String name){
System.out.println(name + " hi");
}
public static synchronized void run(String name){
System.out.println(name + "lock");
try{
Thread.sleep(1000);
} catch(InterruptedException e){
e.printStackTrace();
}
System.out.println(name + "unlock");
}
}
실행 결과
t1lock t2 hi t1unlock
인스턴스 단위의 lock과 클래스 단위의 lock은 공유되지 않았다.
static synchronized method를 정리해보면
클래스 단위로 lock을 걸지만
인스턴스 단위의 synchronized method와 lock을 공유하지 않는다.
3. synchronized block
synchronized block은 인스턴스의 block단위로 lock을 건다. 이 때, lock 객체를 지정해줘야 한다.
public class Main {
public static void main(String[] args) throws InterruptedException {
A a1 = new A();
Thread thread1 = new Thread(()->{
a1.run("t1");
});
Thread thread2 = new Thread(()->{
a1.run("t2");
});
thread1.start();
thread2.start();
}
}
public class A {
public void run(String name) {
synchronized (this) {
System.out.println(name + "lock");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name + "unlock");
}
}
}
실행 결과
t1lock t1unlock t2lock t2unlock
block의 인자로 this를 주었다.
this는 해당 인스턴스를 의미하고 위 코드에서 method 전체가 block으로 감싸져 있으므로 메서드 선언부에 synchronized 키워드를 붙인 것과 똑같이 동작한다.
하지만 여러 로직이 섞여 있는 사이 부분만 lock을 걸 수 있다. lock은 synchronized block에 진입할 때 획득하고 빠져나오면서 반납하므로 block으로 범위를 지정하는 것이 효율적이다.
synchronized block도 method와 동일하게 인스턴스에 대해서 적용된다.
지금까지는 block에 인자로 this를 사용해서 Synchronized를 메서드 선언부에 붙인 것과 별반 다를 것 없이 사용함
이제는 block에 자원을 명시하고 사용
public class Main {
public static void main(String[] args) throws InterruptedException {
A a = new A();
Thread thread1 = new Thread(() -> {
a.run("thread1");
});
Thread thread2 = new Thread(() -> {
a.run("thread2");
});
Thread thread3 = new Thread(() -> {
a.print("자원 B와 상관 없는 thread3");
});
thread1.start();
thread2.start();
thread3.start();
}
}
public class A {
B b = new B();
public void run(String name) {
synchronized (b){
System.out.println(name + " lock");
b.run();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name + " unlock");
}
}
public synchronized void print(String name) {
System.out.println(name + " hello");
}
}
public class B extends Thread{
@Override
public synchronized void run() {
System.out.println("B lock");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("B unlock");
}
}
실행 결과
thread1 lock 자원 B와 상관 없는 thread3 hello B lock B unlock thread1 unlock thread2 lock B lock B unlock thread2 unlock
매우 복잡한 상황이다. lock 객체를 A클래스가 아닌 B클래스의 인스턴스로 사용하고 있다.
thread1, thread2는 b를 사용하는 method를 호출하고 있지만
thread3은 b와 상관없는 method이기 때문에 b의 lock과 상관없이 출력되었다.
즉 인스턴스 단위 lock과 B를 block한 lock은 공유되지 않고 별도로 관리되는 것을 확인할 수 있다.
이번에는 block에 인스턴스가 아니라 class를 명시해보았다.
public class Main {
public static void main(String[] args) {
A a = new A();
Thread thread1 = new Thread(() -> {
a.run("thread1");
});
Thread thread2 = new Thread(() -> {
a.run("thread2");
});
thread1.start();
thread2.start();
}
}
public class A {
B b = new B();
public void run(String name) {
synchronized (B.class){
System.out.println(name + " lock");
b.run();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name + " unlock");
}
}
public synchronized void print(String name) {
System.out.println(name + " hello");
}
}
public class B extends Thread{
@Override
public synchronized void run() {
System.out.println("B lock");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("B unlock");
}
}
실행 결과
thread1 lock B lock B unlock thread1 unlock thread2 lock B lock B unlock thread2 unlock
block의 인자로 인스턴스가 아닌 .class를 주었다.
출력 결과를 보면 lock을 공유하고 있는 것을 확인할 수 있다.
block에는 객체를 넘기면 인스턴스 단위로 lock을 걸고, .class형식으로 넘기면 클래스 단위의 lock을 건다.
4. static synchronized block
static method안에 synchronized block을 지정할 수 있다. static의 특성 상 this같이 현재 객체를 가르키는 표현을 사용할 수 없다. static synchronized method 방식과 차이는 lock 객체를 지정하고 block으로 범위를 한정지을 수 있다는 점이다. 이외에 클래스 단위로 lock을 공유한다는 점은 같다.
public class Main {
public static void main(String[] args) {
A a1 = new A();
A a2 = new A();
Thread thread1 = new Thread(() -> {
a1.run("thread1");
});
Thread thread2 = new Thread(() -> {
a2.run("thread2");
});
thread1.start();
thread2.start();
}
}
public class A {
public static void run(String name) {
synchronized (A.class){
System.out.println(name + " lock");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name + " unlock");
}
}
}