Java 언어 키워드를 사용하여 메소드나 코드 블록을 수정하면 최대 하나의 스레드가 동시에 코드를 실행하도록 할 수 있습니다.
1. 두 개의 동시 스레드가 동일한 개체 개체의 동기화된(this) 동기화 코드 블록에 액세스하는 경우 한 번에 하나의 스레드만 실행될 수 있습니다. 다른 스레드는 이 코드 블록을 실행하기 전에 현재 스레드가 이 코드 블록 실행을 완료할 때까지 기다려야 합니다.
2. 그러나 스레드가 객체의 동기화된(this) 동기화 코드 블록에 액세스하면 다른 스레드는 여전히 객체의 동기화되지 않은(this) 동기화 코드 블록에 액세스할 수 있습니다.
3. 특히 중요한 점은 스레드가 객체의 동기화된(this) 동기화 코드 블록에 액세스할 때 다른 스레드가 객체의 다른 모든 동기화된(this) 동기화 코드 블록에 액세스하는 것이 차단된다는 것입니다.
4. 세 번째 예는 다른 동기화 코드 블록에도 적용 가능합니다. 즉, 스레드가 개체의 동기화된(this) 동기화 코드 블록에 액세스하면 이 개체의 개체 잠금을 획득합니다. 결과적으로 개체 개체의 모든 동기화된 코드 부분에 대한 다른 스레드의 액세스가 일시적으로 차단됩니다.
5. 위의 규칙은 다른 객체 잠금에도 적용됩니다.
예:
1. 두 개의 동시 스레드가 동일한 객체 객체의 동기화된(this) 동기화 코드에 액세스할 때 차단하면 한 번에 하나의 스레드만 실행할 수 있습니다. 다른 스레드는 이 코드 블록을 실행하기 전에 현재 스레드가 이 코드 블록 실행을 완료할 때까지 기다려야 합니다.
package ths;
public class Thread1은 Runnable을 구현합니다. {
public void run() {
synced(this) {
for (int i = 0; i < ; 5; i++) {
System.out.println(Thread.currentThread().getName() + " 동기화 루프 " + i) }
public static void main(String[] args) { ~ 아웃 ) ;
ta.start();
tb.start();
} 1
동기화 루프 2
동기화 루프 3
동기화 루프 4
B 동기화 루프 0
B 동기화 루프 1
B 동기화 루프 2
B 동기화 루프 3
2. 그러나 스레드가 동기화(this) 동기화에 액세스하는 경우 객체의 코드 블록이 있어도 다른 스레드는 여전히 객체의 동기화되지 않은(this) 동기화 코드 블록에 액세스할 수 있습니다.
패키지;
public class Thread2 {
public void m4t1() {
synced(this) {
int i = 5;
while( i-- > 0) {
System.out.println(Thread.currentThread().getName() + " : " + i);
시도해 보세요 {
Thread.sleep(500);
} catch(InterruptedException 즉) {
}
}
}
}
public void m4t2() {
int i = 5;
while( i-- > 0) {
System.out.println(Thread.currentThread().getName() + " : " + i);
시도해 보세요 {
Thread.sleep(500);
} catch(InterruptedException 즉) {
}
}
}
public static void main(String[] args) {
최종 Thread2 myt2 = new Thread2();
스레드 t1 = new Thread( new Runnable() { public void run() { myt2.m4t1(); } }, "t1" );
스레드 t2 = new Thread( new Runnable() { public void run() { myt2.m4t2(); } }, "t2" );
t1.start();
t2.start();
}
}
结果:
t1 : 4
t2 : 4
t1 : 3
t2 : 3
t1 : 2
t2 : 2
t1 : 1
t2 : 1
t1 : 0
t2 : 0
3、尤其关键的是,当一个线程访问객체의 一个동기화됨(this)同步代码块时,其他线程对object中所有其它synchronized(this)同步代码块的访问将被阻塞。
//修改Thread2.m4t2()방법:
public void m4t2() {
synced(this) {
int i = 5;
while( i-- > 0) {
System.out.println(Thread.currentThread().getName() + " : " + i);
시도해 보세요 {
Thread.sleep(500);
} catch(InterruptedException 즉) {
}
}
}
}
结果:
t1 : 4
t1 : 3
t1 : 2
t1 : 1
t1 : 0
t2 : 4
t2 : 3
t2 : 2
t2 : 1
t2 : 0
4、第三个例子同样适用其它同步代码块。也就是说,当一个线程访问객체의 一个synchronized(this)同步代码块时,它就获得了这个객체의 对象锁。结果,其它线程对该object对象所有同步代码部分的访问塞。
//修改Thread2.m4t2()方法如下:
공개 동기화 d 무효 m4t2() {
int i = 5;
while( i-- > 0) {
System.out.println(Thread.currentThread().getName() + " : " + i);
시도해 보세요 {
Thread.sleep(500);
} catch (InterruptedException 즉) {
}
}
}
结果:
t1 : 4
t1 : 3
t1 : 2
t1 : 1
t1 : 0
t2 : 4
t2 : 3
t2 : 2
t2 : 1
t2 : 0
五、以上规则对其它对象锁同样适用:
패키지;
public class Thread3 {
class Inner {
private void m4t1() {
int i = 5;
while(i-- > 0) {
System.out.println(Thread.currentThread().getName() + " : Inner.m4t1()=" + i);
시도해 보세요 {
Thread.sleep(500);
} catch(InterruptedException 즉) {
}
}
}
비공개 void m4t2() {
int i = 5;
while(i-- > 0) {
System.out.println(Thread.currentThread().getName() + " : Inner.m4t2()=" + i);
시도해 보세요 {
Thread.sleep(500);
} catch(InterruptedException 즉) {
}
}
}
}
private void m4t1(내부 내부) {
동기화(내부) { //使用对象锁
inner.m4t1();
}
private void m4t2(내부 내부) {
inner.m4t2();
}
public static void main(String[] args) {
final Thread3 myt3 = new Thread3();
final Inner inner = myt3.new Inner();
스레드 t1 = new Thread( new Runnable() {public void run() { myt3.m4t1(inner);} }, "t1");
스레드 t2 = new Thread( new Runnable() {public void run() { myt3.m4t2(inner);} }, "t2");
t1.start();
t2.start();
}
}
结果:
尽管线程t1获得了对Inner的对象锁 ,但由于线程t2访问的是同一个Inner中的不同步分.所以两个线程互不干扰。
t1 : Inner.m4t1()=4
t2 : Inner.m4t2()=4
t1 : Inner.m4t1()=3
t 2 : Inner.m4t2()=3
t1 : Inner.m4t1()=2
t2 : Inner.m4t2()=2
t1 : Inner.m4t1()=1
t2 : 내부 .m4t2()=1
t1 : Inner.m4t1()=0
t2 : Inner.m4t2()=0
现재재Inner.m4t2()앞면加上synchronized:
개인 동기화 void m4t2() {
int i = 5
while(i-- > 0) {
System.out.println(Thread.currentThread().getName() + " : Inner.m4t2()=" + i);
try {
Thread.sleep(500);
} catch(InterruptedException ie) {
결과:
스레드 t1과 t2가 동일한 Inner 객체의 관련되지 않은 두 부분에 액세스했지만 t1이 먼저 Inner에서 객체 잠금을 획득했기 때문에 t2는 Inner를 제어할 수 없습니다. m4t2() 때문에 .m4t2()에 대한 액세스도 차단됩니다. Inner의 동기화 방법입니다.
t1 : Inner.m4t1()=4
t1 : Inner.m4t1()=3 t1 : Inner.m4t1()=2
t1 : Inner.m4t1()= 1
t1 : Inner.m4t1()=0
t2 : Inner.m4t2()=4
t2 : Inner.m4t2()=3
t2 : Inner.m4t2()=2
t2 : Inner.m4t2()=1
t2 : Inner.m4t2()=0
파트 2:
동기화 키워드, 두 가지 사용법이 포함됨: 동기화 방법 및 동기화 블록.
1. 동기화된 메소드: 메소드 선언에 동기화된 키워드를 추가하여 동기화된 메소드를 선언합니다. 예를 들면 다음과 같습니다.publicsynchronous void accessVal(int newVal);
동기화 메서드는 클래스 멤버 변수에 대한 액세스를 제어합니다. 각 클래스 인스턴스는 잠금에 해당하며 각 동기화 메서드는 호출하는 클래스 인스턴스에 대한 액세스를 얻어야 합니다. 잠금은
실행될 수 있으며, 그렇지 않으면 해당 스레드가 차단됩니다. 메서드가 실행되면 잠금은 메서드에서 반환될 때까지 해제되지 않습니다. 그 후 차단된 스레드는 잠금을 획득하고
상태로 다시 진입할 수 있습니다. 이 메커니즘은 각 클래스 인스턴스에 대해 동시에 동기화됨으로 선언된 모든 멤버 함수 중 최대 하나가 실행 가능 상태가 되도록 보장합니다(최대
만이 클래스 인스턴스에 해당하는 잠금을 얻을 수 있기 때문입니다) , 이는 클래스 멤버 변수와의 액세스 충돌을 효과적으로 방지합니다(클래스 멤버 변수에 액세스할 수 있는 모든 메서드가 동기화로 선언되는 한)
.
Java에서는 클래스 인스턴스뿐만 아니라 각 클래스도 잠금에 해당합니다. 이러한 방식으로 클래스의 정적 멤버 함수를 동기화하여 클래스의 정적 멤버를 제어할 수도 있습니다.
변수에 대한 접근.
동기화로 선언되면 전체 스레드 수명 동안 계속 실행되므로 이 클래스의 동기화된 메서드에 대한 호출은 결코 성공하지 않습니다. 물론 클래스 멤버 변수에 액세스하는 코드를 특수 메소드에 넣고 동기화를 선언한 후 이를 기본 메소드에서 호출하면 이 문제를
해결할 수 있지만 Java는
을 제공합니다. 더 나은 솔루션은 동기화된 블록입니다.
2. 동기화 블록: 동기화 키워드를 통해 동기화 블록을 선언합니다. 구문은 다음과 같습니다.synchronized(syncObject) {
//액세스 제어를 허용하는 코드
}
synchronized 블록은 코드가 syncObject 객체를 획득해야 하는 코드 블록입니다(앞서 언급한 대로). , 당신은 할 수 있습니다. 잠금은 클래스 인스턴스 또는 클래스인 경우에만 실행될 수 있으며 구체적인 메커니즘
은 위에서 언급한 것과 동일합니다. 어떤 코드블록이라도 대상으로 할 수 있고 잠긴 객체를 임의로 지정할 수 있어 유연성이 높다.
1. 두 개의 동시 스레드가 동일한 객체 개체의 동기화(this) 동기화 코드 블록에 액세스하면 한 번에 하나의 스레드만 실행될 수 있습니다. 다른 스레드
는 이 코드 블록을 실행하기 전에 현재 스레드가 이 코드 블록 실행을 완료할 때까지 기다려야 합니다.
(this) 동기화 코드 블록에 액세스할 수 있습니다.
에 액세스한다는 것입니다.
동기화된 코드 블록에 대한 액세스가 차단됩니다.
4. 세 번째 예는 다른 동기화 코드 블록에도 적용 가능합니다. 즉, 스레드가 개체의 동기화된(this) 동기화 코드 블록에 액세스하면 이
개체의 개체 잠금을 획득합니다. 결과적으로 개체 개체의 모든 동기화된 코드 부분에 대한 다른 스레드의 액세스가 일시적으로 차단됩니다.
5. 위의 규칙은 다른 객체 잠금에도 적용됩니다
http://hi.baidu.com/sunshibing/blog/item/5235b9b731d48ff430add14a.html
Java에서 동기화 사용
예를 들어 사물은 큰 집과 같아서 문은 항상 열려있습니다. 집에는 방(즉, 방법)이 많습니다.
이 방은 잠겨 있거나(동기화 방법) 잠금 해제되어 있습니다(일반 방법). 방 문에 열쇠가 있습니다. 이 열쇠로 잠긴 모든 방을 열 수 있습니다.
또한 객체의 메소드를 호출하려는 모든 스레드를 이 집의 특정 방에 들어가고 싶은 사람들과 비교합니다. 이것이 전부입니다. 이러한 것들이 서로 어떻게 작동하는지 살펴보겠습니다.
여기서 먼저 전제 조건을 명확히 합니다. 객체에는 동기화된 메서드가 하나 이상 있습니다. 그렇지 않으면 이 키는 의미가 없습니다. 물론 우리의 그런 주제는 없을 것입니다.
한 사람이 잠긴 방에 들어가고 싶었습니다. 그는 집 문으로 와서 거기에 열쇠가 있는 것을 보았습니다(아직 잠긴 방을 사용하고 싶어하는 사람이 없다는 뜻). 그래서 그는 걸어가서 열쇠를 받고
계획대로 방을 사용했습니다. 그는 잠긴 방을 사용할 때마다 즉시 열쇠를 반환한다는 점에 유의하십시오. 두 개의 잠긴 방을 연속으로 사용하고 싶어도
중간에 열쇠를 반납하고 돌려받아야 한다.
따라서 일반적인 상황에서 열쇠를 사용하는 원칙은 '사용한 대로 빌리고, 사용한 대로 반납'하는 것입니다.
이때, 잠금 해제된 키는 다른 사람이 사용하지 않고도 사용할 수 있습니다. 제한은 없습니다. 1인 1실 사용, 2인 1실 사용 제한은 없습니다. 그러나 누군가가 잠긴 방에 들어가고 싶다면 그것을 보기 위해 문으로 달려가야 합니다. 물론 열쇠가 있으면 가져가셔도 되고, 열쇠가 없으면 기다려야 합니다.
이 열쇠를 많은 사람이 기다리고 있다면, 열쇠를 돌려받았을 때 누가 먼저 열쇠를 받게 될까요? 보장되지 않습니다. 앞의 예에서 잠긴 방 두 개를 연속으로 사용하고 싶은 사람처럼
가 열쇠를 반납할 때 다른 사람이 열쇠를 기다리고 있다면 이 사람이 다시 열쇠를 얻을 수 있다는 보장이 없습니다. . (JAVA 사양에는
Thread.sleep()이 중단 후 실행으로 돌아가는 데 시간이 얼마나 걸리는지 등을 보장할 수 없다고 여러 곳에서 명시되어 있습니다. 우선 순위가 같은 스레드는 무엇입니까? 먼저 실행되고, 객체에 액세스하기 위한 잠금이 해제될 때 대기 풀의 여러 스레드 중 어느 스레드가
를 먼저 얻을 것인지 등 최종 결정은 JVM에 있다고 생각합니다. JVM이 위의 결정을 내릴 때 단순히 하나의 조건으로 판단하는 것이 아니라
판단 조건이 너무 많아서 판단한다는 것입니다. JAVA의 홍보에 영향을 미칠 수도 있고, 지적 재산권 보호로 인해 발생할 수도 있습니다. .SUN에서는 아무런 보증도 하지 않고
에서 통과시켰습니다. 하지만 이러한 불확실성은 컴퓨터 자체에 있어서 완전히 불확실하지는 않다고 생각합니다. , 실제로 찾을 수 있는 규칙이 있습니다
. 컴퓨터를 공부한 사람이라면 컴퓨터에서 난수의 학명이 의사 난수라는 것을 알고 있습니다. 게다가 특정 방법이 너무 번거롭고 의미도 없어서
확실하지 않은 경우도 있습니다.)
살펴보겠습니다. 동기화 코드 블록. 동기화 방식과는 약간 다릅니다.
1. 동기화된 코드 블록은 크기 측면에서 동기화된 방법보다 작습니다. 동기화된 코드 블록은 잠긴 화면으로 분리된 잠금 해제된 방의 공간으로 생각할 수 있습니다.
2. 동기화 코드 블록은 다른 객체의 키를 인위적으로 지정할 수도 있습니다. 이 화면의 자물쇠를 여는 데 사용할 수 있는 열쇠를 지정하는 것과 마찬가지로
을 지정하여 다른 집의 열쇠를 사용하여 열 수도 있습니다. 다른 집의 열쇠를 가져와 그 집의 열쇠를 사용해 이 집의 잠긴 화면을 열려면 달려가야 합니다.
획득한 다른 집의 열쇠는 다른 사람이 그 집의 잠금 해제된 방에 들어가는 것을 방해하지 않는다는 점을 기억하세요.
동기화된 코드 블록을 사용하는 이유는 무엇입니까? 다음과 같아야 한다고 생각합니다. 우선 프로그램의 동기화 부분이 작업 효율성에 영향을 미치며 일반적으로 방법은 먼저 일부 지역 변수
를 만든 다음 이 변수에 대해 몇 가지 작업을 수행하는 것입니다. 계산, 표시 등 동기화에 포함되는 코드가 많을수록 효율성에 미치는 영향은 더욱 심각해집니다. 그래서 우리는 일반적으로 영향 범위를 가능한 한 작게 유지하려고 노력합니다.
어떻게 하나요? 동기화된 코드 블록. 작업과 같이 동기화되어야 하는 메서드 부분만 동기화합니다.
또한, 동기화 코드 블록에서는 키를 지정할 수 있습니다. 이 기능은 일정 시간 내에 객체의 키를 점유할 수 있다는 추가적인 이점이 있습니다. 일반적인 상황에서
키를 사용하는 원칙을 아직도 기억하시나요? 이는 일반적인 상황이 아닙니다. 획득한 키는 영원히 반환되지 않으며 동기화된 코드 블록을 종료할 때만 반환됩니다.
두 개의 잠긴 방을 연속으로 사용하고 싶었던 이전 남자의 비유도 사용하십시오. 한 객실 이용 후 다른 객실을 계속 이용하려면 어떻게 해야 하나요? 동기화된 코드 블록을 사용합니다. 먼저 다른
스레드를 만들고 동기화 코드 블록을 만든 다음 해당 코드 블록의 잠금 장치가 집 열쇠를 가리키도록 합니다. 그런 다음 해당 스레드를 시작하십시오. 해당 코드 블록에 들어갈 때 집 열쇠를 얻을 수 있는 한
해당 코드 블록을 나갈 때까지 보관할 수 있습니다. 즉, 이 방의 잠긴 방을 모두 통과할 수도 있고, 심지어 잠(10*60*1000)까지 할 수도 있지만, 여전히
1000개의 스레드가 문에서 열쇠를 기다리고 있습니다. 꽤 즐겁습니다.
sleep() 메소드와 키의 상관관계에 대해 이야기해보겠습니다. 키를 가져온 후 스레드가 강제로 절전 모드()에 들어가고 동기화 콘텐츠가 완료되지 않은 경우 키는 여전히 그대로 유지됩니다
. 키는 다시 실행되어 모든 동기화 콘텐츠가 완료될 때까지 반환되지 않습니다. 그 사람은 일을 하다가 지쳐서 쉬러 갔다는 것을 기억하세요. 그는 하고 싶은 일을 끝내지 못했습니다.
다른 사람이 그 방에 들어와 내부를 어지럽히는 것을 막기 위해 잠잘 때도 유일한 열쇠를 차고 있어야 했다.
마지막으로, 문마다 열쇠가 하나씩 필요한 대신 왜 문을 여는 데 열쇠가 하나씩 필요한지 묻는 분들도 계실 것입니다. 나는 이것이 순전히 복잡성의 문제라고 생각합니다. 하나의 문에 하나의 열쇠가 있는 것이 확실히
더 안전하지만 많은 문제를 수반하게 됩니다. 키의 생성, 보관, 획득, 반환 등 동기화 방법이 증가함에 따라 복잡성이 기하학적으로 증가하여 효율성에 심각한 영향을 미칠 수 있습니다. 이것은
절충의 문제입니다. 보안을 조금 높이기 위해 효율성을 크게 줄이는 것은 얼마나 바람직하지 않은 일입니까?
동기화의 간단한 예
public class TextThread {
public static void main(String[] args) {
TxtThread tt = new TxtThread()
new Thread(tt).start();
new Thread(tt).start();
new Thread(tt).start(); ;
}
}
int num = 100;
String str = new String();
동기화됨(str) {
while(num > 0) {
Thread.sleep(1)
} catch(예외 e) {
e.getMessage();
}
System.out.println(Thread.currentThread().getName()
+ "이것은 " + num--)
}
}
}
}
동기화 방식인데 이때 어떤 객체가 동기화되어 잠겨있는 걸까요? 잠그는 것은 이 동기화된 메소드를 호출하는 객체입니다. 즉, 객체 P1이 서로 다른 스레드
에서 이 동기화 방법을 실행하면 이들 사이에 상호 배제가 형성되어 동기화 효과를 얻을 수 있습니다. 그러나 이 객체가 속한 Class에 의해 생성된 또 다른 객체 P2는
synchronized 키워드를 추가하여 이 메소드를 임의로 호출할 수 있습니다.
위 예제 코드는 다음 코드와 동일합니다.
public void methodAAA()
{
synchronized (this) // (1)
{
//…..
}
}
(1)의 내용은 무엇을 의미하나요? P1과 같이 이 메서드를 호출하는 개체를 나타냅니다. 동기화 방법의 본질은 개체 참조에 동기화를 적용하는 것임을 알 수 있습니다. ——
가 P1 개체 잠금을 획득한 스레드만 P1의 동기화 메서드를 호출할 수 있습니다. P2에 관한 한 P1 잠금은 프로그램에서도 벗어날 수 있습니다. 이 상황에서 동기화 메커니즘을 제어하면
데이터 혼란이 발생합니다. (
2. 동기화 블록, 샘플 코드는 다음과 같습니다.
public void method3 (SomeObject so)
{
synced(so)
{
//…..
}
}
이때 잠금은 객체이므로 잠금을 얻은 사람은 잠금으로 제어되는 코드를 실행할 수 있습니다. 잠금으로 명확한 개체가 있는 경우에는 다음과 같이 프로그램을 작성할 수 있지만 잠금. 코드 조각을 동기화하려는 경우 잠금 역할을 하는 특수 인스턴스 변수(객체여야 함)를 생성할 수 있습니다.
class Foo는 Runnable을 구현합니다
{
private byte[] lock = new byte[0]; // 특수 인스턴스 변수
Public void methodA()
{
synced(lock) { //… }
}
바이트코드: 길이가 0인 byte[] 객체를 생성하는 데는 3개의 opcode만 필요합니다. 반면 Object lock
= new Object() 7줄의 opcode가 필요합니다.
3. 동기화는 정적 함수에 대해 수행됩니다. 샘플 코드는 다음과 같습니다.
Class Foo
{ publicsynchronous static void methodAAA () // 동기화된 정적 함수{
// .... }
{
synced(Foo.class) / / class literal (클래스 이름 리터럴 상수)
}
"Effective Java"라는 책에서 동기화 잠금을 위해 Foo.class와 P1.getClass()를 사용하는 것이 다르다는 내용을 읽은 기억이 납니다. 이 클래스를 잠그려면 P1.getClass()를 사용할 수 없습니다. 🎜>목적. P1은 Foo 클래스에 의해 생성된 객체를 참조합니다.
추론 가능: 동기화된 정적 함수 A가 클래스에 정의되고 동기화된 인스턴스 함수 B도 정의된 경우 이 클래스의 동일한 개체 Obj
가 저장됩니다. 여러 스레드에서 별도로 두 가지 방법 A와 B에 액세스하면 잠금이 다르기 때문에 동기화가 발생하지 않습니다. 메소드 A의 잠금은 객체 Obj이고, 메소드 B의 잠금은 Obj가 속한 클래스입니다.
요약은 다음과 같습니다.
어떤 개체가 동기화되어 잠겨 있는지 이해하면 보다 안전한 멀티 스레드 프로그램을 설계하는 데 도움이 될 수 있습니다.
공유 리소스에 대한 동기식 액세스를 더욱 안전하게 만들 수 있는 몇 가지 기술도 있습니다.
1. 공용/보호된 인스턴스 변수 대신 개인용 인스턴스 변수 + 해당 get 메소드를 정의하십시오. 변수를 public으로 정의하면 외부에서 동기화 방식의 제어를 우회하여
직접 얻어서 변경할 수 있다. 이는 JavaBean의 표준 구현 방법 중 하나이기도 합니다.
2. 인스턴스 변수가 배열이나 ArrayList와 같은 객체인 경우, 위의 메소드는 여전히 안전하지 않습니다. 왜냐하면 외부 객체가 get 메소드를 통해 인스턴스 객체
의 참조를 얻을 때 이를 가리키기 때문입니다. 다른 개체를 사용하면 이 개인 변수도 변경됩니다. 매우 위험하지 않습니까? 이때 get 메소드와
에 동기화된 동기화를 추가하고 이 전용 객체의 clone()만 반환해야 합니다. 이런 방식으로 호출자가 얻는 것은 객체 복사본에 대한 참조입니다. 🎜>