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)