100명의 스레드로 각각 100번 조회했을 때
public class CountingTest {
public static void main(String[] args) {
Count count = new Count();
for (int i = 0; i < 100; i++) {
new Thread(){
public void run(){
for (int j = 0; j < 100; j++) {
System.out.println(count.view());
}
}
}.start();
}
}
}
class Count {
private int count;
public int view() {return count++;}
public int getCount() {return count;}
}
결과 : 10000번이 아닌, 더 적은 조회수가 나옴
이유 : 조회수를 증가시키는 로직이 두 번의 동작으로 이루어지는데 동시에 여러 스레드가 접근하여 첫 번째 동작할 때의 자원과 두 번째 동작할 때의 자원
상태가 변하기 때문
2.Count
클래스의 view 메서드는 count++이라는 동작을 진행한다.
count 변수의 값을 조회한다.
조회한 count변수 값에 1을 더한 값을 저장한다.
: Lock을 건다.
lock은 메서드, 변수에 각각 걸 수 있다.
1) 메서드 Lock
class Count {
private int count;
public synchronized int view() {return count++;}
}
2) 변수 Lock
class Count {
private Integer count = 0;
public int view() {
synchronized (this.count) {
return count++;
}
}
}
2.명시적 Lock : ReentrantLock
: synchronized 키워드 없이 명시적으로 ReentrantLock을 사용하는 Lock이다.
명시적 Lock을 사용한 예제
public class CountingTest {
public static void main(String[] args) {
Count count = new Count();
for (int i = 0; i < 100; i++) {
new Thread(){
public void run(){
for (int j = 0; j < 1000; j++) {
count.getLock().lock();
System.out.println(count.view());
count.getLock().unlock();
}
}
}.start();
}
}
}
class Count {
private int count = 0;
private Lock lock = new ReentrantLock();
public int view() {
return count++;
}
public Lock getLock(){
return lock;
};
}
1.하나의 객체에 여러개의 스레드가 접근해서 처리하고자 할때
2.static으로 선언한 객체에 여러 스레드가 동시에 사용할때
그러므로 새롭게 생성할 수 없는 Static변수나 기존 변수에 동시성을 제어하기 위해선 synchoronized 키워드를 붙여서 사용한다.
**자원의 가시성:
SharedObject를 공유하는 두 개의 Thread가 있다
public class SharedObject {
public int counter = 0;
}
Thread-1은 counter값을 증가시킨다. 하지만 CPU Cache에만 반영, 실제로 Main Memory에는 반영X → Thread-2는 count값으로 0을 가져오는 문제 발생
public class SharedObject {
public volatile int counter = 0;
}
람다식
public class AtomicIntExample {
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(2);
AtomicInteger atomicInt = new AtomicInteger();
for(int i = 0; i < 10; i++){
executor.submit(()->System.out.println("Counter- " + atomicInt.incrementAndGet())); // 1
}
executor.shutdown();
}
}
public class AtomicIntExample {
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(2);
AtomicInteger atomicInt = new AtomicInteger();
CounterRunnable runnableTask = new CounterRunnable(atomicInt);
for(int i = 0; i < 10; i++){
executor.submit(runnableTask);
}
executor.shutdown();
}
}
class CounterRunnable implements Runnable{
AtomicInteger atomicInt;
CounterRunnable(AtomicInteger atomicInt){
this.atomicInt = atomicInt;
}
@Override
public void run() {
System.out.println("Counter- " + atomicInt.incrementAndGet());
}
}
결과 :
Counter- 1
Counter- 2
Counter- 3
Counter- 4
Counter- 5
Counter- 6
Counter- 7
Counter- 8
Counter- 9
Counter- 10
AtomicInterger를 Decompile 하여 요약한 내용
public class AtomicInteger extends Number implements java.io.Serializable {
private volatile int value;
public final int incrementAndGet() {
int current;
int next;
do {
current = get();
next = current + 1;
} while (!compareAndSet(current, next));
return next;
}
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
}
synchronized
volatile
AtomicIntger
참조 :
https://dzone.com/articles/java-patterns-for-concurrency
https://deveric.tistory.com/104
https://javaplant.tistory.com/23