다섯 가지 Java 멀티스레드 동기화 방법

왜 스레드 동기화
변수나 대상에 동시에 접근해야 할 여러 개의 라인이 있을 때, 이 라인에 읽기와 쓰기 작업이 있을 때, 변수 값이나 대상의 상태에 혼란을 초래하여 프로그램 이상을 초래할 수 있기 때문이다.예를 들어 만약에 한 은행 계좌가 동시에 두 개의 라인에 의해 조작된다면 하나는 100위안을 찾고, 하나는 100위안을 저금한다.가령 계좌에 원래 0위안이 있었다고 가정하면 인출 라인과 예금 라인이 동시에 발생하면 어떤 결과가 나올까요?돈을 찾는 데 성공하지 못하면 계좌 잔액은 100이다.돈을 찾는 데 성공했습니다. 계좌 잔액은 0입니다.그럼 도대체 어떤 거예요?분명하게 말하기 어렵다.따라서 다중 스레드 동기화는 이 문제를 해결하는 것이다.
1. 동기화되지 않을 때의 코드

Bank.java 
 
package threadTest; 
 
/** 
* @author ww 
* 
*/ 
public class Bank { 
 
 private int count =0;//  
 
 //  
 public void addMoney(int money){ 
  count +=money; 
  System.out.println(System.currentTimeMillis()+" :"+money); 
 } 
 
 //  
 public void subMoney(int money){ 
  if(count-money < 0){ 
   System.out.println(" "); 
   return; 
  } 
  count -=money; 
  System.out.println(+System.currentTimeMillis()+" :"+money); 
 } 
 
 //  
 public void lookMoney(){ 
  System.out.println(" :"+count); 
 } 
} 
 
SyncThreadTest.java 
 
package threadTest; 
 
public class SyncThreadTest { 
 
 public static void main(String args[]){ 
  final Bank bank=new Bank(); 
 
  Thread tadd=new Thread(new Runnable() { 
 
   @Override 
   public void run() { 
    // TODO Auto-generated method stub 
    while(true){ 
     try { 
      Thread.sleep(1000); 
     } catch (InterruptedException e) { 
      // TODO Auto-generated catch block 
      e.printStackTrace(); 
     } 
     bank.addMoney(100); 
     bank.lookMoney(); 
     System.out.println("
"); } } }); Thread tsub = new Thread(new Runnable() { @Override public void run() { // TODO Auto-generated method stub while(true){ bank.subMoney(100); bank.lookMoney(); System.out.println("
"); try { Thread.sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }); tsub.start(); tadd.start(); } }
코드가 매우 간단해서 나는 설명하지 않겠다. 운행 결과가 어떠한지 보자.그중의 일부를 캡처했는데 너무 어지럽지 않아서 글씨가 있어서 알아볼 수가 없어요.
잔액 부족
계좌 잔액: 0
잔액 부족
계좌 잔액: 100
1441790503354 저장: 100
계좌 잔액: 100
1441790504354 저장: 100
계좌 잔액: 100
1441790504354 체크 아웃: 100
계좌 잔액: 100
1441790505355 저장: 100
계좌 잔액: 100
144179055355 추출: 100
계좌 잔액: 100
2. 동기화 시 코드 사용
(1) 동기화 방법:
synchronized 키워드를 수식하는 방법이 있습니다.자바의 모든 대상은 내장 자물쇠가 있기 때문에 이 키워드로 수식할 때 내장 자물쇠는 전체 방법을 보호합니다.이 방법을 사용하기 전에 내장 자물쇠를 얻어야 합니다. 그렇지 않으면 막힌 상태입니다.
수정된 Bank.java
실행 결과를 다시 확인하십시오.
잔액 부족
계좌 잔액: 0
잔액 부족
계좌 잔액: 0
1441790837380 저장: 100
계좌 잔액: 100
1441790838380 체크 아웃: 100
계좌 잔액: 0
1441790838380 저장: 100
계좌 잔액: 100
1441790839381 추출: 100
계좌 잔액: 0
순간 이해할 수 있을 것 같죠?
주:synchronized 키워드도 정적 방법을 수식할 수 있습니다. 이 정적 방법을 사용하면 전체 클래스를 잠글 수 있습니다.
(2) 코드 블록 동기화
synchronized 키워드로 수식된 문장 블록이 있습니다.이 키워드로 장식된 문장 블록은 자동으로 내장 자물쇠를 추가하여 동기화를 실현한다
Bank.java 코드는 다음과 같습니다.

package threadTest; 
 
/** 
* @author ww 
* 
*/ 
public class Bank { 
 
 private int count =0;//  
 
 //  
 public void addMoney(int money){ 
 
  synchronized (this) { 
   count +=money; 
  } 
  System.out.println(System.currentTimeMillis()+" :"+money); 
 } 
 
 //  
 public void subMoney(int money){ 
 
  synchronized (this) { 
   if(count-money < 0){ 
    System.out.println(" "); 
    return; 
   } 
   count -=money; 
  } 
  System.out.println(+System.currentTimeMillis()+" :"+money); 
 } 
 
 //  
 public void lookMoney(){ 
  System.out.println(" :"+count); 
 } 
} 
실행 결과는 다음과 같습니다.
잔액 부족
계좌 잔액: 0
1441791806699 저장: 100
계좌 잔액: 100
1441791806700 체크 아웃: 100
계좌 잔액: 0
1441791807699 저장: 100
계좌 잔액: 100
효과와 방법은 차이가 많지 않다.
주: 동기화는 비용이 많이 드는 작업이기 때문에 동기화의 내용을 최대한 줄여야 한다.일반적으로 전체 방법을 동기화할 필요가 없습니다.synchronized 코드 블록을 사용하여 관건적인 코드를 동기화하면 됩니다.
(3) 특수 도메인 변수(Volatile)를 사용하여 스레드 동기화
a.volatile 키워드는 영역 변수에 대한 접근에 잠금 해제 메커니즘을 제공합니다
b. volatile 수식 필드를 사용하는 것은 가상 컴퓨터에 이 필드가 다른 루트로 업데이트될 수 있음을 알려주는 것과 같다
c. 따라서 이 영역을 사용할 때마다 레지스터의 값을 사용하지 않고 다시 계산해야 한다
d.volatile는 어떠한 원자 조작도 제공하지 않으며,final 형식의 변수를 수식할 수도 없습니다
Bank.java 코드는 다음과 같습니다.

package threadTest; 
 
/** 
* @author ww 
* 
*/ 
public class Bank { 
 
 private volatile int count = 0;//   
 
 //   
 public void addMoney(int money) { 
 
  count += money; 
  System.out.println(System.currentTimeMillis() + " :" + money); 
 } 
 
 //   
 public void subMoney(int money) { 
 
  if (count - money < 0) { 
   System.out.println(" "); 
   return; 
  } 
  count -= money; 
  System.out.println(+System.currentTimeMillis() + " :" + money); 
 } 
 
 //   
 public void lookMoney() { 
  System.out.println(" :" + count); 
 } 
} 
운행 효과는 어떻습니까?
잔액 부족
계좌 잔액: 0
잔액 부족
계좌 잔액: 100
1441792010959 저장: 100
계좌 잔액: 100
144179201960 체크 아웃: 100
계좌 잔액: 0
144179201961 저장: 100
계좌 잔액: 100
또 못 알아보고 난리가 난 거 아니야?왜 이러지?바로volatile가 원자 조작을 보장할 수 없기 때문에,volatile가synchronized를 대체할 수 없습니다.또한volatile는 컴파일러를 조직하여 코드에 최적화시키기 때문에 사용하지 않으면 적용되지 않습니다.그것의 원리는volatile 장식된 변수에 접근할 때마다 메모리에서 읽는 것이지 캐시에서 읽는 것이 아니기 때문에 모든 루트가 접근하는 변수 값은 같다.이렇게 하면 동기화를 보장할 수 있다.
(4) 재입력 자물쇠를 사용하여 스레드 동기화
JavaSE5.0에 java가 추가되었습니다.util.동기화를 지원하기 위해 concurrent 패키지입니다.ReentrantLock 클래스는 Lock 인터페이스의 자물쇠를 다시 입력하고 서로 밀어낼 수 있으며 synchronized 방법을 사용하는 것과 같은 기본적인 행위와 의미를 가지며 그 능력을 확장합니다.
ReenreantLock 클래스의 일반적인 방법은 다음과 같습니다.
ReentrantLock(): ReentrantLock 인스턴스 만들기
lock(): 잠금 획득
unlock(): 잠금 해제
참고: ReentrantLock () 은 공평한 자물쇠를 만들 수 있는 구조 방법이 하나 더 있지만, 프로그램 실행 효율을 대폭 낮출 수 있기 때문에 사용을 추천하지 않습니다.
Bank.java 코드 수정은 다음과 같습니다.

package threadTest; 
 
import java.util.concurrent.locks.Lock; 
import java.util.concurrent.locks.ReentrantLock; 
 
/** 
* @author ww 
* 
*/ 
public class Bank { 
 
 private int count = 0;//   
 
 //  
 private Lock lock = new ReentrantLock(); 
 
 //   
 public void addMoney(int money) { 
  lock.lock();//  
  try{ 
  count += money; 
  System.out.println(System.currentTimeMillis() + " :" + money); 
 
  }finally{ 
   lock.unlock();//  
  } 
 } 
 
 //   
 public void subMoney(int money) { 
  lock.lock(); 
  try{ 
 
  if (count - money < 0) { 
   System.out.println(" "); 
   return; 
  } 
  count -= money; 
  System.out.println(+System.currentTimeMillis() + " :" + money); 
  }finally{ 
   lock.unlock(); 
  } 
 } 
 
 //   
 public void lookMoney() { 
  System.out.println(" :" + count); 
 } 
} 
운행 효과는 어떻습니까?
잔액 부족
계좌 잔액: 0
잔액 부족
계좌 잔액: 0
1441792891934 저장: 100
계좌 잔액: 100
1441792892935 저장: 100
계좌 잔액: 200
1441792892954 추출: 100
계좌 잔액: 100
효과는 앞의 두 가지 방법과 차이가 많지 않다.
만약 synchronized 키워드가 사용자의 요구를 충족시킬 수 있다면, 코드를 간소화할 수 있기 때문에synchronized를 사용하십시오.더 높은 기능이 필요하면 ReentrantLock 클래스를 사용하십시오. 이때 자물쇠를 제때에 풀어야 합니다. 그렇지 않으면 자물쇠가 사라집니다. 보통finally 코드에서 자물쇠를 풀어줍니다.
(5) 국부 변수를 사용하여 스레드 동기화
Bank.java 코드는 다음과 같습니다.

package threadTest; 
 
/** 
* @author ww 
* 
*/ 
public class Bank { 
 
 private static ThreadLocal<Integer> count = new ThreadLocal<Integer>(){ 
 
  @Override 
  protected Integer initialValue() { 
   // TODO Auto-generated method stub 
   return 0; 
  } 
 
 }; 
 
 //   
 public void addMoney(int money) { 
  count.set(count.get()+money); 
  System.out.println(System.currentTimeMillis() + " :" + money); 
 
 } 
 
 //   
 public void subMoney(int money) { 
  if (count.get() - money < 0) { 
   System.out.println(" "); 
   return; 
  } 
  count.set(count.get()- money); 
  System.out.println(+System.currentTimeMillis() + " :" + money); 
 } 
 
 //   
 public void lookMoney() { 
  System.out.println(" :" + count.get()); 
 } 
} 
실행 효과:
잔액 부족
계좌 잔액: 0
잔액 부족
계좌 잔액: 0
1441794247939 저장: 100
계좌 잔액: 100
잔액 부족
1441794248940 저장: 100
계좌 잔액: 0
계좌 잔액: 200
잔액 부족
계좌 잔액: 0
1441794249941 저장: 100
계좌 잔액: 300
운행 효과를 보니 처음에는 안개가 자욱했는데 왜 저장만 하고 찾지 못하게 합니까?ThreadLocal의 원리를 살펴보십시오.
ThreadLocal 관리 변수를 사용하면 이 변수를 사용하는 모든 스레드가 이 변수의 복사본을 얻고 복사본 간에 서로 독립적이어서 모든 스레드가 다른 스레드에 영향을 주지 않고 자신의 변수 복사본을 마음대로 수정할 수 있습니다.이제 알겠지. 원래 모든 라인이 실행되는 것은 하나의 사본이었다. 즉, 예금과 인출은 두 개의 계좌이고 지식 이름은 같을 뿐이야.그래서 위의 효과가 발생한다.
ThreadLocal 및 동기화 메커니즘
a.ThreadLocal과 동기화 메커니즘은 다중 스레드에서 같은 변수의 접근 충돌 문제를 해결하기 위한 것이다
b. 전자는'공간으로 시간을 바꾼다'는 방법을 사용하고 후자는'시간으로 공간을 바꾼다'는 방식을 사용한다.
이제 알겠지?각각의 우열이 있고 각각의 적용 장면이 있기 때문에 본고는 여러분에게 자바 다중 스레드 동기화를 더욱 깊이 있게 이해하는 데 도움이 될 수 있기를 바랍니다.

좋은 웹페이지 즐겨찾기