Método de comunicação de dados entre vários threads

Método de comunicação de dados entre vários threads

Modelo de consumidor produtor

O problema de produção / consumidor é um problema multithread muito típico e os objetos envolvidos incluem produtores, consumidores, depósitos e produtos. A relação entre eles é a seguinte:

  • O produtor só produz quando o armazém não está cheio e para a produção quando o armazém está cheio.
  • Os consumidores só podem consumir quando há produtos no armazém e esperar quando o armazém está vazio.
  • Quando o consumidor descobrir que o armazém não tem produtos para consumir, o produtor será notificado para produzir.
  • Quando um produtor produz um produto de consumo, ele deve notificar o consumidor em espera para consumi-lo.

Implementação de codificação

Categoria do armazém, o produto é o atributo dos dados do armazém

//临界资源
public class Basket {
    
    
	private volatile Object data;
	//生产者向仓库中存放数据
	public synchronized void product(Object data) {
    
    
		//如果仓库中有数据,则生产者进入阻塞等待,直到其它线程唤醒
		while(this.data!=null)
			try {
    
    
				this.wait();
			} catch (InterruptedException e) {
    
    
				e.printStackTrace();
			}
		//如果没有数据则进行生产操作
		this.data=data;
		System.out.println(Thread.currentThread().getName()+"生产了一个日期"+this.data);
		this.notifyAll();//唤醒在当前对象上处于wait的所有线程
	}
	//消费者从仓库中消费数据
	public synchronized void consume() {
    
    
		//如果仓库中没有数据,则消费者进入阻塞等待,直到其它线程唤醒
		while(this.data==null)
			try {
    
    
				this.wait();
			} catch (InterruptedException e) {
    
    
				e.printStackTrace();
			}
		//如果有数据data!=null,则执行消费操作
		System.out.println(Thread.currentThread().getName()+"消费了一个数据"+this.data);
		this.data=null;
		this.notifyAll();  //唤醒在当前对象上处于wait的所有线程
	}
}

O segmento produtor é responsável por produzir os produtos e compartilhar armazéns com os consumidores

public class Producer extends Thread {
    
    
	private Basket basket;
	//通过构造器传入对应的basket对象
	public Producer(Basket basket) {
    
    
		this.basket=basket;
	}
	@Override
	public void run() {
    
    
		//生产20次日期对象
		for(int i=0;i<20;i++) {
    
    
			Object data=new Date();  //生产者生产的具体产品
			basket.product(data);
		}
	}
}

O segmento do consumidor é responsável por consumir o produto e compartilhar o armazém com o produtor

public class Consumer extends Thread {
    
    
	private Basket basket;

	public Consumer(Basket basket) {
    
    
		this.basket = basket;
	}

	@Override
	public void run() {
    
    
		// 消费20次日期对象
		for (int i = 0; i < 20; i++) {
    
    
			basket.consume();
		}
	}
}

Produtores e consumidores compartilham o mesmo espaço de armazenamento no mesmo período de tempo. Os produtores adicionam produtos ao espaço de armazenamento e os consumidores removem produtos do espaço de armazenamento. Quando o espaço de armazenamento está vazio, o consumidor bloqueia, e quando o espaço de armazenamento está cheio Quando o produtor está bloqueado.

Por que usar o modelo produtor / consumidor

No mundo encadeado, o produtor é o encadeamento que produz dados e o consumidor é o encadeamento que consome os dados. No desenvolvimento multithread, se a velocidade de processamento do produtor for muito rápida, mas a velocidade de processamento do consumidor for muito lenta, o produtor deve esperar que o consumidor termine o processamento antes de continuar a produzir dados. Da mesma forma, se o poder de processamento do consumidor for maior que o do produtor, então o consumidor deve esperar pelo produtor. Para resolver este problema do desequilíbrio na capacidade de produção e consumo, existe um modelo produtor e consumidor.

Vantagens do modelo produtor / consumidor

1. Decoupling. Como há um buffer extra, o produtor e o consumidor não se ligam diretamente. Isso é fácil de pensar. Dessa forma, o código do produtor e do consumidor não afetarão um ao outro. Na verdade, o produtor e o consumidor não será afetado. O forte acoplamento entre o consumidor e o consumidor é desvendado e torna-se o acoplamento fraco entre o produtor e o buffer / consumidor e o buffer

2. Melhorar a velocidade geral de processamento de dados equilibrando as capacidades de processamento de produtores e consumidores Esta é uma das vantagens mais importantes do modelo produtor / consumidor. Se o consumidor obtém os dados diretamente do produtor, se a velocidade de produção do produtor é muito lenta, mas a velocidade de consumo do consumidor é muito rápida, então o consumidor terá que ocupar a fatia de tempo da CPU e esperar por nada. Com o modelo produtor / consumidor, o produtor e o consumidor são duas entidades concorrentes independentes. O produtor apenas joga os dados produzidos no buffer, independentemente do consumidor; o consumidor também é do buffer. Basta obter os dados e você não t tem que se preocupar com o produtor, se o pulmão estiver cheio não será feita produção, e se o pulmão estiver vazio não será feito nenhum consumo, para que a capacidade de processamento do produtor / consumidor alcance um equilíbrio dinâmico.

O papel do modelo produtor / consumidor

  • Concorrência de suporte
  • Dissociação
  • Suporta disponibilidade desigual

Métodos de chamada, como esperar / notificar, devem estar dentro do objeto de thread atual, como no método sincronizado

Singleton

Certifique-se de que haja apenas uma instância na VM

  • Modo homem faminto
    • Construtor privado
    • Instância de Singleton estática privada = new Singleton ();
    • Método estático compartilhado
  • Modo homem preguiçoso
    • Construtor privado
    • Propriedades estáticas privadas, não crie objetos diretamente
    • Método estático compartilhado
public class Singleton {
    
    
	private Singleton() {
    
    }
	private static Singleton instance;
	public static Singleton getInstance() {
    
    
		if(instance==null)
			instance=new Singleton();   //当第一次使用对象时才进行创建
		return instance;
	}
}

Pode haver várias criações do objeto, como resolvê-lo

public class Singleton {
    
    
	private Singleton() {
    
    }
	private static Singleton instance;
	public synchronized static Singleton getInstance() {
    
    
		if(instance==null)
			instance=new Singleton();   //当第一次使用对象时才进行创建
		return instance;
	}
}

Geralmente, não é recomendado usar um mecanismo de processamento de bloqueio mais granular, pois a simultaneidade será afetada

Modo preguiçoso de detecção dupla

  • Crie objetos sob demanda para evitar operações de criação inúteis

  • Discussão segura

public class Singleton{
    
     
    private Singleton(){
    
    }
    private static Singleton instance=null;
    public static Singleton getInstance(){
    
    
    	if(instance==null){
    
    
    		synchronized(Singleton.class){
    
    
    			if(instance==null)
    	    	    instance=new Singleton();
    	    }
    	 }   
        return instance;
    }
}

Uso de fechadura

Lock é uma ferramenta de sincronização de threads introduzida no Java 1.5. É usada principalmente para controlar recursos compartilhados em vários threads. Em essência, Lock é apenas uma interface. A sincronização pode ser alcançada definindo explicitamente um objeto de bloqueio de sincronização. Ele pode fornecer uma gama mais ampla de operações de bloqueio do que sincronizadas e oferece suporte a vários objetos Condition relacionados.

  • void lock (); tentativa de adquirir o bloqueio, retorna se a aquisição for bem-sucedida, caso contrário, bloqueie o segmento atual

void lockInterruptibly () lança InterruptedException; tente adquirir o bloqueio, o encadeamento é interrompido antes de adquirir o bloqueio com sucesso, então desista de adquirir o bloqueio e lança uma exceção

boolean tryLock (); Tenta adquirir o bloqueio, retorna verdadeiro se o bloqueio for adquirido com sucesso, caso contrário retorna falso

boolean tryLock (longo tempo, unidade TimeUnit) tenta adquirir o bloqueio. Se o bloqueio for adquirido dentro do tempo especificado, ele retorna verdadeiro, caso contrário, retorna falso. Se for interrompido antes de adquirir o bloqueio, uma exceção é lançada.

  • void unlock (); libere o bloqueio
  • Condition newCondition (); retorna a variável de condição do bloqueio atual. A função semelhante a notificar e esperar pode ser realizada por meio da variável de condição. Um bloqueio pode ter várias variáveis ​​de condição

Lock tem três classes de implementação, uma é ReentrantLock e as outras duas são as duas classes internas estáticas ReadLock e WriteLock na classe ReentrantReadWriteLock.

Uso: Ao acessar recursos compartilhados (mutuamente exclusivos) sob multithreading, bloqueie antes de acessar e desbloqueie depois de acessar.Recomenda-se que a operação de desbloqueio seja colocada no bloco finally.

bloqueio ReentrantLock final privado = novo ReentrantLock ();

No método específico lock.lock () tente {} finalmente {lock.unlock}

Lock lock=new ReentrantLock();//构建锁对象
try{
    
    
	lock.lock();//申请锁,如果可以获取锁则立即返回,如果锁已经被占用则阻塞等待
	System.out.println(lock);//执行处理逻辑
} finally{
    
    
	lock.unlock();//释放锁,其它线程可以获取锁
}

Exemplo 1: iniciar 4 threads, realizar 50 operações de adição e subtração em um número int, exigindo 2 adições e 2 subtrações para garantir a segurança da thread de saída

public class OperNum {
    
    
	private int num=0;	
	private final static Lock lock=new ReentrantLock();  //构建锁对象
	public void add() {
    
    
		try {
    
    
			lock.lock();  //申请加锁操作,如果能加上则立即返回,否则阻塞当前线程
			num++;
			System.out.println(Thread.currentThread().getName()+"add..."+num);
		} finally {
    
    
			lock.unlock(); //具体实现采用的是重入锁,所以持有锁的线程可以多次申请同一个锁,但是申请加锁次数必须和释放锁的次数一致
		}
	}
	public void sub() {
    
    
		try {
    
    
			lock.lock();
			num--;
			System.out.println(Thread.currentThread().getName()+"sub..."+num);
		} finally {
    
    
			lock.unlock();
		}
	}
}

Interface de condição

Condition é uma interface fornecida no pacote juc. Ele pode ser traduzido em um objeto de condição e sua função é que o thread espere primeiro e, quando uma determinada condição for atendida externamente, o thread de espera é despertado por meio do objeto de condição.

void await () lança InterruptedException; Deixe o thread entrar na espera. Se outros threads chamarem notificar / notificarAll do mesmo objeto Condition, o thread em espera pode ser ativado. Solte o cadeado

void signal (); ativa o thread de espera

void signalAll (); ativa todos os tópicos

Pontos especiais de uso Condição:

  • Quando condition.await () é chamado para bloquear o encadeamento, o bloqueio será automaticamente liberado, não importa quantos lock.lock () seja chamado, então o encadeamento bloqueado no método lock.lock () pode adquirir o bloqueio
  • Quando condition.signal () é chamado para despertar o thread, ele continuará a executar na última posição bloqueada, e o bloqueio será readquirido automaticamente por padrão (observe que o número de bloqueios adquiridos durante o bloqueio é o mesmo)

Acho que você gosta

Origin blog.csdn.net/qq_43480434/article/details/114153429
Recomendado
Clasificación