스레드 동기화 제어를 위해 Java에서 동기화된 키워드를 사용하면 주요 리소스에 대한 순차적 액세스를 달성하고 여러 스레드의 동시 실행으로 인해 발생하는 데이터 불일치와 같은 문제를 피할 수 있습니다. 동기화의 원리는 개체 모니터(잠금)입니다. 모니터를 획득한 스레드만 계속 실행할 수 있습니다. 그렇지 않으면 스레드는 모니터를 획득하기 위해 대기합니다. Java의 각 객체 또는 클래스에는 연관된 잠금이 있습니다. 객체의 경우 모니터링되는 것은 객체의 인스턴스 변수입니다(클래스 자체는 Class의 객체입니다). 따라서 클래스와 연관된 잠금도 객체 잠금입니다. 동기화 키워드를 사용하는 방법에는 동기화 방법과 동기화 블록의 두 가지가 있습니다. 두 모니터링 영역은 모두 도입된 개체와 연결되어 있습니다. 이 모니터링 영역에 도착하면 JVM은 참조 개체를 잠그고 나갈 때 참조 개체에 대한 잠금을 해제합니다(JVM은 예외 종료가 있을 때 잠금을 해제합니다). . 객체 잠금은 JVM의 내부 메커니즘으로, 모니터링 영역을 운영할 때 동기화 방법이나 블록만 작성하면 JVM이 자동으로 잠금을 획득하거나 해제합니다.
예제 1
먼저 첫 번째 예를 살펴보겠습니다. Java에서는 동일한 객체의 하나의 중요한 섹션에만 동시에 액세스할 수 있습니다(모두 비정적 동기화 방법입니다). ):
package concurrency; public class Main8 { public static void main(String[] args) { Account account = new Account(); account.setBalance(1000); Company company = new Company(account); Thread companyThread = new Thread(company); Bank bank = new Bank(account); Thread bankThread = new Thread(bank); System.out.printf("Account : Initial Balance: %f\n", account.getBalance()); companyThread.start(); bankThread.start(); try { //join()方法等待这两个线程运行完成 companyThread.join(); bankThread.join(); System.out.printf("Account : Final Balance: %f\n", account.getBalance()); } catch (InterruptedException e) { e.printStackTrace(); } } }
/*帐户*/ class Account{ private double balance; /*将传入的数据加到余额balance中*/ public synchronized void addAmount(double amount){ double tmp = balance; try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } tmp += amount; balance = tmp; } /*将传入的数据从余额balance中扣除*/ public synchronized void subtractAmount(double amount){ double tmp = balance; try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } tmp -= amount; balance = tmp; } public double getBalance() { return balance; } public void setBalance(double balance) { this.balance = balance; } }
/*银行*/ class Bank implements Runnable{ private Account account; public Bank(Account account){ this.account = account; } @Override public void run() { for (int i = 0; i < 100; i++) { account.subtractAmount(1000); } } }
/*公司*/ class Company implements Runnable{ private Account account; public Company(Account account){ this.account = account; } @Override public void run() { for (int i = 0; i < 100; i++) { account.addAmount(1000); } } }
잔액을 충전하고 공제할 수 있는 시뮬레이션 은행 계좌 애플리케이션을 개발하셨습니다. 이 프로그램은 addAmount() 메서드를 100번 호출하여 매번 1000을 입금한 다음, subtractAmount() 메서드를 100번 호출하여 계정 잔액을 공제하고 매번 계정의 최종 잔액을 공제합니다. 초기 잔액과 동일 동기화 키워드를 통해 평등이 달성됩니다.
공유 데이터의 동시접속 문제를 확인하고 싶다면 addAmount(), subtractAmount() 메소드 선언부에서synchronous 키워드만 삭제하면 된다. 동기화된 키워드가 없으면 인쇄된 잔액 값이 일치하지 않습니다. 이 프로그램을 여러 번 실행하면 다른 결과가 나타납니다. JVM은 스레드의 실행 순서를 보장하지 않기 때문에 스레드는 실행될 때마다 다른 순서로 계정 잔액을 읽고 수정하므로 최종 결과가 달라집니다.
객체의 메소드는 동기화 키워드를 사용하여 선언되며 하나의 스레드에서만 액세스할 수 있습니다. 스레드 A가 동기화 메서드 syncMethodA()를 실행하고 스레드 B가 이 객체의 다른 동기화 메서드 syncMethodB()를 실행하려는 경우 스레드 B는 스레드 A가 액세스를 완료할 때까지 차단됩니다. 그러나 스레드 B가 동일한 클래스의 다른 개체에 액세스하면 두 스레드 모두 차단되지 않습니다.
예제 2
동일한 객체에 대한 정적 동기화 메서드와 비정적 동기화 메서드가 동일한 시점에 여러 스레드에서 액세스할 수 있는 문제를 확인합니다.
package concurrency; public class Main8 { public static void main(String[] args) { Account account = new Account(); account.setBalance(1000); Company company = new Company(account); Thread companyThread = new Thread(company); Bank bank = new Bank(account); Thread bankThread = new Thread(bank); System.out.printf("Account : Initial Balance: %f\n", account.getBalance()); companyThread.start(); bankThread.start(); try { //join()方法等待这两个线程运行完成 companyThread.join(); bankThread.join(); System.out.printf("Account : Final Balance: %f\n", account.getBalance()); } catch (InterruptedException e) { e.printStackTrace(); } } }
/*帐户*/ class Account{ /*这里改为静态变量*/ private static double balance = 0; /*将传入的数据加到余额balance中,注意是用static修饰过的*/ public static synchronized void addAmount(double amount){ double tmp = balance; try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } tmp += amount; balance = tmp; } /*将传入的数据从余额balance中扣除*/ public synchronized void subtractAmount(double amount){ double tmp = balance; try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } tmp -= amount; balance = tmp; } public double getBalance() { return balance; } public void setBalance(double balance) { this.balance = balance; } }
/*银行*/ class Bank implements Runnable{ private Account account; public Bank(Account account){ this.account = account; } @Override public void run() { for (int i = 0; i < 100; i++) { account.subtractAmount(1000); } } }
/*公司*/ class Company implements Runnable{ private Account account; public Company(Account account){ this.account = account; } @Override public void run() { for (int i = 0; i < 100; i++) { account.addAmount(1000); } } }
이전 예제에서는 static 키워드를 추가하여 잔액을 수정했습니다. addAmount() 메서드도 static 키워드로 수정할 수 있습니다. 실행 결과를 직접 테스트할 수 있습니다. 각 실행 결과는 다릅니다.
요약:
synchronized는 소프트웨어(JVM)를 통해 구현되며 간단하고 사용하기 쉽습니다. JDK5 이후의 Lock에서도 여전히 널리 사용되고 있습니다.
동기화는 사실 불공평합니다. 새로운 스레드는 즉시 모니터를 얻을 수도 있고, 오랫동안 대기 중인 스레드는 다시 기다릴 수도 있습니다. 하지만 이 선점 방식을 사용하면 기아 상태를 방지할 수 있습니다.
동기화된 잠금은 하나의 조건(잠금 획득 여부)에만 연결되며 유연성이 없으므로 나중에 조건과 잠금을 조합하여 이 문제를 해결했습니다.
여러 스레드가 잠금을 놓고 경쟁할 때 잠금을 획득하지 못한 나머지 스레드는 중단 없이 계속 잠금을 획득하려고 시도할 수 있습니다. 동시성이 높으면 성능 저하가 발생합니다. ReentrantLock의 lockInterruptible() 메서드는 인터럽트에 대한 응답 우선 순위를 지정할 수 있습니다. 스레드가 너무 오래 기다리면 자체적으로 인터럽트가 발생할 수 있으며 ReentrantLock은 인터럽트에 응답하여 더 이상 스레드가 계속 기다리지 못하게 합니다. 이 메커니즘을 사용하면 ReentrantLock을 사용할 때 동기화와 같은 교착 상태가 발생하지 않습니다.
스레드 동기화를 위해 동기화된 수정 방법을 사용하는 Java의 더 많은 예제와 데모를 보려면 PHP 중국어 웹사이트에 주목하세요!