0. 주요 질문 코드
다음 코드는 카운터를 보여 주며 두 스레드가 동시에 i에 대해 누적 작업을 수행하며 각각 1,000,000번 실행됩니다. 우리가 예상하는 결과는 확실히 i=2000000입니다. 하지만 여러 번 실행하면 i 값이 항상 2000000보다 작다는 것을 알 수 있습니다. 이는 두 스레드가 동시에 i에 쓸 때 한 스레드의 결과가 다른 스레드를 덮어쓰게 되기 때문입니다.
public class AccountingSync implements Runnable { static int i = 0; public void increase() { i++; } @Override public void run() { for (int j = 0; j < 1000000; j++) { increase(); } } public static void main(String[] args) throws InterruptedException { AccountingSync accountingSync = new AccountingSync(); Thread t1 = new Thread(accountingSync); Thread t2 = new Thread(accountingSync); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(i); } }
이 문제를 근본적으로 해결하려면 i에서 작업할 때 여러 스레드가 완전히 동기화되도록 해야 합니다. 즉, 스레드 A는 동기화할 때 i에 씁니다. 한 번에 하나의 스레드만 동기화 블록에 들어갈 수 있도록 동기화된 코드를 잠가서 스레드 간의 보안을 보장하는 것입니다. 위 코드와 마찬가지로 i++ 작업은 동시에만 수행할 수 있습니다.
2. 동기화 키워드 사용법
객체 잠금 지정: 주어진 객체를 잠그고, 동기화된 코드 블록을 입력하여 주어진 객체를 얻습니다. 잠금
다음 코드는 동기화에 적용됩니다. 여기서 주목해야 할 점은 주어진 객체가 정적이어야 한다는 것입니다. 그렇지 않으면 새 스레드를 생성할 때마다 객체가 서로 공유되지 않으며 잠금의 의미도 다릅니다. 더 오래 존재합니다.
public class AccountingSync implements Runnable { final static Object OBJECT = new Object(); static int i = 0; public void increase() { i++; } @Override public void run() { for (int j = 0; j < 1000000; j++) { synchronized (OBJECT) { increase(); } } } public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(new AccountingSync()); Thread t2 = new Thread(new AccountingSync()); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(i); } }2.2 인스턴스 메소드에 직접 적용
동기화된 키워드는 인스턴스 메소드에 작용합니다. 즉, 스레드는 증가() 메소드를 시작하기 전에 현재 인스턴스의 잠금을 획득해야 합니다. 이를 위해서는 Thread 인스턴스를 생성할 때 동일한 Runnable 객체 인스턴스를 사용해야 합니다. 스레드 잠금이 동일한 인스턴스에 있지 않으며 잠금/동기화 문제에 대해 이야기할 방법이 없습니다.
인스턴스 메서드의 올바른 키워드 사용법을 설명하는 세 줄입니다.
public class AccountingSync implements Runnable { static int i = 0; public synchronized void increase() { i++; } @Override public void run() { for (int j = 0; j < 1000000; j++) { increase(); } } public static void main(String[] args) throws InterruptedException { AccountingSync accountingSync = new AccountingSync(); Thread t1 = new Thread(accountingSync); Thread t2 = new Thread(accountingSync); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(i); } }
정적 메서드에 동기화 키워드를 사용하므로 위의 작업을 수행할 필요가 없습니다. 예에서 두 스레드는 동일한 Runnable 메서드를 가리켜야 하기 때문입니다. 메소드 블록은 현재 인스턴스가 아닌 현재 클래스의 잠금을 요청해야 합니다.
3. 잠금
위의 예에서 우리는 카운터 애플리케이션이 필요한 경우 데이터의 정확성을 보장하기 위해 당연히 카운터가 잠겨 있어야 한다는 것을 알고 있으므로 다음 코드를 작성할 수 있습니다.
public class AccountingSync implements Runnable { static int i = 0; public static synchronized void increase() { i++; } @Override public void run() { for (int j = 0; j < 1000000; j++) { increase(); } } public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(new AccountingSync()); Thread t2 = new Thread(new AccountingSync()); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(i); } }
위의 코드를 실행하면 i 출력이 매우 작음을 알 수 있습니다. 이는 스레드가 안전하지 않음을 나타냅니다.
이 문제를 설명하려면 Integer부터 시작해야 합니다. Java에서 Integer는 문자열과 마찬가지로 일단 생성되면 수정할 수 없습니다. Integer=1이면 수정이 불가능합니다. 항상 1이어야 합니다. 이 객체를 2로 설정하려면 Integer만 다시 생성할 수 있습니다. 각 i++ 이후에는 Integer의 valueOf 메서드를 호출하는 것과 같습니다. Integer의 valueOf 메서드 소스 코드를 살펴보겠습니다.public class BadLockOnInteger implements Runnable { static Integer i = 0; @Override public void run() { for (int j = 0; j < 1000000; j++) { synchronized (i) { i++; } } } public static void main(String[] args) throws InterruptedException { BadLockOnInteger badLockOnInteger = new BadLockOnInteger(); Thread t1 = new Thread(badLockOnInteger); Thread t2 = new Thread(badLockOnInteger); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(i); } }Integer.valueOf()는 실제로 팩토리 메서드이며 새 Integer 객체를 반환하고 해당 값을 i에 다시 복사하는 경향이 있습니다.
따라서 우리는 문제의 원인을 알고 있습니다. 왜냐하면 여러 스레드 사이에서 i++ 이후에는 스레드가 잠길 때마다 다른 개체 인스턴스를 로드할 수 있기 때문입니다. 해결 방법은 매우 간단합니다.
public static Integer valueOf(int i) { if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i); }
Java 동기화 키워드 사용과 관련된 더 많은 기사를 보려면 PHP 중국어 웹사이트를 참고하세요. !