Uso de sincronizado em Java

O artigo "Múltiplos Threads e Multiprocessos em Pensamentos de Programação (1) - Threads e Processos na Perspectiva do Sistema Operacional" descreve detalhadamente a relação entre threads e processos e seu desempenho no sistema operacional. Esta é a base que deve ser ser entendido para aprendizagem multi-thread. A seguir, este artigo falará sobre sincronizado, um conceito importante na sincronização de threads Java.
sincronizado é uma palavra-chave em Java e é um tipo de bloqueio de sincronização. Os objetos que ele modifica incluem o seguinte:

  1. Modifique um bloco de código. O bloco de código modificado é chamado de bloco de instrução de sincronização. Seu escopo é o código entre chaves {}, e o objeto sobre o qual ele atua é o objeto que chama esse bloco de código;
  2. Modificar um método. O método modificado é chamado de método de sincronização. Seu escopo é todo o método, e o objeto sobre o qual ele atua é o objeto que chama esse método;
  3. Modifique um método estático, seu escopo é todo o método estático e seu destino são todos os objetos desta classe;
  4. Ao modificar uma classe, seu escopo é a parte entre parênteses após sincronizada, e os objetos principais são todos objetos desta classe.

Modificando um bloco de código
Quando um thread acessa o bloco de código sincronizado (este) sincronizado em um objeto, outros threads que tentam acessar o objeto serão bloqueados. Vejamos o seguinte exemplo:
[Demo1]: uso de sincronização

/**
 * 同步线程
 */
class SyncThread implements Runnable {
    
    
   private static int count;
   public SyncThread() {
    
    
      count = 0;
   }
   public  void run() {
    
    
      synchronized(this) {
    
    
         for (int i = 0; i < 5; i++) {
    
    
            try {
    
    
               System.out.println(Thread.currentThread().getName() + ":" + (count++));
               Thread.sleep(100);
            } catch (InterruptedException e) {
    
    
               e.printStackTrace();
            }
         }
      }
   }
   public int getCount() {
    
    
      return count;
   }
}

Chamada SyncThread:

SyncThread syncThread = new SyncThread();
Thread thread1 = new Thread(syncThread, "SyncThread1");
Thread thread2 = new Thread(syncThread, "SyncThread2");
thread1.start();
thread2.start();

O resultado é o seguinte:

SyncThread1:0 SyncThread1:1 SyncThread1:2 SyncThread1:3 SyncThread1:4 SyncThread2:5 SyncThread2:6 SyncThread2:7 SyncThread2:8 SyncThread2:9*

Quando dois threads simultâneos (thread1 e thread2) acessam o bloco de código sincronizado no mesmo objeto (syncThread), apenas um thread pode ser executado ao mesmo tempo, e o outro thread é bloqueado e deve esperar que o thread atual termine de executar o bloco de código. O bloco de código só pode ser executado posteriormente. Thread1 e thread2 são mutuamente exclusivos, porque o objeto atual é bloqueado quando o bloco de código sincronizado é executado. Somente após a execução do bloco de código o bloqueio do objeto pode ser liberado e o próximo thread pode executar e bloquear o objeto.
Vamos mudar um pouco a chamada para SyncThread:

Thread thread1 = new Thread(new SyncThread(), "SyncThread1");
Thread thread2 = new Thread(new SyncThread(), "SyncThread2");
thread1.start();
thread2.start();

O resultado é o seguinte:

SyncThread1:0 SyncThread2:1 SyncThread1:2 SyncThread2:3 SyncThread1:4 SyncThread2:5 SyncThread2:6 SyncThread1:7 SyncThread1:8 SyncThread2:9

Isso não significa que quando um thread executa um bloco de código sincronizado, outros threads são bloqueados? Por que thread1 e thread2 estão sendo executados ao mesmo tempo no exemplo acima? Isso ocorre porque o sincronizado bloqueia apenas objetos, e cada objeto possui apenas um bloqueio associado a ele, e o código acima é equivalente ao seguinte código:

SyncThread syncThread1 = new SyncThread();
SyncThread syncThread2 = new SyncThread();
Thread thread1 = new Thread(syncThread1, "SyncThread1");
Thread thread2 = new Thread(syncThread2, "SyncThread2");
thread1.start();
thread2.start();

Neste momento, dois objetos SyncThread, syncThread1 e syncThread2, são criados. Thread thread1 executa o código sincronizado (run) no objeto syncThread1, e thread thread2 executa o código sincronizado (run) no objeto syncThread2; sabemos que objetos de bloqueios sincronizados , então haverá dois bloqueios bloqueando o objeto syncThread1 e o objeto syncThread2 respectivamente, e esses dois bloqueios não interferem um no outro e não formam exclusão mútua, portanto, os dois threads podem ser executados ao mesmo tempo.

2. Quando um thread acessa um bloco de código sincronizado (este) sincronizado de um objeto, outro thread ainda pode acessar o bloco de código não sincronizado (este) sincronizado no objeto.
[Demo2]: Vários threads acessam blocos de código sincronizados e não sincronizados

class Counter implements Runnable{
    
    
   private int count;
   public Counter() {
    
    
      count = 0;
   }
   public void countAdd() {
    
    
      synchronized(this) {
    
    
         for (int i = 0; i < 5; i ++) {
    
    
            try {
    
    
               System.out.println(Thread.currentThread().getName() + ":" + (count++));
               Thread.sleep(100);
            } catch (InterruptedException e) {
    
    
               e.printStackTrace();
            }
         }
      }
   }
   //非synchronized代码块,未对count进行读写操作,所以可以不用synchronized
   public void printCount() {
    
    
      for (int i = 0; i < 5; i ++) {
    
    
         try {
    
    
            System.out.println(Thread.currentThread().getName() + " count:" + count);
            Thread.sleep(100);
         } catch (InterruptedException e) {
    
    
            e.printStackTrace();
         }
      }
   }
   public void run() {
    
    
      String threadName = Thread.currentThread().getName();
      if (threadName.equals("A")) {
    
    
         countAdd();
      } else if (threadName.equals("B")) {
    
    
         printCount();
      }
   }
}

Código de chamada:

Counter counter = new Counter();
Thread thread1 = new Thread(counter, "A");
Thread thread2 = new Thread(counter, "B");
thread1.start();
thread2.start();

O resultado é o seguinte:

A:0 B count:1 A:1 B count:2 A:2 B count:3 A:3 B count:4 A:4 B count:5

No código acima, countAdd está sincronizado e printCount não está sincronizado. Pode-se observar pelos resultados acima que quando um thread acessa o bloco de código sincronizado de um objeto, outros threads podem acessar o bloco de código não sincronizado do objeto sem serem bloqueados.

Especifique para bloquear um objeto
[Demo3]: Especifique para bloquear um objeto

/**
 * 银行账户类
 */
class Account {
    
    
   String name;
   float amount;
   public Account(String name, float amount) {
    
    
      this.name = name;
      this.amount = amount;
   }
   //存钱
   public  void deposit(float amt) {
    
    
      amount += amt;
      try {
    
    
         Thread.sleep(100);
      } catch (InterruptedException e) {
    
    
         e.printStackTrace();
      }
   }
   //取钱
   public  void withdraw(float amt) {
    
    
      amount -= amt;
      try {
    
    
         Thread.sleep(100);
      } catch (InterruptedException e) {
    
    
         e.printStackTrace();
      }
   }
   public float getBalance() {
    
    
      return amount;
   }
}
/**
 * 账户操作类
 */
class AccountOperator implements Runnable{
    
    
   private Account account;
   public AccountOperator(Account account) {
    
    
      this.account = account;
   }
   public void run() {
    
    
      synchronized (account) {
    
    
         account.deposit(500);
         account.withdraw(500);
         System.out.println(Thread.currentThread().getName() + ":" + account.getBalance());
      }
   }
}

Código de chamada:

Account account = new Account("zhang san", 10000.0f);
AccountOperator accountOperator = new AccountOperator(account);
final int THREAD_NUM = 5;
Thread threads[] = new Thread[THREAD_NUM];
for (int i = 0; i < THREAD_NUM; i ++) {
    
    
   threads[i] = new Thread(accountOperator, "Thread" + i);
   threads[i].start();
}

O resultado é o seguinte:

Thread3:10000.0 Thread2:10000.0 Thread1:10000.0 Thread4:10000.0 Thread0:10000.0

No método run da classe AccountOperator, usamos sincronizado para bloquear o objeto conta. Neste momento, quando um thread acessa o objeto de conta, outros threads que tentam acessar o objeto de conta serão bloqueados até que o thread acesse o objeto de conta. Em outras palavras, quem obtiver o bloqueio poderá executar o código que ele controla.
Quando você tem um objeto limpo como bloqueio, você pode escrever um programa de maneira semelhante à seguinte.

public void method3(SomeObject obj)
{
    
    
   //obj 锁定的对象
   synchronized(obj)
   {
    
    
      // todo
   }
}

Quando você não tem um objeto explícito para atuar como bloqueio e deseja apenas que um trecho de código seja sincronizado, você pode criar um objeto especial para atuar como bloqueio:

c

lass Test implements Runnable
{
    
    
   private byte[] lock = new byte[0];  // 特殊的instance变量
   public void method()
   {
    
    
      synchronized(lock) {
    
    
         // todo 同步代码块
      }
   }
   public void run() {
    
    
   }
}

Descrição: Um objeto de matriz de bytes de comprimento zero será mais econômico de criar do que qualquer objeto - veja o bytecode compilado: gerar um objeto byte[] de comprimento zero requer apenas 3 opcodes, enquanto Object lock = new Object() Requer 7 linhas de Código de operação.

Modificar um método
Sincronizado Modificar um método é muito simples, basta adicionar sincronizado na frente do método, publicsynchronous void method(){//todo}; O método de modificação sincronizada é semelhante a modificar um bloco de código, mas o escopo é diferente. O bloco de código modificado é o escopo entre chaves, enquanto o escopo do método modificado é a função inteira. Se você alterar o método de execução em [Demo1] para o método a seguir, o efeito será o mesmo.
*[Demo4]: sincronizado modifica um método

public synchronized void run() {
    
    
   for (int i = 0; i < 5; i ++) {
    
    
      try {
    
    
         System.out.println(Thread.currentThread().getName() + ":" + (count++));
         Thread.sleep(100);
      } catch (InterruptedException e) {
    
    
         e.printStackTrace();
      }
   }
}

Atos sincronizados em todo o método.
Método de escrita um:

public synchronized void method()
{
    
    
   // todo
}

Método de escrita dois:

public void method()
{
    
    
   synchronized(this) {
    
    
      // todo
   }
}

A primeira forma de escrever modifica um método, e a segunda forma de escrever modifica um bloco de código, mas a primeira forma de escrever e a segunda forma de escrever são equivalentes, ambas bloqueiam o conteúdo de todo o método.
Preste atenção aos seguintes pontos ao usar métodos de modificação sincronizados:

  1. A palavra-chave sincronizada não pode ser herdada.
    Embora você possa usar sincronizado para definir métodos, sincronizado não faz parte da definição do método, portanto, a palavra-chave sincronizada não pode ser herdada. Se um método na classe pai usa a palavra-chave sincronizada e substitui esse método na subclasse, o método na subclasse não é sincronizado por padrão e deve ser especificado explicitamente na subclasse. Basta adicionar a palavra-chave sincronizada ao método. Claro, você também pode chamar o método correspondente na classe pai no método da subclasse. Desta forma, embora o método na subclasse não seja síncrono, a subclasse chama o método de sincronização da classe pai. Portanto, o método da classe pai. subclasse é equivalente à sincronização. Os códigos de exemplo para esses dois métodos são os seguintes:
    Adicione a palavra-chave sincronizada ao método da subclasse
class Parent {
    
    
   public synchronized void method() {
    
     }
}
class Child extends Parent {
    
    
   public synchronized void method() {
    
     }
}

Chame o método sincronizado da classe pai no método da subclasse

class Parent {
    
    
   public synchronized void method() {
    
       }
}
class Child extends Parent {
    
    
   public void method() {
    
     super.method();   }
}

A palavra-chave sincronizada não pode ser usada ao definir métodos de interface.
O construtor não pode usar a palavra-chave sincronizada, mas pode usar blocos de código sincronizados para sincronização.

Modificando um método estático
Sincronizado também pode modificar um método estático. O uso é o seguinte:

public synchronized static void method() {
    
    
   // todo
}

Sabemos que os métodos estáticos pertencem a classes e não a objetos. Da mesma forma, o método estático modificado por sincronizado bloqueia todos os objetos desta classe. Fazemos algumas modificações no Demo1 como segue:
[Demo5]: método estático modificado sincronizado

/**
 * 同步线程
 */
class SyncThread implements Runnable {
    
    
   private static int count;
   public SyncThread() {
    
    
      count = 0;
   }
   public synchronized static void method() {
    
    
      for (int i = 0; i < 5; i ++) {
    
    
         try {
    
    
            System.out.println(Thread.currentThread().getName() + ":" + (count++));
            Thread.sleep(100);
         } catch (InterruptedException e) {
    
    
            e.printStackTrace();
         }
      }
   }
   public synchronized void run() {
    
    
      method();
   }
}

Código de chamada:

SyncThread syncThread1 = new SyncThread();
SyncThread syncThread2 = new SyncThread();
Thread thread1 = new Thread(syncThread1, "SyncThread1");
Thread thread2 = new Thread(syncThread2, "SyncThread2");
thread1.start();
thread2.start();

O resultado é o seguinte:

SyncThread1:0 SyncThread1:1 SyncThread1:2 SyncThread1:3 SyncThread1:4 SyncThread2:5 SyncThread2:6 SyncThread2:7 SyncThread2:8 SyncThread2:9

syncThread1 e syncThread2 são dois objetos de SyncThread, mas a sincronização do thread é mantida quando thread1 e thread2 são executados simultaneamente. Isso ocorre porque o método estático é chamado em execução e o método estático pertence à classe, portanto, syncThread1 e syncThread2 são equivalentes a usar o mesmo bloqueio. Isso é diferente do Demo1.

Modificando uma classe
Sincronizado também pode atuar em uma classe. O uso é o seguinte:

class ClassName {
    
    
   public void method() {
    
    
      synchronized(ClassName.class) {
    
    
         // todo
      }
   }
}

Vamos fazer algumas modificações no Demo5.
【Demo6】: Modifique uma classe

/**
 * 同步线程
 */
class SyncThread implements Runnable {
    
    
   private static int count;
   public SyncThread() {
    
    
      count = 0;
   }
   public static void method() {
    
    
      synchronized(SyncThread.class) {
    
    
         for (int i = 0; i < 5; i ++) {
    
    
            try {
    
    
               System.out.println(Thread.currentThread().getName() + ":" + (count++));
               Thread.sleep(100);
            } catch (InterruptedException e) {
    
    
               e.printStackTrace();
            }
         }
      }
   }
   public synchronized void run() {
    
    
      method();
   }
}

O efeito é o mesmo de [Demo5], quando sincronizado atua sobre uma classe T, ele bloqueia a classe T. Todos os objetos de T usam o mesmo bloqueio.

Acho que você gosta

Origin blog.csdn.net/www61621/article/details/129248772
Recomendado
Clasificación