1. 안전하지 않은 클래스 소스 코드 분석
JDK의 Rt.jar 패키지의 안전하지 않은 클래스는 하드웨어 수준 원자 작업을 제공합니다. 안전하지 않은 방법은 모두 기본 방법이며 로컬 C ++ 구현 라이브러리는 JNI를 사용하여 액세스합니다.
Rt.jar에서 안전하지 않은 클래스의 주요 기능에 대한 설명. 안전하지 않은 클래스는 하드웨어 수준의 원자 작업을 제공하며 메모리 변수를 직접 안전하게 작동시킬 수 있습니다. JUC 소스 코드에서 널리 사용됩니다. 그 원칙을 이해하면 JUC 소스 코드를 연구하기위한 기초가됩니다.
먼저, 안전하지 않은 클래스에서 주요 방법의 사용을 다음과 같이 이해해 봅시다.
1. Long ObjectfieldOffset (필드 필드) 메소드 : 지정된 변수의 메모리 오프셋 주소를 반환합니다. 오프셋 주소는 안전하지 않은 함수에서 지정된 필드에 액세스 할 때만 사용됩니다. 다음 코드는 안전하지 않으면 Atomiclong 객체에서 Atomiclong에서 가변 값의 메모리 오프셋을 얻습니다. 코드는 다음과 같습니다.
static {try {valueOffset = unsafe.objectfieldoffset (atomiclong.class.getDeclaredfield ( "value")); } catch (Exception Ex) {Throw New Error (예 :); }}2. arrayBaseOffSet (클래스 ArrayClass) 메소드 : 배열에서 첫 번째 요소의 주소를 가져옵니다.
3. arrayindexScale (클래스 ArrayClass) 메소드 : 배열에서 단일 요소가 차지하는 바이트 수를 가져옵니다.
3.Boolean CompareAndswaplong (Object OBJ, Long Offset, Long Expect, Long Update) 메소드 : OBJ의 오프셋 오프셋 변수의 값이 기대와 동일인지 비교하십시오. 동일하면 업데이트 값으로 업데이트 된 다음 true를 반환하면 False를 반환합니다.
4. 공개 기본 Long GetLongVolative (Object OBJ, Long Offset) 메소드 : Object OBJ의 오프셋 오프셋 변수에 해당하는 휘발성 메모리 의미의 값을 얻습니다.
5. void putorderedLong (Object OBJ, Long Offset, Long value) 메소드 : OBJ 오브젝트의 오프셋 오프셋 주소에 해당하는 긴 필드의 값을 값으로 설정합니다. 이것은 지연이있는 putlongvolatile 방법이며, 값 수정이 다른 스레드에 즉시 볼 수 있다고 보장하지 않습니다. 변수는 휘발성으로 수정되어 예기치 않게 수정 될 것으로 예상되는 경우에만 유용합니다.
6. 공원 공원 (부울 Isabsolute, 오랜 시간) 방법 : 현재 스레드를 차단하십시오. 파라미터 isabsolute가 false와 같을 때, 시간은 0과 동일하면 항상 차단하는 것을 의미합니다. 0보다 큰 시간은 지정된 시간을 기다린 후 차단 스레드가 깨어날 것임을 의미합니다. 이번에는 상대적 값, 증분 값, 즉 현재 스레드가 현재 시간에 비해 시간을 축적 한 후에 깨어날 것입니다. ISABSOLUTE가 True와 같고 시간이 0보다 크면 지정된 시점으로 차단 한 후에 깨어납니다. 여기서 시간은 절대 시간이며, 이는 특정 시점에서 MS로 변환되는 값입니다. 또한 다른 스레드가 현재 차단 스레드의 인터럽트 메소드를 호출하고 현재 스레드를 방해하면 현재 스레드도 반환됩니다. 다른 스레드가 파크 메소드를 호출하고 현재 스레드를 매개 변수로 취하면 현재 스레드도 반환됩니다.
7. void unpark (객체 스레드) 방법 : 공원을 호출 한 후 차단 스레드를 깨우면 매개 변수는 깨어나야 할 스레드입니다.
JDK1.8에 몇 가지 새로운 방법이 추가되었습니다. 다음은 다음과 같이 긴 유형을 작동하는 방법의 간단한 목록입니다.
8. long getAndsetLong (Object OBJ, Long Offset, Long Update) 메소드 : Object OBJ에서 오프셋으로 변수 휘발성 시맨틱의 값을 얻고 변수 휘발성 시맨틱의 값을 업데이트하도록 설정합니다. 사용법은 다음과 같습니다.
public final long getandsetlong (객체 obj, 긴 오프셋, 긴 업데이트) {long l; {l = getLongVolatile (obj, 오프셋); // (1)} while (! compareandswaplong (obj, offset, l, update)); 리턴 l; }내부 코드 (1)에서 getLongVolative를 사용하여 현재 변수의 값을 얻은 다음 CAS 원자력 작업을 사용하여 새 값을 설정할 수 있습니다. 여기서는 루프를 사용하면 여러 스레드가 동시에 호출되는 상황을 고려한 다음 CAS가 실패한 후 스핀 레트로가 필요합니다.
9. Long getAndaddlong (Object OBJ, Long Offset, Long AddValue) 메소드 : Object OBJ에서 오프셋으로 변수 휘발성의 의미 값을 얻고 원래 값 + addValue로 변수 값을 설정합니다. 사용법은 다음과 같습니다.
공개 최종 Long GetAndAddlong (Object OBJ, Long Offset, Long AddValue) {Long L; do {l = getLongVolatile (obj, 오프셋); } while (! compareAndswaplong (obj, 오프셋, l, l + addValue)); 리턴 l; }여기에서 CAS를 사용할 때 원래 값 + 전송 된 증분 매개 변수 addValue의 값이 사용된다는 점을 제외하고는 getAndSetLong의 구현과 유사합니다.
그렇다면 안전하지 않은 클래스를 사용하는 방법은 무엇입니까?
안전하지 않다는 것을보고 정말 대단합니다. 정말로 연습하고 싶습니까? 자, 다음 코드를 먼저 살펴 보겠습니다.
com.hjc; import sun.misc.unsafe;/*** Package 2018/6/6에 Cong에 의해 생성되었습니다. */public class testunsafe {// 안전하지 않은 인스턴스를 얻습니다 (2.2.1) 정적 최종 최종 안전하지 않은 안전하지 않은 = unsafe.getunsafe (); // 클래스에서 변수 상태의 오프셋 값을 레코드 testunsafe (2.2.2) 정적 최종 긴 StateOffset; // 변수 (2.2.3) 개인 휘발성 긴 상태 = 0; static {try {// 클래스 testunsafe (2.2.4)에서 상태 변수의 오프셋 값을 가져옵니다 (2.2.4) stateOffset = unsafe.objectfieldoffset (testunsafe.class.getDeclaredfield ( "state")); } catch (예외) {System.out.println (예 : getLocalizedMessage ()); 새로운 오류를 던지십시오 (예 :); }} public static void main (String [] args) {// 인스턴스를 생성하고 상태 값을 1 (2.2.5) testunsafe test = new testunsafe ()로 설정합니다. //(2. System.out.println (sucess); }}코드 (2.2.1)는 안전하지 않은 인스턴스를 가져오고 코드 (2.2.3)는 초기화 된 상태를 0으로 만듭니다.
Code (2.2.4)는 unsafe.objectfieldoffset을 사용하여 testunsafe 객체의 testunsafe 클래스에서 상태 변수의 메모리 오프셋 주소를 얻고 stateOffset 변수에 저장합니다.
Code (2.2.6)는 생성 된 안전하지 않은 인스턴스의 비교와 핀트 메소드를 호출하고 테스트 객체의 상태 변수의 값을 설정합니다. 구체적으로, 테스트 객체의 메모리 오프셋 인 StateOffset 인 상태 변수가 0 인 경우 업데이트 값이 1으로 변경됩니다.
위의 코드에서 true를 입력하려고하지만 실행 후 다음 결과가 출력됩니다.
왜 이런 일이 일어나고 있습니까? 수행 된 작업을 확인하는 것과 같은 getunsafe 코드를 입력해야합니다.
개인 정적 최종 최종 안전하지 않은 theUnsafe = 새로운 불안한 (); 공개 정적 안전하지 않은 getunsafe () {// (2.2.7) class localclass = reclection.getCallerClass (); // (2.2.8) if (! vm.issystemdomainloader (localclass.getClassLoader ())) {Throw New SecurityException ( "안전하지 않은"); } return theUnsafe;} // paramclassloader가 부트 스트랩 클래스 로더인지 판단하십시오 (2.2.9) 공개 정적 부울 issystemdomainloader (classloader paramclassloader) {return paramclassloader == null; }코드 (2.2.7) getunsafe를 호출하는 객체의 클래스 객체를 가져옵니다. 여기에 testunsafe.cals가 있습니다.
Code (2.2.8)는 부트 스트랩 클래스 로더가로드 한 로컬 클래스인지 여부를 결정합니다. 여기서 핵심은 부트 스트랩 로더가 testunsafe.class를로드하는지 여부입니다. Java Virtual Machine의 클래스 로딩 메커니즘을 본 사람들은 TestunSafe.class가 AppClassLoader를 사용하여로드되기 때문에 여기에 직접 제외됩니다.
따라서 문제는 왜이 판단을해야합니까?
안전하지 않은 클래스는 Rt.jar에 제공되며 Rt.jar의 클래스는 Bootstrap 클래스 로더를 사용하여로드됩니다. 우리가 기본 함수를 시작하는 클래스는 AppClassLoader를 사용하여로드되므로, 부모 위임 메커니즘이 부트 스트랩을 위임하여 안전하지 않은 클래스를로드하는 것을 고려할 때, 주 기능에 안전하지 않은 클래스를로드 할 때.
코드 인증이 없으면 (2.2.8), 우리의 응용 프로그램은 안전하지 않은 일을 할 수 있습니다. 안전하지 않은 클래스는 메모리를 직접 작동 할 수 있으며 이는 매우 안전하지 않습니다. 따라서 JDK 개발 팀은 개발자가 일반 채널에서 안전하지 않은 클래스를 사용할 수는 없지만 Rt.jar의 핵심 클래스에서 안전하지 않은 기능을 사용하지 않도록 특별히 이러한 제한 사항을 제한했습니다.
문제는 안전하지 않은 클래스를 실제로 인스턴스화하고 안전하지 않은 기능을 사용하려면 어떻게해야합니까?
우리는 검은 색 반사 기술을 잊어서는 안되며, 안전하지 않은 인스턴스 방법을 얻기 위해 보편적 반사를 사용해야합니다. 코드는 다음과 같습니다.
package com.hjc; import sun.misc.unsafe; import java.lang.reflect.field;/*** Cong에 의해 생성 된 2018/6/6. */public class testunsafe {정적 최종 안전하지 않은 안전하지 않은; 정적 최종 최종 긴 StateOffset; 개인 휘발성 긴 상태 = 0; static {try {// theUnsafe 멤버 변수를 얻기 위해 반사하십시오. theunsafe (2.2.10) 필드 필드 = unsafe.class.getDeclaredfield ( "theunsafe"); // 액세스 가능 (2.2.11) Field.SetAccessible (true)로 설정합니다. //이 변수의 값을 가져옵니다 (2.2.12) 안전하지 않은 = (안전하지 않은) field.get (null); // testunsafe (2.2.13)에서 상태 오프셋을 가져옵니다. } catch (예외) {System.out.println (예 : getLocalizedMessage ()); 새로운 오류를 던지십시오 (예 :); }} public static void main (String [] args) {testunsafe test = new testunsafe (); 부울 성공 = unsafe.compareAndswapint (테스트, StateOffset, 0, 1); System.out.println (sucess); }}위의 코드 (2.2.10 2.2.11 2.2.12)가 안전하지 않은 예제를 반영하는 경우 실행 결과는 다음과 같습니다.
2. Locksupport 클래스의 소스 코드에 대한 연구
JDK의 Rt.jar의 Locksupport는 도구 클래스이며 주요 기능은 스레드를 중단하고 깨우는 것입니다. 잠금 및 기타 동기화 클래스를 만드는 기초입니다.
Locksupport 클래스는이를 사용하는 각 스레드와 관련이 있습니다. 기본적으로 Locksupport 클래스의 메소드를 호출하는 스레드는 라이센스를 보유하지 않습니다. Locksupport는 안전하지 않은 클래스를 사용하여 내부적으로 구현됩니다.
여기서 우리는 다음과 같이 Locksupport의 몇 가지 중요한 기능에주의를 기울여야합니다.
1. Void Park () 메소드 : Calling Park ()가 Locksupport와 관련된 라이센스를 얻은 경우 Locksupport.park () 호출이 즉시 반환됩니다. 그렇지 않으면, 호출 스레드는 스레드의 스케줄링에 참여하는 것이 금지됩니다. 즉, 차단 및 일시 중단됩니다. 다음 코드는 다음 예입니다.
package com.hjc; import java.util.concurrent.locks.locksupport;/*** Cong에 의해 생성 된 2018/6/6. */public class locksupporttest {public static void main (String [] args) {System.out.println ( "Park Start!"); Locksupport.park (); System.out.println ( "Park Stop!"); }}위의 코드에서 볼 수 있듯이 주요 기능에서 파크 메소드를 직접 호출하면 최종 결과가 공원 시작 만 출력합니다! 호출 스레드가 기본적으로 라이센스를 보유하지 않기 때문에 현재 스레드가 중단됩니다. 작업 결과는 다음과 같습니다.
다른 스레드가 unpark (스레드 스레드) 메소드를 호출하고 현재 스레드가 매개 변수로 사용되면 파크 메소드를 호출하는 스레드가 반환됩니다. 또한 다른 스레드는 차단 스레드의 인터럽트 () 메소드를 호출합니다. 인터럽트 플래그가 설정되거나 스레드의 오 탐용 후 차단 스레드가 돌아 오면 루프 조건을 사용하여 판단하는 것이 가장 좋습니다.
Park () 메소드를 차단하는 스레드는 다른 스레드에 의해 중단되고 차단 된 스레드 리턴은 인터럽트 한 예외 예외를 던지지 않습니다.
2. void unpark (스레드 스레드) 메소드 스레드를 호출 할 때, 매개 변수 스레드 스레드가 스레드 및 Locksupport 클래스와 관련된 라이센스를 보유하지 않으면 스레드가 그것을 유지하도록합니다. Park ()라는 스레드가 이전에 매달리고 스레드가 호출되면, 파크가 호출 된 후에 실이 깨어납니다.
스레드가 이전에 공원에 전화하지 않은 경우, 파크 방법을 호출 한 후 Park () 방법은 즉시 반환됩니다. 위 코드는 다음과 같이 수정됩니다.
package com.hjc; import java.util.concurrent.locks.locksupport;/*** Cong에 의해 생성 된 2018/6/6. */public class locksupporttest {public static void main (String [] args) {System.out.println ( "Park Start!"); // 현재 스레드가 라이센스 lecupport.unpark를 얻습니다 (Thread.CurrentThread ()); // Park Locksupport.park ()을 다시 호출합니다. System.out.println ( "Park Stop!"); }}작업 결과는 다음과 같습니다.
다음으로, 우리는 Park에 대한 이해를 심화시키기위한 예를 살펴보고, 코드는 다음과 같습니다.
java.util.concurrent.locks.locksupport;/*** import 2018/6/6에 Cong에 의해 생성되었습니다. */public class locksupporttest {public static void main (String [] args)은 인터럽트 exception {스레드 스레드 = 새로운 스레드 (new Runnable () {@override public void run () {system.out.println ( "Child Thread Park Start!"); // childsupport.park (); ") }); // 자식 스레드 스레드를 시작합니다. // 메인 스레드는 1s stread.sleep (1000); System.out.println ( "메인 스레드가 시작되지 않은 시작!"); // 스레드가 라이센스를 유지하도록하기 위해 Unpark에 전화를 걸면 Park 메소드는 Locksupport.unpark (스레드)를 반환합니다. }}작업 결과는 다음과 같습니다.
위의 코드는 먼저 자식 스레드 스레드를 만듭니다. 스타트 업 후, 어린이 스레드는 공원 방법을 호출합니다. 기본 하위 스레드는 라이센스를 보유하지 않기 때문에 자체적으로 매달려 있습니다.
메인 스레드는 1s에 잠이 듭니다. 목적은 메인 스레드가 파크 방법을 호출하고 Child Thread가 Child Thread Park를 출력하게한다는 것입니다! 그리고 블록.
그런 다음 기본 스레드는 매개 변수가 자식 스레드 인 경우 UNPARK 메소드를 실행하고, Child Thread가 라이센스를 유지하고 Child Thread가 호출하는 공원 메소드가 반환하는 것이 목적입니다.
공원 방법을 반환 할 때 어떤 이유가 돌아 오는지 알려주지 않습니다. 따라서 발신자는 현재 호출에 어떤 파크 방법이 있었는지에 따라 상태가 만족되는지 다시 확인해야합니다. 충족되지 않으면 공원 방법에 다시 전화해야합니다.
예를 들어, 스레드의 인터럽트 상태가 돌아올 때 스레드의 인터럽트 상태는 통화 전후에 인터럽트 상태 비교에 따라 중단되기 때문에 반환되는지 여부를 결정할 수 있습니다.
공원 메소드를 호출 한 후 스레드가 중단 된 후 리턴이 돌아옵니다. 위의 예제 코드를 수정하고 Locksupport.unpark (스레드)를 삭제하십시오. 그런 다음 thread.interrupt ()를 추가하십시오. 코드는 다음과 같습니다.
java.util.concurrent.locks.locksupport;/*** import 2018/6/6에 Cong에 의해 생성되었습니다. */public class locksupporttest {public static void main (string [] args)은 인터럽트 exception {스레드 스레드 = 새 스레드 (new runnable () {@override public void run () {system.out.println ( "하위 스레드 파크 스타트!"); // 자신을 매달고 잠깐만 인터럽트가 끝납니다. (! thread.currentthread (). isterrupted ()) {locksupport.park (); // 자식 스레드 스레드를 시작합니다 .Start (); // 메인 스레드는 1s 스레드 (1000); System.out.println ( "메인 스레드가 시작되지 않은 시작!"); // child streud.interrupt ()를 인터럽트합니다. }}작업 결과는 다음과 같습니다.
위의 코드로서, 자식 스레드는 자식 스레드가 중단 된 후에 만 종료됩니다. 아동 스레드가 중단되지 않으면 Unpark (스레드)라고 부르더라도 자식 스레드는 종료되지 않습니다.
3. Void Parknanos (Long Nanos) 방법 : Park와 유사하게, Thread Calling Park가 Locksupport와 관련된 라이센스를 얻은 경우 Locksupport.park ()에게 즉시 반환됩니다. 차이점은 스레드를 호출하는 스레드를 얻지 못하면 중단 된 다음 나노 시간이 지나면 돌아옵니다.
Park는 또한 차단 매개 변수가있는 세 가지 방법을 지원합니다. 스레드가 라이센스를 보유하지 않고 Park를 호출하고 차단 및 매달리면 차단제 객체는 스레드 내부에 기록됩니다.
진단 도구를 사용하여 스레드가 차단 된 이유를 관찰하십시오. 진단 도구는 GetBlocker (Thread) 메소드를 사용하여 차단제 객체를 얻습니다. 따라서 JDK는 차단기 매개 변수와 함께 파크 메소드를 사용하고 차단기를이를 설정하는 것이 좋습니다. 따라서 메모리 덤프가 문제를 해결할 때 어떤 클래스가 차단되는지 알 수 있습니다.
예는 다음과 같습니다.
java.util.concurrent.locks.locksupport;/*** import 2018/6/6에 Cong에 의해 생성되었습니다. */public class testpark {public void testpark () {locksupport.park (); // (1)} public static void main (string [] args) {testpark testpark = new TestPark (); testpark.testpark (); }}작업 결과는 다음과 같습니다.
블록이 실행 중이라는 것을 알 수 있으므로 JDK/빈 디렉토리의 도구를 사용하여 살펴 봐야합니다. 모르는 경우 먼저 JVM 모니터링 도구를 살펴 보는 것이 좋습니다.
JSTACK PID를 사용하여 실행 후 스레드 스택을 볼 때 다음과 같습니다.
그런 다음 위의 코드 (1)를 다음과 같이 수정합니다.
Locksupport.park (this); // (1)
다시 실행하고 JSTACK PID를 사용하여 결과를 다음과 같이보십시오.
차단제가있는 파크 방법 후에는 스레드 스택이 물체 차단에 대한 자세한 정보를 제공 할 수 있음을 알 수 있습니다.
그런 다음 파크 (객체 차단기) 함수의 소스 코드를 확인합니다. 소스 코드는 다음과 같습니다.
public static void park (객체 차단기) {// 호출 스레드를 가져옵니다. t = thread.currentthread (); // 차단기 변수 setBlocker (t, blocker)를 설정합니다. // 스레드를 안전하지 않은 스레드를 걸어. // 스레드가 활성화 된 후 차단 변수를 지우십시오. 스레드가 차단 될 때 이유는 일반적으로 setBlocker (t, null);}를 분석하기 때문입니다.스레드 클래스 휘발성 객체 ParkBlocker에는 변수가 있습니다. 공원이 통과하는 차단제 객체를 저장하는 데 사용됩니다. 즉, 차단 변수는 파크 방법을 호출하는 스레드의 멤버 변수에 저장됩니다.
4. 빈 공간 파크 나노 (객체 차단제, 긴 나노) 함수는 Park (객체 차단기)에 비해 추가 시간 초과 시간이 있습니다.
5. void parkuntil (객체 차단기, 긴 마감일) Parkuntil의 소스 코드는 다음과 같습니다.
public static void parkuntil (객체 차단기, 긴 마감일) {스레드 t = thread.currentthread (); setBlocker (t, 차단제); // isabsolute = true, time = 마감일; 안전하지 않은 것을 의미합니다. setBlocker (t, null); }정해진 마감일이라는 것을 알 수 있습니다. 시간 단위는 밀리 초이며 1970 년부터 현재 시점까지 밀리 초 후에 값으로 변환됩니다. 이것과 parknanos (객체 차단제, 긴 나노)의 차이점은 후자가 현재 시간에서 대기 나노 시간을 계산하는 반면, 전자는 시점을 지정한다는 것입니다.
예를 들어, 2018.06.06에서 20:34까지 기다린 다음이 시점을 1970 년 부터이 시점까지 총 밀리 초로 변환해야합니다.
다른 예를 살펴 보겠습니다. 코드는 다음과 같습니다.
import java.util.queue; import java.util.concurrent.concurrentlinkedqueue; import java.util.concurrent.atomic.atomicboolean; import java.util.concurrent.locks.locksupport;/*** *** *** 2018/6/6. */public class fifomutex {private final atomicboolean locked = new atomicboolean (false); 비공개 최종 큐 <Sodren> 웨이터 = 새로운 동시경 링크 Queue <Stride> (); public void lock () {부울은 중단되었습니다 = 거짓; 스레드 current = thread.currentthread (); 웨이터 .add (현재); // 팀장의 스레드만이 잠금을 얻을 수 있습니다 (1) while (waiters.peek ()! = current ||! locked.compareAndset (false, true)) {locksupport.park (this); if (thread.interrupted ()) // (2)가 중단되었습니다 = true; } waiters.remove (); if (was interprupted) // (3) current.interrupt (); } public void unlock () {locked.set (false); Locksupport.unpark (waiters.peek ()); }}이것이 첫 번째 우선 잠금 잠금 장치라는 것을 알 수 있습니다. 즉, 큐 헤더 요소 만 얻을 수 있습니다. 코드 (1) 현재 스레드가 큐 헤더가 아니거나 다른 스레드에 의해 현재 잠금 장치가 획득 된 경우, 파크 방법을 호출하여 스스로를 일시 중단하십시오.
그런 다음 코드 (2)는 판단합니다. 파크 메소드가 중단되어 인터럽트가 무시되고 인터럽트 플래그가 재설정되고 플래그 만 만들어진 다음 현재 스레드가 큐의 헤드 요소인지 또는 다른 스레드에 의해 잠금이 획득되었는지 여부를 다시 결정합니다. 그렇다면 공원 방법에 전화하여 스스로를 걸으십시오.
코드 (3)에서 마크가 참이면 스레드가 중단됩니다. 이것을 어떻게 이해합니까? 실제로 다른 스레드가 스레드를 방해했습니다. 인터럽트 신호에 관심이없고 무시하지만 다른 스레드가 플래그에 관심이 없다는 것을 의미하지는 않기 때문에 복원해야합니다.
요약
위는이 기사의 전체 내용입니다. 이 기사의 내용에 모든 사람의 연구 나 작업에 대한 특정 참조 가치가 있기를 바랍니다. 궁금한 점이 있으면 의사 소통을 위해 메시지를 남길 수 있습니다. Wulin.com을 지원 해주셔서 감사합니다.