1. 스레드 안전 문제는 언제 발생합니까?
단일 스레드에는 스레드 안전 문제가 없지만 다중 스레드 프로그래밍에서는 동시에 동일한 리소스에 액세스 할 수 있습니다. 이 리소스는 변수, 객체, 파일, 데이터베이스 테이블 등 다양한 유형의 리소스가 될 수 있습니다. 여러 스레드가 동시에 동일한 자원에 액세스 할 때 다음과 같습니다.
각 스레드에 의해 실행 된 프로세스는 통제 할 수 없으므로 최종 결과는 실제 소원에 위배되거나 프로그램 오류로 직접 이어질 가능성이 높습니다.
간단한 예를 들어 봅시다 :
이제 네트워크에서 데이터를 별도로 읽은 다음 데이터베이스 테이블에 삽입하는 두 개의 스레드가있어 중복 데이터를 삽입 할 수 없습니다.
그런 다음 데이터를 삽입하는 과정에서 두 가지 작업이 있어야합니다.
1) 데이터가 데이터베이스에 존재하는지 확인하십시오.
2) 존재하면 삽입되지 않습니다. 존재하지 않으면 데이터베이스에 삽입됩니다.
두 스레드가 각각 Thread-1 및 Thread-2로 표시되고, 어느 시점에서 Thread-1 및 Thread-2는 데이터 X를 읽습니다.
Thread-1은 데이터 X가 데이터베이스에 존재하는지 여부를 확인하고 Thread-2는 데이터베이스에 데이터 X가 존재하는지 확인합니다.
결과적으로, 두 스레드를 점검 한 결과는 데이터 X가 데이터베이스에 존재하지 않으므로 두 스레드는 각각 데이터 X를 데이터베이스 테이블에 각각 삽입합니다.
이것은 스레드 안전 문제입니다. 즉, 여러 스레드가 동시에 리소스에 액세스 할 때 프로그램 실행 결과는보고 싶은 결과가 아닙니다.
여기서는이 리소스를 다음과 같이합니다. Critical Resource (공유 리소스라고도 함).
즉, 여러 스레드가 중요한 리소스에 액세스 할 때 (하나의 객체, 객체의 속성, 파일, 데이터베이스 등) 스레드 안전 문제가 발생할 수 있습니다.
그러나 여러 스레드가 메소드를 실행할 때 메소드가 스택에서 실행되기 때문에 메소드 내부의 로컬 변수는 중요한 자원이 아닙니다.
2. 스레드 안전 문제를 해결하는 방법은 무엇입니까?
일반적으로 스레드 안전 문제를 해결하는 방법은 무엇입니까?
기본적으로 스레드 안전 문제를 해결할 때 모든 동시성 모드는 "임계 리소스에 대한 직렬화 된 액세스"솔루션을 채택합니다.
일반적으로, 중요한 자원에 액세스하는 코드 전에 잠금이 추가됩니다. 중요한 자원에 액세스 한 후 잠금이 해제되고 다른 스레드가 계속 액세스됩니다.
Java에서는 동기식 뮤트 액세스를 구현하는 두 가지 방법이 제공됩니다 : 동기화 및 잠금.
이 기사는 주로 동기화 된 사용에 대해 설명하며 잠금의 사용은 다음 블로그 게시물에 설명되어 있습니다.
3. 동기화 된 동기화 방법 또는 동기화 블록
동기화 된 키워드 사용을 이해하기 전에 먼저 뮤 테스 액세스의 목적을 달성 할 수있는 잠금 장치 : Mutx Locks 개념을 먼저 살펴 보겠습니다.
간단한 예를 들어 보려면 : 중요한 리소스가 뮤텍스에 추가되면 한 스레드가 임계 리소스에 액세스 할 때 다른 스레드는 대기 할 수 있습니다.
Java에서 각 객체에는 모니터라고도하는 잠금 마크 (모니터)가 있습니다. 여러 스레드가 동시에 객체에 액세스하면 스레드가 객체의 잠금을 얻는 경우에만 액세스 할 수 있습니다.
Java에서는 동기화 된 키워드를 사용하여 메소드 또는 코드 블록을 표시 할 수 있습니다. 스레드가 객체의 동기화 된 메소드를 호출하거나 동기화 된 코드 블록에 액세스하면 스레드는 객체의 잠금을 얻습니다. 다른 스레드는 당분간 방법에 액세스 할 수 없습니다. 메소드가 실행되거나 코드 블록이 실행 된 경우에만 스레드가 객체의 잠금을 해제하고 다른 스레드는 메소드 또는 코드 블록을 실행할 수 있습니다.
다음은 동기화 된 키워드의 사용을 설명하기위한 간단한 예입니다.
1. 동기화 된 방법
다음 코드에서는 두 개의 스레드가 insertData 객체를 호출하여 데이터를 삽입합니다.
공개 클래스 테스트 {public static void main (String [] args) {Final InsertData insertData = new insertData (); new Thread () {public void run () {insertData.insert (Thread.CurrentThread ()); }; }.시작(); new Thread () {public void run () {insertData.insert (Thread.CurrentThread ()); }; }.시작(); }} class insertData {private arraylist <integer> arraylist = new ArrayList <integer> (); public void insert (스레드 스레드) {for (int i = 0; i <5; i ++) {system.out.println (thread.getName ()+"data"+i); ArrayList.add (i); }}} 현재 프로그램의 출력 결과는 다음과 같습니다.
이것은 두 스레드가 삽입 메소드를 동시에 실행 함을 보여줍니다.
삽입 메소드 전에 동기화 된 키워드가 추가되면 실행 결과는 다음과 같습니다.
클래스 INSERTDATA {private arrayList <integer> arraylist = new ArrayList <integer> (); public synchronized void insert (스레드 스레드) {for (int i = 0; i <5; i ++) {system.out.println (thread.getname ()+"data insert data"+i); ArrayList.add (i); }}}상기 출력으로부터, Thread-1에 삽입 된 데이터는 Thread-0이 삽입 된 후에 만 수행된다. 이것은 Thread-0 및 Thread-1이 삽입 메소드를 순차적으로 실행 함을 보여줍니다.
이것은 동기화 된 방법입니다.
그러나 몇 가지 점이 있습니다.
1) 스레드가 객체의 동기화 된 메소드에 액세스하는 경우 다른 스레드는 객체의 다른 동기화 된 방법에 액세스 할 수 없습니다. 객체에는 하나의 잠금 만 가지고 있기 때문에 이러한 이유는 매우 간단합니다. 스레드가 객체의 잠금을 획득하면 다른 스레드는 물체의 잠금을 얻을 수 없으므로 객체의 다른 동기화 된 메소드에 액세스 할 수 없습니다.
2) 스레드가 객체의 동기화 된 메소드에 액세스하는 경우 다른 스레드는 객체의 비 동기화 된 메소드에 액세스 할 수 있습니다. 그 이유는 매우 간단합니다. 비 동기화 된 메소드에 액세스하면 객체의 잠금이 필요하지 않습니다. 메소드가 동기화 된 키워드로 수정되지 않은 경우 중요한 리소스를 사용하지 않으면 다른 스레드 가이 메소드에 액세스 할 수 있습니다.
3) 스레드 A가 Object1의 동기화 된 메소드 Fun1에 액세스 해야하는 경우, 다른 스레드 B는 Object1과 Object2의 동기화 된 메소드 Fun1, Object1과 Object2가 동일한 유형 인 경우에도 다른 객체에 액세스하기 때문에 스레드 안전 문제가 없을 것이므로 상호 제외 문제가 없습니다.
2. 동기화 된 코드 블록
동기화 된 코드 블록은 다음 형식과 유사합니다.
동기화 (syncobject) {
}
이 코드 블록이 스레드에서 실행되면 스레드는 객체 synobject의 잠금을 얻으므로 다른 스레드가 코드 블록에 동시에 액세스 할 수 없습니다.
synobject는 현재 객체를 얻는 잠금을 나타내거나 클래스에서 속성이 될 수 있으며 속성을 얻는 잠금을 나타냅니다.
예를 들어, 위의 삽입 방법은 다음 두 가지 형태로 변경할 수 있습니다.
클래스 INSERTDATA {private arrayList <integer> arraylist = new ArrayList <integer> (); public void insert (스레드 스레드) {synchronized (this) {for (int i = 0; i <100; i ++) {system.out.println (thread.getname ()+"insert data"+i); ArrayList.add (i); }}}} class insertData {private arraylist <integer> arraylist = new ArrayList <integer> (); 개인 객체 개체 = 새 개체 (); public void insert (스레드 스레드) {synchronized (object) {for (int i = 0; i <100; i ++) {system.out.println (thread.getname ()+"insert data"+i); ArrayList.add (i); }}}}위에서 볼 수 있듯이 동기화 된 코드 블록은 동기화 된 방법보다 사용하기가 훨씬 유연합니다. 메소드의 코드의 일부만 동기화해야하므로 현재 전체 메소드가 동기화되면 프로그램 실행 효율에 영향을 미칩니다. 이 문제는 동기화 된 코드 블록을 사용하여 피할 수 있습니다. 동기화 된 코드 블록은 동기화가 필요한 경우에만 동기화 할 수 있습니다.
또한 각 클래스에는 잠금 장치가있어 정적 데이터 구성원에 대한 동시 액세스를 제어하는 데 사용할 수 있습니다.
그리고 스레드가 객체의 비 정적 동기화 된 방법을 실행하고 다른 스레드가 객체가 속한 클래스의 정적 동기화 된 메소드를 실행 해야하는 경우, 정적 동기화 된 방법에 액세스하는 것은 클래스 잠금 장치에 액세스하기 때문에 객체 잠금을 점유하지 않기 때문에이 시점에서 상호 배제는 없을 것입니다.
다음 코드를 살펴보면 이해할 것입니다.
공개 클래스 테스트 {public static void main (String [] args) {Final InsertData insertData = new insertData (); 새 스레드 () {@override public void run () {insertData.insert (); } }.시작(); 새 스레드 () {@override public void run () {insertData.insert1 (); } }.시작(); }} class insertData {public synchronized void insert () {system.out.println ( "execute insert"); try {thread.sleep (5000); } catch (InterruptedException e) {e.printstacktrace (); } system.out.println ( "execute insert1"); } public synchronized static void insert1 () {system.out.println ( "execute insert1"); System.out.println ( "execute insert1"); }} 실행 결과;
삽입 메소드는 첫 번째 스레드에서 실행되므로 두 번째 스레드가 insert1 메소드를 차단하지 않습니다.
동기화 된 키워드가 무엇을하는지 살펴 보겠습니다. 바이트 코드를 분해합시다. 다음 코드의 코드 코드는 다음과 같습니다.
public class insertData {private object 객체 = new Object (); public void insert (스레드 스레드) {synchronized (object) {}} public synchronized void insert1 (스레드 스레드) {} public void insert2 (스레드 스레드) {}}소집하여 얻은 바이트 코드에서 동기화 된 코드 블록에는 실제로 Moniterenter와 MonitoreXit의 두 가지 지침이 있음을 알 수 있습니다. Moniterenter 명령이 실행되면 객체의 잠금 수가 1 씩 증가하고 MonitorexIt 명령어가 실행되면 객체의 잠금 수가 1만큼 줄어 듭니다. 실제로 이는 운영 체제의 PV 작동과 매우 유사합니다. 운영 체제의 PV 운영은 중요한 리소스에 액세스하기 위해 여러 스레드를 제어하는 데 사용됩니다. 동기화 된 메소드의 경우, 실행중인 스레드는 메소드의 메소드 _info 구조에 ACC_SynChronized 플래그 설정이 있는지 여부를 인식 한 다음 객체의 잠금을 자동으로 획득하고 메소드를 호출하고 최종적으로 잠금을 방출합니다. 예외가 발생하면 스레드가 자동으로 잠금을 출시합니다.
한 가지 주목할 사항 : 동기화 된 메소드 또는 동기화 된 코드 블록의 경우 예외가 발생하면 JVM은 현재 스레드가 차지하는 잠금 장치를 자동으로 해제하므로 예외로 인해 교착 상태가 없습니다.
3. 동기화 된 다른 주목할만한 것들
1. 동기화 된 것과 정적 동기화의 차이
동기화 된 잠금 장치는 클래스의 현재 인스턴스를 잠금하여 다른 스레드가 클래스 인스턴스의 동시에 동시에 동시에 액세스하는 것을 방지합니다. 이것은 "클래스의 현재 인스턴스"이며, 클래스의 두 가지 사례에 그러한 제약이 없습니다. 그런 다음 정적 동기화는 클래스의 모든 인스턴스에 대한 액세스를 제어하기 위해 발생합니다. 정적 동기화 된 스레드는 스레드를 제한하여 JVM의 클래스의 모든 인스턴스에 동시에 액세스하고 해당 코드에 빠르게 액세스합니다. 실제로, 클래스에서 메소드 또는 코드 블록에 동기화 된 경우,이 클래스의 인스턴스를 생성 한 후 클래스는 빠른 모니터링을 갖고, 수정 된 인스턴스에 대한 스레드 동시 액세스는 빠르게 보호됩니다. 정적 동기화 된 것은 모니터링 중 공개 중 하나이며, 이는 둘 사이의 차이입니다. 즉, 동기화 된 것은 이것과 동일합니다 .Synchronized 및 static synchronized는 Synchronized와 동일합니다.
일본 작가 인 Jie Chenghao의 "Java Multithreaded Design Pattern"은 다음과 같은 열이 있습니다.
pulbic class something () {public synchronized void issynca () {} public synchronized void void issyncb () {} public static synchronized void csynca () {} public static synchronized void csyncb () {}} 그런 다음 클래스의 두 인스턴스 A와 B가 추가되면 왜 다음 메소드 그룹에 동시에 둘 이상의 스레드에 액세스 할 수 있습니까? axissynca () 및 x.issyncb ()
bxissynca () 및 y.issynca ()
cxcsynca () 및 y.csyncb ()
dxissynca () 및 뭔가 .csynca ()
여기에서 판단 할 수 있음이 분명합니다.
A, 동일한 인스턴스의 동기화 된 도메인에 대한 액세스이므로 동시에 액세스 할 수 없습니다. B는 다른 인스턴스이므로 동시에 액세스 할 수 있습니다. 정적 동기화되어 있으므로 다른 인스턴스가 여전히 제한되며, 이는 무언가.issynca () 및 Something.issyNCB ()와 동일하므로 동시에 액세스 할 수 없습니다.
따라서 D?는 어떻습니까?이 책의 답은 동시에 액세스 할 수 있습니다. 답의 이유는 동기화 된 인스턴스 메소드와 동기화 클래스 방법이 잠금과 다르기 때문입니다.
개인 분석은 동기화되고 정적 동기화 된 두 개의 갱과 동일하며 각 갱단과 동일하며 각 갱단은 자체 제어를 가지고 있으며 서로 제약이 없으며 동시에 액세스 할 수 있습니다. Java 내부 디자인에서 Synchronzied가 어떻게 구현되는지는 확실하지 않습니다.
결론 : A : 동기화 된 정적은 특정 클래스의 범위입니다. 동기화 된 정적 csync {}는 여러 스레드가 동시에 동시에 동기화 된 정적 메소드에 액세스하는 것을 방지합니다. 클래스의 모든 객체 인스턴스에서 작동합니다.
B : Synchronized는 인스턴스의 범위입니다. 동기화 된 ISSYNC () {}은 여러 스레드 가이 인스턴스의 동기화 된 메소드에 동시에 액세스하는 것을 방지합니다.
2. 동기화 된 방법과 동기화 된 코드 빠른 차이
동기화 된 메소드 () {}와 동기화 된 (this) {} 사이에는 차이가 없지만 동기화 된 메소드 () {}는 읽기 이해력에 편리하지만 동기화 된 (this) {}는 갈등을 더 정확하게 제어 할 수 있으며 때로는 더 효율적으로 수행 할 수 있습니다.
3. 동기화 된 키워드를 상속받을 수 없습니다