>  기사  >  Java  >  Java의 동기화 키워드에 대한 심층적 이해

Java의 동기화 키워드에 대한 심층적 이해

高洛峰
高洛峰원래의
2016-12-13 11:15:321079검색

동기화된 키워드는 이 메서드의 잠금을 나타냅니다. 즉, 어떤 스레드 A가 매번 이 메서드를 실행하더라도 이 메서드를 사용하는 다른 스레드 B(또는 CD 등)가 있는지 확인해야 한다는 의미입니다. 그렇다면 이 스레드 A를 실행하기 전에 이 메서드를 사용하는 스레드 B(또는 C D)가 이 메서드 실행을 완료할 때까지 기다려야 합니다. 그렇지 않은 경우 동기화된 메서드와 동기화된 블록의 두 가지 사용법을 포함하여 직접 실행하십시오.

1. 동기화 메소드:
메소드 선언부에 동기화 키워드를 추가하여 동기화 메소드를 선언합니다. 예:

public synchronized void accessVal(int newVal); 

동기화된 메서드는 클래스 멤버 변수에 대한 액세스를 제어합니다. 각 클래스 인스턴스는 잠금에 해당하며 각 동기화된 메서드는 메서드를 호출하는 클래스 인스턴스의 잠금을 획득해야 합니다. 그렇지 않으면 해당 스레드가 차단된 경우 메서드가 실행되면 해당 스레드가 잠금을 독점적으로 차지합니다. 그 후 잠금은 메서드에서 반환될 때까지 해제되지 않습니다. -실행 가능한 상태로 들어갑니다. 이 메커니즘은 각 클래스 인스턴스에 대해 동시에 동기화된 것으로 선언된 모든 멤버 함수 중 최대 하나가 실행 가능 상태에 있도록 보장합니다(최대 하나는 클래스 인스턴스에 해당하는 잠금을 얻을 수 있기 때문입니다). 클래스 멤버의 변수 액세스 위반(클래스 멤버 변수에 액세스할 수 있는 모든 메소드가 동기화로 선언된 경우). Java에서는 클래스 인스턴스뿐만 아니라 각 클래스도 잠금에 해당합니다. 이러한 방식으로 클래스의 정적 멤버 변수에 대한 액세스를 제어하기 위해 클래스의 정적 멤버 함수를 선언할 수도 있습니다. 동기화된 메소드의 결함: 큰 메소드가 동기화됨으로 선언되면 효율성이 크게 영향을 받습니다. 일반적으로 스레드 클래스 메소드 run()이 동기화됨으로 선언되면 스레드의 전체 수명 주기 동안 계속 실행됩니다. . 이 클래스의 동기화된 메서드에 대한 호출이 절대 성공하지 못하게 됩니다. 물론 클래스 멤버 변수에 접근하는 코드를 특수 메소드에 넣어서 동기화된 것으로 선언하고 이를 메인 메소드에서 호출하면 이 문제를 해결할 수 있지만, 자바는 더 나은 솔루션을 제공하는데, 바로 동기화 블록이다.

2. 동기화 블록:
Synchronized 키워드를 통해 동기화 블록을 선언합니다. 구문은 다음과 같습니다:

synchronized(syncObject)
{  
//允许访问控制的代码  
} 

동기화된 블록은 코드가 개체 전에 syncObject(앞에서 언급한 것처럼 클래스 인스턴스 또는 클래스일 수 있음)의 잠금을 획득해야 하는 코드 블록입니다. 구체적인 메커니즘은 앞서 설명한 것과 동일합니다. 어떤 코드블록이라도 대상으로 할 수 있고 잠긴 객체를 임의로 지정할 수 있어 유연성이 높다.

동기화(this)에 대한 이해
1. 두 개의 동시 스레드가 동일한 객체 개체의 동기화(this) 동기화 코드 블록에 액세스할 때 한 번에 하나의 스레드만 이를 얻을 수 있습니다. 다른 스레드는 이 코드 블록을 실행하기 전에 현재 스레드가 이 코드 블록 실행을 완료할 때까지 기다려야 합니다. ​

2. 스레드가 객체의 동기화된(this) 동기화 코드 블록에 액세스하면 다른 스레드의 객체에 있는 다른 모든 동기화(this) 동기화 코드 블록에 대한 액세스가 차단됩니다.

3. 그러나 스레드가 객체의 동기화된(this) 동기화 코드 블록에 액세스하면 다른 스레드는 여전히 동기화된(this) 동기화 코드 블록이 아닌 객체의 일부에 액세스할 수 있습니다.

4. 세 번째 예는 다른 동기화 코드 블록에도 적용 가능합니다. 즉, 스레드가 개체의 동기화된(this) 동기화 코드 블록에 액세스하면 이 개체의 개체 잠금을 획득합니다. 결과적으로 개체 개체의 모든 동기화된 코드 부분에 대한 다른 스레드의 액세스가 일시적으로 차단됩니다. ​

5. 위의 규칙은 다른 객체 잠금에도 적용됩니다.

동기화의 간단한 예

public class TextThread
{
/**
* @param args
*/
public static void main(String[] args)
{
// TODO 自动生成方法存根
        TxtThread tt = new TxtThread();
        new Thread(tt).start();
        new Thread(tt).start();
        new Thread(tt).start();
        new Thread(tt).start();
}
}
class TxtThread implements Runnable
{
int num = 100;
String str = new String();
public void run()
{
while (true)
{
   synchronized(str)
   {
   if (num>0)
   {
    try
    {
     Thread.sleep(10);
    }
    catch(Exception e)
    {
     e.getMessage();
    }
    System.out.println(Thread.currentThread().getName()+ "this is "+ num--);
   }
   }
}
}
}

위의 예에서는 오류의 기회인 시차를 발생시키기 위해 Java의 Thread.sleep(10)을 사용한다. 멀티스레딩 지원 및 동기화 메커니즘은 모두가 좋아하는 것 같습니다. 동기화 키워드를 사용하면 멀티스레딩 공유 데이터 동기화 문제를 쉽게 해결할 수 있는 것 같습니다. 어떻게? ——결론을 내리기 전에 동기화 키워드의 역할에 대한 심층적인 이해가 필요합니다.

일반적으로 동기화 키워드는 함수의 수식어로 사용될 수도 있고, 함수 내의 명령문으로 사용될 수도 있는데, 이를 보통 동기화 방법, 동기화 명령문 블록이라고 합니다. 더 자세히 분류된 경우 동기화는 인스턴스 변수, 개체 참조(객체 참조), 정적 함수 및 클래스 리터럴(클래스 이름 리터럴)에 대해 작동할 수 있습니다. 더 자세히 설명하기 전에 몇 가지 사항을 명확히 해야 합니다.

A. 동기화된 키워드가 메소드나 객체에 추가되었는지 여부에 관계없이, 이것이 획득하는 잠금은 코드 조각이나 함수를 잠금으로 취급하는 것이 아니라 객체입니다. 그리고 동기화된 메소드는 다른 객체에 의해 액세스될 가능성이 높습니다. 스레드.

B. 각 객체에는 연관된 잠금이 하나만 있습니다.

다. 동기화를 달성하려면 시스템 오버헤드가 많이 필요하고 교착 상태가 발생할 수도 있으므로 불필요한 동기화 제어를 피하십시오.

1. 함수 수정자로 동기화를 사용하는 경우 샘플 코드는 다음과 같습니다.

Public synchronized void methodAAA()
{
//…
}

这也就是同步方法,那这时synchronized锁定的是哪个对象呢?它锁定的是调用这个同步方法对象。也就是说,当一个对象P1在不同的线程中执行这个同步方法时,它们之间会形成互斥,达到同步的效果。但是这个对象所属的Class所产生的另一对象P2却可以任意调用这个被加了synchronized关键字的方法。上边的示例代码等同于如下代码:

public void methodAAA()
{
synchronized (this)      // (1)
{
       //…..
}
}

(1)处的this指的是什么呢?它指的就是调用这个方法的对象,如P1。可见同步方法实质是将synchronized作用于object reference。――那个拿到了P1对象锁的线程,才可以调用P1的同步方法,而对P2而言,P1这个锁与它毫不相干,程序也可能在这种情形下摆脱同步机制的控制,造成数据混乱。


2.同步块,示例代码如下:

public void method3(SomeObject so)
{
    synchronized(so)
    {
       //…..
    }
}

这时,锁就是so这个对象,谁拿到这个锁谁就可以运行它所控制的那段代码。当有一个明确的对象作为锁时,就可以这样写程序,但当没有明确的对象作为锁,只是想让一段代码同步时,可以创建一个特殊的instance变量(它得是一个对象)来充当锁:

class Foo implements Runnable
{
        private byte[] lock = new byte[0]; // 特殊的instance变量
        Public void methodA()
        {
           synchronized(lock) { //… }
        }
        //…..
}

注:零长度的byte数组对象创建起来将比任何对象都经济――查看编译后的字节码:生成零长度的byte[]对象只需3条操作码,而Object lock = new Object()则需要7行操作码。

3.将synchronized作用于static 函数,示例代码如下:

Class Foo
{
    public synchronized static void methodAAA()   // 同步的static 函数
    {
        //….
    }
    public void methodBBB()
    {
       synchronized(Foo.class)   // class literal(类名称字面常量)
    }
}

代码中的methodBBB()方法是把class literal作为锁的情况,它和同步的static函数产生的效果是一样的,取得的锁很特别,是当前调用这个方法的对象所属的类(Class,而不再是由这个Class产生的某个具体对象了)。

记得在《Effective Java》一书中看到过将 Foo.class和 P1.getClass()用于作同步锁还不一样,不能用P1.getClass()来达到锁这个Class的目的。P1指的是由Foo类产生的对象。可以推断:如果一个类中定义了一个synchronized的static函数A,也定义了一个synchronized 的instance函数B,那么这个类的同一对象Obj在多线程中分别访问A和B两个方法时,不会构成同步,因为它们的锁都不一样。A方法的锁是Obj这个对象,而B的锁是Obj所属的那个Class。

小结如下:

搞清楚synchronized锁定的是哪个对象,就能帮助我们设计更安全的多线程程序。 还有一些技巧可以让我们对共享资源的同步访问更加安全:1.定义private 的instance变量+它的 get方法,而不要定义public/protected的instance变量。如果将变量定义为public,对象在外界可以绕过同步方法的控制而直接取得它,并改动它。这也是JavaBean的标准实现方式之一。2.如果instance变量是一个对象,如数组或ArrayList什么的,那上述方法仍然不安全,因为当外界对象通过get方法拿到这个instance对象的引用后,又将其指向另一个对象,那么这个private变量也就变了,岂不是很危险。这个时候就需要将get方法也加上synchronized同步,并且,只返回这个private对象的clone()――这样,调用端得到的就是对象副本的引用了  还有,比较常用的就有:Collections.synchronizedMap(new HashMap()),当然这个MAP就是生命在类中的全局变量,就是一个线程安全的HashMap,web的application是全web容器公用的,所以要使用线程安全来保证数据的正确。


성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.